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

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions