Skip to content

[bot] Anthropic: client-side tool_use blocks in the standard Messages API get no dedicated TOOL span #575

Description

@braintrust-bot

<!-- provider-gap-audit: anthropic-messages-tool-use-no-span -->

Summary

The Anthropic integration (py/src/braintrust/integrations/anthropic/) creates dedicated child SpanTypeAttribute.TOOL spans for server-side tools (_log_server_tool_spans, e.g. web_search_tool_result, code execution) and for the beta Managed Agents surface (agent.tool_use/agent.mcp_tool_use), but a standard client.messages.create() call that uses client-side tool calling (tool_use/tool_result content blocks — Anthropic's primary, most widely used tool-calling mechanism) produces no dedicated tool span at all. The tool_use block is only ever visible embedded inside the parent LLM span's output.content/input array.

This is Anthropic's flagship, most heavily documented execution surface (https://docs.anthropic.com/en/docs/build-with-claude/tool-use) and is instrumented with materially less structural detail here than the equivalent surface in every other multi-tool-call provider integration already present in this same repo.

Verification

  • _is_server_tool_result_type (py/src/braintrust/integrations/anthropic/tracing.py:1304-1305) explicitly excludes the plain "tool_result" type: item_type.endswith("_tool_result") and item_type != "tool_result". Only types like web_search_tool_result, code_execution_tool_result, etc. reach _log_server_tool_spans (tracing.py:1424 onward).
  • Regular tool_use blocks are converted only via the generic content-block pass-through used for _log_message_to_span (tracing.py:1474-1508) — they appear as plain JSON inside the LLM span's output, with no tool_use_id/name/input broken out into a queryable child span, no separate latency, and no per-call error surfacing.
  • Confirmed no other code path in this file creates a SpanTypeAttribute.TOOL span keyed off a bare tool_use/tool_result pair (only _MANAGED_AGENTS_CALL_TYPES at tracing.py:868 and the server-tool path at tracing.py:1301-1471 produce TOOL spans).

Comparison with other tool-calling surfaces in this repo

Integration/surface Client-executed tool calls get dedicated TOOL spans?
OpenAI Responses API Yes — per-item spans for function_call, web_search_call, code_interpreter_call, etc. (integrations/openai/tracing.py:842-974)
Cohere (chat) Yes — dedicated child TOOL spans (integrations/cohere/tracing.py:634-648)
Mistral (chat/conversations) Yes — tool spans for both completion-style and conversation-style tool calls (integrations/mistral/tracing.py:990-1067)
Google GenAI (Interactions API) Yes — live tool-call spans across turns (integrations/google_genai/tracing.py:798-993)
Claude Agent SDK Yes — TOOL spans keyed off tool_use/tool_result (integrations/claude_agent_sdk/tracing.py:275-996)
Anthropic server-side tools Yes_log_server_tool_spans (tracing.py:1402-1471)
Anthropic Managed Agents (beta) Yes_log_managed_agents_tool_spans (tracing.py:1126-1169)
Anthropic standard Messages API client tool_use No — embedded in output only
OpenAI Chat Completions (base) No (same asymmetry, out of scope for this issue)

Anthropic's own integration already contains three separate TOOL-span code paths (server tools, managed agents, and — per the Claude Agent SDK integration in the same repo — regular tool_use/tool_result), making the omission for the single most common case (plain client-side tool calling in messages.create()) an inconsistency rather than a deliberate scope boundary.

What should be instrumented

For a messages.create()/messages.stream() response whose content contains tool_use blocks, log a child SpanTypeAttribute.TOOL span per tool call (mirroring _log_server_tool_span's shape) capturing:

Span field Content
name tool name from the tool_use block
input tool_use.input
metadata tool_use_id
output the matching tool_result block content from the next request's input, when available in the same call context

Braintrust docs status

unclear. The Anthropic integration docs (https://www.braintrust.dev/docs/providers/anthropic) describe the Messages API tracing as capturing "input messages, system prompt, model, request parameters, response content, stop reason, and stop sequence" — tool_use is only implied as part of generic "response content," with no explicit statement about dedicated tool-call spans. The same docs do describe "beta tool-runner spans" (a different, opt-in feature) capturing "task input, tools, response messages, and aggregated metrics across iterations," confirming Braintrust considers structured tool-call spans a meaningful capability elsewhere in the same integration.

Upstream sources

Local repo files inspected

  • py/src/braintrust/integrations/anthropic/tracing.py:1301-1471 — server-tool span logic (_is_server_tool_result_type, _log_server_tool_spans), confirmed to exclude plain tool_result
  • py/src/braintrust/integrations/anthropic/tracing.py:868-1176 — Managed Agents tool span logic (separate code path, beta-only)
  • py/src/braintrust/integrations/anthropic/tracing.py:1474-1508_message_output/_log_message_to_span, the only path that touches regular tool_use content, with no span creation
  • py/src/braintrust/integrations/openai/tracing.py:842-974, integrations/cohere/tracing.py:634-648, integrations/mistral/tracing.py:990-1067, integrations/google_genai/tracing.py:798-993, integrations/claude_agent_sdk/tracing.py:275-996 — comparable dedicated tool-span handling in other integrations

Metadata

Metadata

Labels

No labels
No labels

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions