---
id: S02
parent: M003
milestone: M003
provides:
  - Per-stage LLM model/modality routing
  - strip_think_tags() utility for reasoning model output
requires:
  []
affects:
  []
key_files:
  - backend/config.py
  - backend/pipeline/llm_client.py
  - backend/pipeline/stages.py
  - backend/tests/test_pipeline.py
  - .env.example
key_decisions:
  - Per-stage config via Settings fields with getattr-based helper
  - Thinking modality appends JSON instructions to system prompt (no response_format)
  - strip_think_tags uses re.DOTALL regex
patterns_established:
  - Per-stage config pattern: Settings fields + _get_stage_config helper with getattr
  - Modality-aware LLM client: chat (response_format) vs thinking (prompt instructions + tag stripping)
observability_surfaces:
  - INFO log per stage showing model and modality used
  - INFO log per LLM request showing model, modality, response_model
drill_down_paths:
  - .gsd/milestones/M003/slices/S02/tasks/T01-SUMMARY.md
  - .gsd/milestones/M003/slices/S02/tasks/T02-SUMMARY.md
  - .gsd/milestones/M003/slices/S02/tasks/T03-SUMMARY.md
  - .gsd/milestones/M003/slices/S02/tasks/T04-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-03-30T02:12:55.741Z
blocker_discovered: false
---

# S02: Per-Stage LLM Model Routing + Think-Tag Stripping

**Per-stage LLM model/modality routing with think-tag stripping — 59 tests pass**

## What Happened

Implemented per-stage LLM model routing across 4 tasks. Each pipeline stage (2-5) can now independently configure which model and modality (chat vs thinking) to use. The LLMClient handles modality differences: chat uses response_format json_object, thinking appends JSON instructions and strips <think> tags. All 59 tests pass including the new think-tag stripping test.

## Verification

59/59 tests pass, imports work, config defaults correct

## Requirements Advanced

None.

## Requirements Validated

None.

## New Requirements Surfaced

None.

## Requirements Invalidated or Re-scoped

None.

## Deviations

None.

## Known Limitations

None.

## Follow-ups

None.

## Files Created/Modified

- `backend/config.py` — Added 8 per-stage model/modality config fields
- `backend/pipeline/llm_client.py` — Modality-aware complete() with strip_think_tags()
- `backend/pipeline/stages.py` — Stages 2-5 use _get_stage_config, pass modality/model_override
- `backend/tests/test_pipeline.py` — Added test_strip_think_tags with 7 cases
- `.env.example` — Documented per-stage LLM vars with modality comments
