Surface Anthropic server_tool_use and web_search_tool_result response blocks - #635
Conversation
9424710 to
9ba6d46
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
awmgmcpg
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
Generated by Go API Consistency Review Agent · 80.7 AIC · ⌖ 5.28 AIC · ⊞ 5.7K
| Arguments: args, | ||
| ContentHeader: message.ContentHeader{ | ||
| RawRepresentation: v, | ||
| }, |
There was a problem hiding this comment.
Parity issue: InformationalOnly not set for server_tool_use blocks
The Python implementation (_chat_client.py) sets informational_only=True for server_tool_use content blocks, marking the resulting FunctionCallContent as informational so the tool-autocall harness skips local dispatch (Anthropic already executed the tool server-side).
This PR omits InformationalOnly: true from the FunctionCallContent it creates for ServerToolUseBlock. As a result, toolautocall/autocall.go (which guards on !fcc.InformationalOnly at lines 237, 362, 596, 908, 927, etc.) will treat the server-side invocation as a locally-dispatchable call, search for a registered tool by name, and produce a runtime error when none is found.
Suggested fix:
contents = append(contents, &message.FunctionCallContent{
CallID: v.ID,
Name: string(v.Name),
Arguments: args,
InformationalOnly: true, // Anthropic executed this server-side; skip local dispatch
ContentHeader: message.ContentHeader{
RawRepresentation: v,
},
})Upstream reference: python/packages/anthropic/agent_framework_anthropic/_chat_client.py, case "server_tool_use" branch.
| contents = append(contents, result) | ||
| } | ||
| return contents | ||
| } |
There was a problem hiding this comment.
Parity note: web_search_tool_result shape diverges from Python
The Python implementation maps web_search_tool_result blocks to Content.from_function_result(call_id=..., result=content_block.content, ...) — it passes the raw content array through without unpacking individual web search result items into citation annotations.
This PR instead:
- Iterates
v.Content.AsWebSearchResultBlockArray() - Maps each result to a
*message.CitationAnnotationstored inresult.Annotations - Sets
result.Result = results(the raw array)
This is a richer representation, but it diverges from the upstream Go convention where Annotations live on text content (via ContentHeader) rather than on a FunctionResultContent. The FunctionResultContent struct does not have an Annotations field — the PR is attaching them via the embedded ContentHeader.Annotations, which is typically used for text spans and citations on text blocks.
If cross-SDK consistency matters here, consider aligning with Python by passing result.Result = content_block.content (the raw slice) and omitting the citation-annotation expansion, or opening a follow-up issue to also adopt the richer citation-on-function-result shape in Python.
Upstream reference: python/packages/anthropic/agent_framework_anthropic/_chat_client.py, case "web_search_tool_result" | "web_fetch_tool_result" branch.
9ba6d46 to
997dbfe
Compare
This comment has been minimized.
This comment has been minimized.
buildBlock only switched on TextBlock/ThinkingBlock/RedactedThinkingBlock/ ToolUseBlock, so server-side web search response blocks were silently dropped. Add cases for ServerToolUseBlock (as a FunctionCallContent) and WebSearchToolResultBlock (as a FunctionResultContent whose citations point at each source), mirroring the Python SDK which parses server_tool_use and web_search_tool_result.
997dbfe to
e63be0d
Compare
# Conflicts: # provider/anthropicprovider/agent_test.go
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by Go API Consistency Review Agent · sonnet46 · 31.5 AIC · ⌖ 5.86 AIC · ⊞ 5.7K
| RawRepresentation: v, | ||
| }, | ||
| }) | ||
| case anthropic.WebSearchToolResultBlock: |
There was a problem hiding this comment.
Parity gap — InformationalOnly not set for server_tool_use
The upstream Python SDK sets informational_only=True explicitly when the content block type is "server_tool_use":
# python/packages/anthropic/agent_framework_anthropic/_chat_client.py, ~line 1256
Content.from_function_call(
call_id=content_block.id,
name=resolved_tool_name,
arguments=content_block.input,
informational_only=content_block.type == "server_tool_use", # True for server tools
raw_representation=content_block,
)The Go FunctionCallContent type already has an InformationalOnly bool field, but it is not set in this new case. Downstream consumers that check InformationalOnly to decide whether to re-invoke a tool will treat an Anthropic server-tool call the same as a regular client-side tool call, diverging from Python behaviour.
Suggested fix:
contents = append(contents, &message.FunctionCallContent{
CallID: v.ID,
Name: string(v.Name),
Arguments: args,
InformationalOnly: true, // server tool was already executed by Anthropic
ContentHeader: message.ContentHeader{
RawRepresentation: v,
},
})There was a problem hiding this comment.
🟡 Not ready to approve
The new server_tool_use mapping can be treated as an actionable function call by the tool-autocall harness unless it is explicitly marked informational-only, risking accidental local invocation when a matching result is absent or interrupted.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR updates the Anthropic provider’s response-block mapping so Anthropic-hosted tool interactions (notably server-side web_search) are no longer silently dropped from the returned message.Content stream.
Changes:
- Surface
server_tool_useblocks as*message.FunctionCallContentso callers can observe server-side tool requests. - Surface
web_search_tool_resultblocks as*message.FunctionResultContentwithCitationAnnotationentries for each search result. - Add a black-box test that verifies both blocks are emitted via
RunText().Collect()rather than being dropped.
File summaries
| File | Description |
|---|---|
| provider/anthropicprovider/agent.go | Extends buildBlock to translate Anthropic server-side tool-use and web-search result blocks into framework FunctionCallContent/FunctionResultContent with citations. |
| provider/anthropicprovider/agent_test.go | Adds a regression test ensuring server_tool_use and web_search_tool_result blocks are preserved and mapped into contents. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| contents = append(contents, &message.FunctionCallContent{ | ||
| CallID: v.ID, | ||
| Name: string(v.Name), | ||
| Arguments: args, | ||
| ContentHeader: message.ContentHeader{ | ||
| RawRepresentation: v, | ||
| }, | ||
| }) |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by Go API Consistency Review Agent · sonnet46 · 40.1 AIC · ⌖ 4.34 AIC · ⊞ 6K
| args = string(b) | ||
| } | ||
| } | ||
| contents = append(contents, &message.FunctionCallContent{ |
There was a problem hiding this comment.
Parity issue: InformationalOnly not set for server_tool_use blocks
The Python _chat_client.py explicitly marks server-side tool calls as informational-only (_chat_client.py line 1271):
Content.from_function_call(
call_id=content_block.id,
name=resolved_tool_name,
arguments=content_block.input,
informational_only=content_block.type == "server_tool_use", # always True for this case
...
)The Go FunctionCallContent emitted here is missing InformationalOnly: true. Without it, the Go tool auto-call harness (autocall.go) will treat this as a locally-executable tool and try to dispatch web_search or other server-side tools as client-side calls — which will fail, since Anthropic already executed them.
Suggested fix:
contents = append(contents, &message.FunctionCallContent{
CallID: v.ID,
Name: string(v.Name),
Arguments: args,
InformationalOnly: true, // server already executed this tool
ContentHeader: message.ContentHeader{
RawRepresentation: v,
},
})Upstream reference: python/packages/anthropic/agent_framework_anthropic/_chat_client.py line 1271.
Parity Review — PR #635: Surface Anthropic
|
| Behavior | Python (_chat_client.py) |
Go (this PR) |
|---|---|---|
server_tool_use → FunctionCallContent |
✅ line 1265–1274, informational_only=True |
InformationalOnly not set |
web_search_tool_result → FunctionResultContent |
✅ lines 1297–1305 | ✅ lines 322–349 |
.NET (dotnet/src/Microsoft.Agents.AI.Anthropic/) has no evidence of server_tool_use or web_search_tool_result parsing in the current codebase, so .NET parity cannot be assessed.
Generated by Go API Consistency Review Agent · sonnet46 · 34.5 AIC · ⌖ 5.39 AIC · ⊞ 6K · ◷
There was a problem hiding this comment.
Generated by Go API Consistency Review Agent · sonnet46 · 34.5 AIC · ⌖ 5.39 AIC · ⊞ 6K
| CallID: v.ID, | ||
| Name: string(v.Name), | ||
| Arguments: args, | ||
| ContentHeader: message.ContentHeader{ |
There was a problem hiding this comment.
Parity gap: InformationalOnly not set on ServerToolUseBlock
The upstream Python implementation sets informational_only=True for every server_tool_use block (_chat_client.py line 1271):
informational_only=content_block.type == "server_tool_use",This flag tells the tool-autocall harness not to attempt re-execution — the server already ran the tool. Go's FunctionCallContent has the same field (InformationalOnly bool, message/content.go:346), and toolautocall/autocall.go already gates invocation on !fcc.InformationalOnly (lines 240, 403, 654, 1000).
As written, the Go ServerToolUseBlock case emits a FunctionCallContent with InformationalOnly left as false. If toolautocall is active, it will attempt to invoke a locally-registered tool whose name matches the server-tool name (e.g. web_search), which either fails or silently calls the wrong handler.
Suggested fix:
contents = append(contents, &message.FunctionCallContent{
CallID: v.ID,
Name: string(v.Name),
Arguments: args,
InformationalOnly: true, // server already executed this; do not re-invoke locally
ContentHeader: message.ContentHeader{
RawRepresentation: v,
},
})
What
buildBlockinprovider/anthropicprovider/agent.goonly switched onTextBlock,ThinkingBlock,RedactedThinkingBlock, andToolUseBlock. Anthropic's server-side tool response blocks (server_tool_useandweb_search_tool_result, emitted when the model runs a hosted web search) hit no case and were silently dropped from the response.This adds two cases to
buildBlock:anthropic.ServerToolUseBlock-> a*message.FunctionCallContent(CallID/Name/Input mirroring the client-sideToolUseBlockcase), so callers can observe the server tool request. It is appended in place rather than via thefunctionsmap, since that map is nil on the streamingContentBlockStartEventpath and Anthropic has already executed the call.anthropic.WebSearchToolResultBlock-> a*message.FunctionResultContentkeyed bytool_use_id, with eachweb_search_resultmapped to a*message.CitationAnnotation(URL/Title), reusing the same citation shape as the existingTextBlockhandling. The error content variant (web_search_tool_result_error) is guarded and surfaced viaError+ the error code.Why
Cross-SDK parity: the Python client (
_chat_client.py) already parsesserver_tool_useandweb_search_tool_result. Without these cases the Go provider drops the entire server-side web-search interaction, so downstream consumers can neither see which query the model ran nor cite the sources it retrieved. This complements the request-side WebSearch mapping and is scoped to only the response-block region (buildBlock, not the tool loop) so it is reviewable independently.Testing
Added
TestServerToolUseAndWebSearchResultBlocksto the canonicalagent_test.go, black-box throughRunText().Collect()with a fake transport that returns both blocks. It asserts aFunctionCallContent(correct CallID/Name/arguments) and aFunctionResultContent(correct CallID plus a populatedCitationAnnotation) are produced rather than dropped. The test fails before the change and passes after.go build ./...,go vet ./provider/anthropicprovider/..., andgo test ./provider/anthropicprovider/...all pass.Open design questions
server_tool_useis surfaced as aFunctionCallContentandweb_search_tool_resultas aFunctionResultContentwith citation annotations. Open to representing these as a dedicated content type if that better matches .NET/Python, but function call/result reuses existing shapes.buildBlockonContentBlockStartEvent); a dedicated streaming assertion could be added if desired.