-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Anthropic Structured Tool Call Ports #9880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Anthropic Structured Tool Call Ports #9880
Conversation
… with structured output
| } | ||
|
|
||
| // when a json response tool is used, we don't emit text events | ||
| if (!usesJsonResponseTool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wont this removal introduce bugs when text messages are generated before the tool call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some tests to cover potential edge cases like this in 0f28874, but either I'm doing this wrong and it's not catching them, or it's being accounted for by below:
Here we're adding all text parts to the content, regardless of usesJsonResponseTool:
ai/packages/amazon-bedrock/src/bedrock-chat-language-model.ts
Lines 323 to 326 in 0f28874
| for (const part of response.output.message.content) { | |
| // text | |
| if (part.text) { | |
| content.push({ type: 'text', text: part.text }); |
Then, while processing tool calls:
- If it's the JSON response tool, it sets
isJsonResponseFromTool = trueand adds the tool input as text content - Otherwise, it adds a regular
tool-call
ai/packages/amazon-bedrock/src/bedrock-chat-language-model.ts
Lines 362 to 369 in 0f28874
| const isJsonTool = usesJsonResponseTool && part.toolUse.name === 'json'; | |
| if (isJsonTool) { | |
| isJsonResponseFromTool = true; | |
| content.push({ | |
| type: 'text', | |
| text: JSON.stringify(part.toolUse.input), | |
| }); |
This block only skips tool-specific events (not text events) when it's a JSON response tool:
ai/packages/amazon-bedrock/src/bedrock-chat-language-model.ts
Lines 712 to 719 in 0f28874
| // when a json response tool is used, we don't emit tool events | |
| if (!isJsonTool) { | |
| controller.enqueue({ | |
| type: 'tool-input-start', | |
| id: toolUse.toolUseId!, | |
| toolName: toolUse.name!, | |
| }); | |
| } |
- The
usesJsonResponseToolflag only affects:- Converting the JSON tool's output to text format
- Changing the finish reason from
tool-callstostop - Adding metadata flags
Are there other potential edge cases I might be missing to cover, or would you prefer I rewrote this in a more readable fashion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I've tested this live now finally and I'm having trouble getting a text part. I'll come back to this in a few
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added more test coverage just in case because I was a bit worried about this. I cannot for the life of me find a case where that happens with the code currently.
This test, in theory, should cover that case:
ai/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts
Lines 1630 to 1703 in c439b76
| it('should stream text introduction before JSON-only response', async () => { | |
| setupMockEventStreamHandler(); | |
| prepareChunksFixtureResponse('bedrock-json-only-text-first.1'); | |
| const { stream } = await model.doStream({ | |
| prompt: [ | |
| { role: 'user', content: [{ type: 'text', text: 'Return name data' }] }, | |
| ], | |
| responseFormat: { | |
| type: 'json', | |
| schema: { | |
| type: 'object', | |
| properties: { name: { type: 'string' } }, | |
| required: ['name'], | |
| additionalProperties: false, | |
| $schema: 'http://json-schema.org/draft-07/schema#', | |
| }, | |
| }, | |
| includeRawChunks: false, | |
| }); | |
| const result = await convertReadableStreamToArray(stream); | |
| // Should emit text events for "Here is your data:" followed by JSON output as text | |
| expect(result).toMatchInlineSnapshot(` | |
| [ | |
| { | |
| "type": "stream-start", | |
| "warnings": [], | |
| }, | |
| { | |
| "id": "0", | |
| "type": "text-start", | |
| }, | |
| { | |
| "delta": "Here is your data:", | |
| "id": "0", | |
| "type": "text-delta", | |
| }, | |
| { | |
| "id": "0", | |
| "type": "text-end", | |
| }, | |
| { | |
| "id": "1", | |
| "type": "text-start", | |
| }, | |
| { | |
| "delta": "{"name":"John"}", | |
| "id": "1", | |
| "type": "text-delta", | |
| }, | |
| { | |
| "id": "1", | |
| "type": "text-end", | |
| }, | |
| { | |
| "finishReason": "stop", | |
| "providerMetadata": { | |
| "bedrock": { | |
| "isJsonResponseFromTool": true, | |
| }, | |
| }, | |
| "type": "finish", | |
| "usage": { | |
| "cachedInputTokens": undefined, | |
| "inputTokens": 100, | |
| "outputTokens": 20, | |
| "totalTokens": 120, | |
| }, | |
| }, | |
| ] | |
| `); | |
| }); |
If you have any suggestions on how else to account for that, happy to make adjustments.
Also if you have any guidance on how to back port this to v5, I would love to do that as well.
|
ideally introduce tests with fixtures and snapshots like we have in the anthropic provider |
…al edge cases chore(providers/amazon-bedrock): prettier run
1dde227 moved existing tests to fixtures |
Background
The Anthropic provider recently added support for combining tool calling with structured outputs in commit cf4e2a9 (#9793). This feature allows developers to use tools (like web search, weather lookup, etc.) alongside structured JSON output schemas, enabling multi-step workflows with structured final outputs.
However, this capability was ported to the Amazon Bedrock and Google Vertex Anthropic providers
Summary
This PR ports the structured tool call support from the Anthropic provider to both Amazon Bedrock and Google Vertex Anthropic providers.
Amazon Bedrock Provider Changes
Core Implementation:
{ type: 'required' }instead of{ type: 'tool', toolName: 'json' }when combining tools with structured outputs (bedrock-chat-language-model.ts:133-134)isJsonResponseFromToolparameter tomapBedrockFinishReasonfunction to correctly map finish reason fromtool_usetostopwhen the JSON tool is used (map-bedrock-finish-reason.ts)doGenerateanddoStreammethods to passusesJsonResponseFromToolflag to the finish reason mapperTesting:
json schema response format with json tool response- Tests when only the JSON tool is calledjson schema response format with other tool response- Tests when regular tools are called alongside JSON schemaExamples:
examples/ai-core/src/generate-text/bedrock-output-array-tools.tsexamples/ai-core/src/stream-text/bedrock-output-array-tools.tsGoogle Vertex Anthropic Provider Changes
Since this provider inherits from the Anthropic provider through
AnthropicMessagesLanguageModel, it automatically gains the feature. Added:Testing:
Examples:
examples/ai-core/src/generate-text/google-vertex-anthropic-output-array-tools.tsexamples/ai-core/src/stream-text/google-vertex-anthropic-output-array-tools.tsManual Verification
Run the following examples to verify the feature works end-to-end:
Amazon Bedrock:
Google Vertex Anthropic:
Expected behavior:
stopwhen JSON tool is used, andtool-callswhen regular tools are usedChecklist
pnpm changesetin the project root)Future Work
I need to actually test this, hence why it's a draft for now
Related Issues