feat(anthropic): handle pause_turn and refusal stop reasons#1003
Open
adamthehutt wants to merge 1 commit intoprism-php:mainfrom
Open
feat(anthropic): handle pause_turn and refusal stop reasons#1003adamthehutt wants to merge 1 commit intoprism-php:mainfrom
adamthehutt wants to merge 1 commit intoprism-php:mainfrom
Conversation
Anthropic returns stop_reason="pause_turn" when long-running server-side tools (web_search, web_fetch, code_execution, MCP connectors, etc.) need the client to continue the turn, and stop_reason="refusal" when the model declines to respond. Both currently fall through the Text handler's match and surface as a generic "Anthropic: unknown finish reason" exception with no information about what actually happened. This change: - Adds FinishReason::Pause and FinishReason::Refusal cases. - Maps "pause_turn" and "refusal" in Anthropic's FinishReasonMap. - Implements pause_turn resume in the Text handler per Anthropic's documented protocol: append the assistant message to the conversation and re-send the request so the model can continue. Bounded by the existing maxSteps() guard. - Throws a descriptive exception for refusal that names the stop reason. - Surfaces the raw stop_reason string in the fallback "unhandled finish reason" exception so debugging future stop_reason additions doesn't require patching the library. - Adds fixture-backed tests for the pause_turn resume flow (including the maxSteps boundary) and for the refusal exception path. Stream handler is left unchanged in this PR; pause_turn resume in streaming mode is more involved and can be addressed separately.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Anthropic returns
stop_reason="pause_turn"when a long-running server-side tool (web_search,web_fetch,code_execution, MCP connectors, etc.) needs the client to continue the turn, andstop_reason="refusal"when the model declines to respond. Today both fall through theTexthandler'smatchand surface as a generic:…with no information about which stop reason actually occurred. For requests using
web_search/web_fetchprovider tools this can fail an entire job after several minutes of work, with no way to recover or even diagnose what happened.Changes
FinishReason::PauseandFinishReason::Refusalcases.pause_turnandrefusalinAnthropic\Maps\FinishReasonMap.pause_turnresume inAnthropic\Handlers\Textper Anthropic's documented protocol: append the assistant message to the conversation and re-send the request so the model can continue. Bounded by the existingmaxSteps()guard, identical in shape to howhandleToolCalls()already loops.PrismExceptionforrefusalthat names the stop reason.stop_reasonstring in the fallback "unhandled finish reason" exception, so any future stop reason that Anthropic introduces is debuggable without patching the library.pause_turnresume flow producing a final response with two steps.pause_turnresume flow respectingmaxSteps()and stopping early.refusalexception path.Out of scope
The
Streamhandler is not modified. Streamingpause_turnresume is more involved (the partial assistant message needs to be reassembled from deltas before being re-sent) and is best handled in a follow-up PR. Today, streamingpause_turnwill still fall through to the existingFinishReason::Stopdefault — which is no worse than current behaviour.Test plan
vendor/bin/pest tests/Providers/Anthropic/— 155 tests pass (3 new, 152 existing).pause_turn → it resumes the turn when Anthropic returns stop_reason="pause_turn"pause_turn → it stops resuming once maxSteps is reachedexceptions → it throws a descriptive exception when Anthropic returns a refusal stop_reasonReferences
pause_turn: https://docs.anthropic.com/en/api/handling-stop-reasons#pause-turnrefusal: https://docs.anthropic.com/en/api/handling-stop-reasons#refusal