You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Audit finding (2026-04-22):anthropic is not in requirements.txt and ANTHROPIC_API_KEY / ARIA_MODEL are absent from core/config.py.
Both are required before any M3 code can run. Add them in this issue.
Do NOT wrap messages.create(...) in a helper that forces stream=False.
M4.5 (Investigator extended thinking) calls anthropic.messages.create(stream=True)
directly on the singleton. Export the raw object.
model_for()
Warning
The original spec used Literal["dev", "vision", "agent"]. "agent" was
ambiguous — Investigator needs Opus for extended thinking, KB Builder Q&A needs
only Sonnet. Renamed to prevent coupling.
fromtypingimportLiteral_SONNET="claude-sonnet-4-5"_OPUS="claude-opus-4-7"defmodel_for(use_case: Literal["extraction", "vision", "reasoning", "chat"]) ->str:
"""Return the model slug for a given use case. - vision : Opus — PDF document vision (Opus only) - reasoning : Opus — Investigator extended thinking (Opus only) - extraction : Sonnet — onboarding patch extraction from free text - chat : Sonnet — KB Builder answer_kb_question, interactive Q&A (ALWAYS Sonnet regardless of ARIA_MODEL — calling KB Builder with Opus when ARIA_MODEL=opus would be 10x cost for no benefit) """ifuse_casein ("vision", "reasoning"):
return_OPUSreturn_SONNET
parse_json_response()
Claude often wraps JSON in code fences or adds preamble text. This helper is
used by answer_kb_question (M3.5) and the onboarding patch extractor (M3.3).
importjsonimportrefromanthropic.typesimportMessagedefparse_json_response(response: Message) ->dict:
"""Extract the first JSON object from a Claude Message. Handles raw JSON, ```json fences, and JSON preceded by preamble text. Raises ValueError if no valid JSON is found. """text=next(
(block.textforblockinresponse.contentifhasattr(block, "text")),
None,
)
ifnottext:
raiseValueError("no text block in response")
# 1. Direct parsetry:
returnjson.loads(text)
exceptjson.JSONDecodeError:
pass# 2. Strip ```json ... ``` fencesm=re.search(r"```(?:json)?\s*([\s\S]*?)```", text)
ifm:
try:
returnjson.loads(m.group(1).strip())
exceptjson.JSONDecodeError:
pass# 3. Find first {...} blockm=re.search(r"\{[\s\S]*\}", text)
ifm:
try:
returnjson.loads(m.group(0))
exceptjson.JSONDecodeError:
passraiseValueError(f"could not parse JSON from response: {text[:200]}")
4. Acceptance
await anthropic.messages.create(model=model_for("chat"), max_tokens=64, messages=[{"role": "user", "content": "ping"}]) returns a response
Note
Milestone: M3 — KB Builder Agent
Planning doc:
docs/planning/M3-kb-builder/issues.mdScope. Create
backend/agents/anthropic_client.pywith:AsyncAnthropicsingleton (timeout 60 s, max 2 retries)model_for(use_case)helper — maps use cases to model slugsparse_json_response(response)helper — strips fences, extracts JSONImportant
Audit finding (2026-04-22):
anthropicis not inrequirements.txtandANTHROPIC_API_KEY/ARIA_MODELare absent fromcore/config.py.Both are required before any M3 code can run. Add them in this issue.
1.
backend/requirements.txtAdd:
Verify the exact latest slug at https://docs.anthropic.com/en/docs/about-claude/models
on implementation day.
2.
backend/core/config.pyAdd to the
Settingsclass:Both are read from
.env/ Docker environment automatically bypydantic-settings.3.
backend/agents/anthropic_client.pySingleton
Note
Do NOT wrap
messages.create(...)in a helper that forcesstream=False.M4.5 (Investigator extended thinking) calls
anthropic.messages.create(stream=True)directly on the singleton. Export the raw object.
model_for()Warning
The original spec used
Literal["dev", "vision", "agent"]."agent"wasambiguous — Investigator needs Opus for extended thinking, KB Builder Q&A needs
only Sonnet. Renamed to prevent coupling.
parse_json_response()Claude often wraps JSON in code fences or adds preamble text. This helper is
used by
answer_kb_question(M3.5) and the onboarding patch extractor (M3.3).4. Acceptance
await anthropic.messages.create(model=model_for("chat"), max_tokens=64, messages=[{"role": "user", "content": "ping"}])returns a responsemodel_for("vision")always returnsclaude-opus-4-7model_for("chat")always returnsclaude-sonnet-4-5even whenARIA_MODEL=opusparse_json_responsecorrectly extracts JSON from fenced and unfenced responsesANTHROPIC_API_KEYin env raises a clear startup error (Pydantic handles automatically)timeout=60.0andmax_retries=2