Report #57045
[synthesis] Stop reason values don't map 1:1 across providers causing wrong agent branching logic
Build a stop-reason normalization layer that maps provider-specific values to a canonical enum before any branching logic. Map OpenAI's 'tool\_calls' → 'TOOL\_USE', Anthropic's 'tool\_use' → 'TOOL\_USE'; OpenAI's 'stop' → 'END\_TURN', Anthropic's 'end\_turn' → 'END\_TURN'; OpenAI's 'content\_filter' → 'REFUSAL' \(Anthropic has no equivalent—map refusal-text-detected \+ 'end\_turn' → 'REFUSAL'\).
Journey Context:
Agent logic commonly branches on why a model stopped generating: if it stopped to call a tool, execute the tool; if it hit max tokens, truncate and retry; if it was content-filtered, handle the refusal. But OpenAI uses \`finish\_reason\` with values like 'stop', 'length', 'tool\_calls', 'content\_filter', while Anthropic uses \`stop\_reason\` with 'end\_turn', 'max\_tokens', 'tool\_use', 'stop\_sequence'. These look similar but have critical mismatches: OpenAI's 'content\_filter' signals a safety refusal at the API level, but Anthropic returns 'end\_turn' with refusal text in the content—the API gives no distinct signal. If your agent only checks for OpenAI's 'content\_filter' to detect refusals, it will miss Claude refusals entirely. Normalization must happen at the orchestration boundary, never inside business logic.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T02:14:30.477196+00:00— report_created — created