Skip to content

Surface Anthropic server_tool_use and web_search_tool_result response blocks - #635

Open
PratikDhanave (PratikDhanave) wants to merge 4 commits into
microsoft:mainfrom
PratikDhanaveFork:surface-anthropic-server-tool-blocks
Open

Surface Anthropic server_tool_use and web_search_tool_result response blocks#635
PratikDhanave (PratikDhanave) wants to merge 4 commits into
microsoft:mainfrom
PratikDhanaveFork:surface-anthropic-server-tool-blocks

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

buildBlock in provider/anthropicprovider/agent.go only switched on TextBlock, ThinkingBlock, RedactedThinkingBlock, and ToolUseBlock. Anthropic's server-side tool response blocks (server_tool_use and web_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-side ToolUseBlock case), so callers can observe the server tool request. It is appended in place rather than via the functions map, since that map is nil on the streaming ContentBlockStartEvent path and Anthropic has already executed the call.
  • anthropic.WebSearchToolResultBlock -> a *message.FunctionResultContent keyed by tool_use_id, with each web_search_result mapped to a *message.CitationAnnotation (URL/Title), reusing the same citation shape as the existing TextBlock handling. The error content variant (web_search_tool_result_error) is guarded and surfaced via Error + the error code.

Why

Cross-SDK parity: the Python client (_chat_client.py) already parses server_tool_use and web_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 TestServerToolUseAndWebSearchResultBlocks to the canonical agent_test.go, black-box through RunText().Collect() with a fake transport that returns both blocks. It asserts a FunctionCallContent (correct CallID/Name/arguments) and a FunctionResultContent (correct CallID plus a populated CitationAnnotation) are produced rather than dropped. The test fails before the change and passes after. go build ./..., go vet ./provider/anthropicprovider/..., and go test ./provider/anthropicprovider/... all pass.

Open design questions

  • Scope: this PR handles only the response blocks. Request-side WebSearch tool configuration is a separate change.
  • API shape: server_tool_use is surfaced as a FunctionCallContent and web_search_tool_result as a FunctionResultContent with citation annotations. Open to representing these as a dedicated content type if that better matches .NET/Python, but function call/result reuses existing shapes.
  • Follow-ups: the streaming path accumulates these blocks (they flow through buildBlock on ContentBlockStartEvent); a dedicated streaming assertion could be added if desired.

@PratikDhanave
PratikDhanave (PratikDhanave) force-pushed the surface-anthropic-server-tool-blocks branch from 9424710 to 9ba6d46 Compare July 23, 2026 15:42
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.allowed list 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,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Iterates v.Content.AsWebSearchResultBlockArray()
  2. Maps each result to a *message.CitationAnnotation stored in result.Annotations
  3. 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.

@PratikDhanave
PratikDhanave (PratikDhanave) force-pushed the surface-anthropic-server-tool-blocks branch from 9ba6d46 to 997dbfe Compare July 24, 2026 01:41
@github-actions

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.
@PratikDhanave
PratikDhanave (PratikDhanave) force-pushed the surface-anthropic-server-tool-blocks branch from 997dbfe to e63be0d Compare July 24, 2026 09:36
@github-actions github-actions Bot added the public-api-change Pull Request changes public APIs label Jul 24, 2026
# Conflicts:
#	provider/anthropicprovider/agent_test.go
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by Go API Consistency Review Agent · sonnet46 · 31.5 AIC · ⌖ 5.86 AIC · ⊞ 5.7K

RawRepresentation: v,
},
})
case anthropic.WebSearchToolResultBlock:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
    },
})

@PratikDhanave
PratikDhanave (PratikDhanave) marked this pull request as ready for review August 4, 2026 06:06
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner August 4, 2026 06:06
Copilot AI lite review requested due to automatic review settings August 4, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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_use blocks as *message.FunctionCallContent so callers can observe server-side tool requests.
  • Surface web_search_tool_result blocks as *message.FunctionResultContent with CitationAnnotation entries 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.

Comment on lines +303 to +310
contents = append(contents, &message.FunctionCallContent{
CallID: v.ID,
Name: string(v.Name),
Arguments: args,
ContentHeader: message.ContentHeader{
RawRepresentation: v,
},
})
@github-actions github-actions Bot added area:provider Changes files in the provider area area:provider/anthropic Changes files in the provider / anthropic area size:large At most 300 changed lines across at most 10 files pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by Go API Consistency Review Agent · sonnet46 · 40.1 AIC · ⌖ 4.34 AIC · ⊞ 6K

args = string(b)
}
}
contents = append(contents, &message.FunctionCallContent{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions github-actions Bot added failed-auto-risk Automatic risk classification was inconclusive or failed and removed pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions github-actions Bot added pending-auto-risk Automatic risk classification is in progress risk:medium Contained production impact requiring normal review depth and removed failed-auto-risk Automatic risk classification was inconclusive or failed pending-auto-risk Automatic risk classification is in progress labels Aug 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — PR #635: Surface Anthropic server_tool_use and web_search_tool_result response blocks

Scope

This PR modifies provider/anthropicprovider/agent.go (unexported helper buildBlock) and adds tests. The change adds two new response-block parse paths and emits new FunctionCallContent and FunctionResultContent values. These are user-visible through the framework's message content API, so the change is in parity review scope.

Overall Assessment

⚠️ One parity issue found. The web_search_tool_result mapping is aligned with the Python SDK. The server_tool_use mapping is structurally correct but is missing the InformationalOnly: true flag that the Python SDK sets, which causes a behavioral divergence in frameworks that use the tool-autocall harness.


1. WebSearchToolResultBlockFunctionResultContent

Aligned with the Python SDK (_chat_client.py lines 1297–1305), which calls Content.from_function_result(call_id=content_block.tool_use_id, result=content_block.content, ...). The Go implementation maps v.ToolUseID as CallID, converts each WebSearchResultBlock to a CitationAnnotation, and surfaces errors via result.Error. This is semantically equivalent and idiomatic to Go. No parity gap.

2. ServerToolUseBlockFunctionCallContent ⚠️

The Python SDK (_chat_client.py line 1271) explicitly sets:

informational_only=content_block.type == "server_tool_use",

This causes server_tool_use blocks to be recorded as transcript-only entries. The Go framework has the identical concept: FunctionCallContent.InformationalOnly bool (message/content.go:346), and toolautocall/autocall.go skips invocation for any call with InformationalOnly == true (lines 240, 403, 654).

This PR emits the ServerToolUseBlock as a FunctionCallContent without setting InformationalOnly: true. If a caller has a tool-autocall harness configured, it will attempt to look up and call a local tool named (e.g.) web_search for a tool Anthropic already executed server-side. This will either fail (no tool registered) or silently double-invoke the wrong handler.

See the inline comment on agent.go:316 for the suggested one-line fix.


Upstream References

Behavior Python (_chat_client.py) Go (this PR)
server_tool_useFunctionCallContent ✅ line 1265–1274, informational_only=True ⚠️ line 299–321, InformationalOnly not set
web_search_tool_resultFunctionResultContent ✅ 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 ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
    },
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider/anthropic Changes files in the provider / anthropic area area:provider Changes files in the provider area public-api-change Pull Request changes public APIs risk:medium Contained production impact requiring normal review depth size:large At most 300 changed lines across at most 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants