Populate ResponseUpdate.FinishReason for the Anthropic provider from stop_reason - #596
Conversation
There was a problem hiding this comment.
Pull request overview
This PR brings the Anthropic provider to parity with other providers by populating ResponseUpdate.FinishReason based on Anthropic’s stop_reason for both non-streaming and streaming (via message_delta) flows.
Changes:
- Add
mapStopReason(anthropic.StopReason) stringhelper to translate Anthropicstop_reasonvalues to canonical finish reasons. - Populate
FinishReasonon non-streaming responses usingresp.StopReason. - Capture streaming
stop_reasonfromMessageDeltaEventand emit it on the terminal update, avoiding overwriting with later empty stop reasons.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| provider/anthropicprovider/agent.go | Adds stop-reason mapping and wires FinishReason into non-streaming and streaming terminal updates. |
| provider/anthropicprovider/agent_test.go | Adds integration-style tests asserting collected FinishReason values for non-streaming and streaming runs. |
| provider/anthropicprovider/stopreason_internal_test.go | Adds table test for mapStopReason covering known, empty, and unknown reasons. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "event: message_delta\n" + | ||
| `data: {"type":"message_delta","delta":{"stop_reason":"max_tokens","stop_sequence":null},"usage":{"output_tokens":5}}` + "\n\n" + | ||
| "event: message_stop\n" + | ||
| `data: {"type":"message_stop"}` + "\n\n" |
There was a problem hiding this comment.
Good catch — fixed in 3f03a5e: added TestStreamingFinishReasonNotClobbered, which streams a max_tokens message_delta followed by a second message_delta with stop_reason:null and asserts FinishReason stays "length", validating the guard against an empty stop_reason overwriting a captured value.
1c873d6 to
559950b
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The Anthropic provider never set FinishReason, so collected responses lacked the reason generation ended even though the API returns stop_reason. Add a mapStopReason helper that maps the SDK stop_reason to the canonical values used by the OpenAI and Copilot providers (stop/length/tool_calls/content_filter), set it on the non-streaming update from resp.StopReason, and capture it from the streaming message_delta event for the terminal update, guarding against a later empty chunk clobbering the value.
3f03a5e to
1bf84f2
Compare
Parity Review ✅Scope: Internal implementation fix — No exported API surface changed. The Cross-repo alignment confirmed:
The mapping table ( Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
What
The Anthropic provider never populated
ResponseUpdate.FinishReason, so collected responses had an emptyFinishReasoneven though the Messages API returnsstop_reasonon both the non-streaming response and the streamingmessage_deltaevent.This adds a package-level
mapStopReasonhelper that maps the SDKstop_reasonto the canonicalFinishReasonvalues shared across providers:end_turn,stop_sequence,pause_turn->stopmax_tokens->lengthtool_use->tool_callsrefusal->content_filterempty / unknown ->
""Non-streaming: sets
FinishReason: mapStopReason(resp.StopReason)on the update.Streaming: captures the stop reason from
MessageDeltaEventand sets it on the terminal update, guarding against a later empty chunk overwriting a value already captured.Why
Brings the Anthropic provider to parity with the other providers, which already surface a finish reason:
openaiprovider/chat.gosetsfinishReason = choice.FinishReasonandcopilotprovider/copilot.gosetsupdate.FinishReason. This mirrors the .NETChatFinishReasonsemantics so downstream consumers can reason about why generation stopped regardless of provider.Tests
TestMapStopReason: table test asserting each SDKstop_reasonconstant maps to the expected canonical string (including empty/unknown ->"").TestNonStreamingFinishReason: fake-transport run returningstop_reason: tool_useasserts the collectedResponse.FinishReason == tool_calls.TestStreamingFinishReason: streaming run emitting amessage_deltawithstop_reason: max_tokensassertsResponse.FinishReason == length.go build ./...,go vet ./provider/anthropicprovider/..., andgo test ./provider/anthropicprovider/...all pass.