feat(agent): move every model call to Sonnet 5 (#1218)

* feat(agent): move every model call to Sonnet 5

Sonnet 5 is verified enabled on our Bedrock account already: a live probe of
eu.anthropic.claude-sonnet-5 in eu-north-1 answered normally, so no model-access
request was needed. The bare anthropic.claude-sonnet-5 is rejected (on-demand
throughput needs the cross-region inference profile), so the eu. prefix we
already use stays.

This is not a model-string swap. Sonnet 5 REJECTS the fixed thinking budget
outright: thinking {type:'enabled', budget_tokens} returns 400 "not supported
for this model. Use thinking.type.adaptive and output_config.effort". Every
chat intent set a budget, so the assistant would have failed on the first turn
after a bare ID change. Reasoning depth is now an effort level (STANDARD high,
DEEP xhigh), and max_tokens is explicit per tier rather than derived from a
budget that no longer exists.

display:'summarized' is load-bearing, not cosmetic. The default is 'omitted',
which still emits thinking blocks but with empty text. Measured on our own
account at xhigh effort: summarized returned ~1k characters of reasoning, the
default returned none. Without it the collapsible "Tänker ..." block in the
chat would have gone silently empty, which no mocked test would have caught.

Ceilings are raised (16k standard, 24k deep) because Sonnet 5's tokenizer
produces roughly 30% more tokens for the same text and max_tokens now caps
thinking and the visible reply together.

Also resolves the Opus 4.7 landmine recorded in the readiness doc: the composer
comment told ops to flip BEDROCK_OPUS_MODEL_ID to Opus 4.7, which would have
400d every thinking intent against the legacy budget shape. Both model
constants now point at Sonnet 5 and the stale instruction is gone.

Checked but deliberately unchanged: forced tool_choice in atom-selection. The
Sonnet 5 docs require thinking:{type:'disabled'} alongside a forced tool_choice
on Bedrock; probed against our account, the forced call succeeds without it, so
no change was made rather than adding a guard we cannot show is needed.

Other call sites moved too: invoice-inbox extraction, document extraction, the
compliance config, and the CI/CD workflows (pr-agent MODEL and MODEL_WEAK,
swedish-compliance-review, compliance-swarm).

Verified: 11315 tests pass, lint and tsc clean on every touched file, guards
pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(agent): review triage: keep the no-thinking output ceiling, finish the model sweep

max_tokens now caps thinking and the visible reply together, so collapsing the
two tiers into one made every non-thinking intent inherit a 16000 ceiling where
it used to have 4096. Give it its own MAX_TOKENS_NO_THINKING instead, set to the
old 4096 scaled ~30% for Sonnet 5's tokenizer so the effective reply length is
unchanged rather than quietly cut.

scripts/swedish-compliance-review.mjs still fell back to Sonnet 4.6 when
REVIEW_MODEL was unset, so a manual run silently used the old model. The initial
sweep only covered .ts and .yml.

pr-agent's FALLBACK_MODELS listed the primary model as its own fallback, which is
not a fallback; dropped it and rewrote the surrounding comments, which still
described Opus 4.8 and a 200k window.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-27 10:16:40 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent f3eacb436d
commit 2d543ac999
20 changed files with 143 additions and 74 deletions
+36 -22
View File
@@ -59,27 +59,41 @@ export function getAnthropic(): AnthropicBedrock {
return cached
}
// Bedrock model IDs. Region prefix `eu.` keeps inference inside eu-north-1.
// Both are env-overridable so ops can swap models without a code deploy.
// Bedrock model IDs. Region prefix `eu.` keeps inference inside eu-north-1
// (a bare `anthropic.claude-sonnet-5` is rejected: on-demand throughput needs
// the cross-region inference profile). Both are env-overridable so ops can
// swap models without a code deploy.
//
// Per plan §14 the composer's atom-selection call should run on Opus 4.7 for
// the higher-stakes selection reasoning. Opus 4.7 is not yet enabled on this
// AWS Bedrock account (403 "not available for this account": request access
// on the AWS console under Bedrock → Model access). For now we point OPUS at
// Sonnet 4.6 so the composer still works; atom selection on Sonnet is still
// good: it's a structured-output call via tool_use forcing, not deep
// reasoning. Flip BEDROCK_OPUS_MODEL_ID back to eu.anthropic.claude-opus-4-7
// once Opus access lands.
export const OPUS_MODEL = process.env.BEDROCK_OPUS_MODEL_ID || 'eu.anthropic.claude-sonnet-4-6'
export const SONNET_MODEL = process.env.BEDROCK_SONNET_MODEL_ID || 'eu.anthropic.claude-sonnet-4-6'
// Both point at Sonnet 5, verified enabled on this Bedrock account. The two
// names are kept because the intents split on them: OPUS_MODEL marks the
// heavy-reasoning intents (supplier-invoice review, VAT review, bokslut) so
// that split survives if a genuinely larger model is enabled here later.
export const OPUS_MODEL = process.env.BEDROCK_OPUS_MODEL_ID || 'eu.anthropic.claude-sonnet-5'
export const SONNET_MODEL = process.env.BEDROCK_SONNET_MODEL_ID || 'eu.anthropic.claude-sonnet-5'
// Extended-thinking budgets (budget_tokens) for the chat intents. These are
// ceilings, not floors: the model spends only what a turn needs, so a generous
// cap improves hard turns (multi-source VAT synthesis, anomaly detection)
// without taxing simple ones. run-turn derives max_tokens = budget + 4096, so
// raising these is safe: no manual max_tokens bookkeeping. Tiered to match the
// model split: DEEP for the Opus / heavy-reasoning intents, STANDARD for the
// rest. Early-stage default favours reasoning quality over token cost; dial
// down here in one place if latency/cost ever bites.
export const THINKING_BUDGET_STANDARD = 6000
export const THINKING_BUDGET_DEEP = 12000
// Reasoning depth for the chat intents.
//
// Sonnet 5 removed the fixed thinking budget: `thinking: {type:'enabled',
// budget_tokens}` is rejected outright ("not supported for this model. Use
// thinking.type.adaptive and output_config.effort"). Depth is now a qualitative
// effort level and the model spends what a turn actually needs.
//
// Levels are `low | medium | high | xhigh | max`. Measured on this account:
// at `medium` a multi-step Swedish VAT question produced no reasoning at all,
// at `xhigh` it produced ~1k characters. Since the point of enabling thinking
// on these intents is that the agent reasons BEFORE it answers rather than
// narrating in the reply, DEEP uses xhigh and STANDARD high.
export const EFFORT_STANDARD = 'high' as const
export const EFFORT_DEEP = 'xhigh' as const
// Output ceilings per tier. Previously derived as budget + 4096; with no budget
// to derive from these are explicit, and there are now three of them because
// max_tokens caps thinking AND the visible reply together: an intent that does
// not think must not inherit a ceiling sized for one that does.
//
// NO_THINKING is the old 4096 reply cap scaled by ~30% for Sonnet 5's new
// tokenizer, which spends that much more on the same Swedish text, so the
// effective reply length is unchanged rather than quietly cut.
export const MAX_TOKENS_NO_THINKING = 5400
export const MAX_TOKENS_STANDARD = 16000
export const MAX_TOKENS_DEEP = 24000