Skip to content

[opentelemetry-instrumentation-genai-anthropic] Record gen_ai.tool.definitions - #652

Merged
lmolkova merged 2 commits into
open-telemetry:mainfrom
sfc-gh-zeningchen:fix/ob-66110-anthropic-tool-definitions
Sep 14, 2026
Merged

lmolkova merged 2 commits into
open-telemetry:mainfrom
sfc-gh-zeningchen:fix/ob-66110-anthropic-tool-definitions

Conversation

@sfc-gh-zeningchen

@sfc-gh-zeningchen sfc-gh-zeningchen commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Description

The Anthropic instrumentation never recorded gen_ai.tool.definitions, so a
tool-calling request produced a span with no trace of the tools it offered the
model. extract_params reads the request arguments one by one and had no slot
for tools, so the list never reached the code that records telemetry.

A custom tool carries input_schema and maps to a FunctionToolDefinition.
Server tools (web_search_20250305, bash_20250124) and toolsets
(computer_toolset_20260801) map to a GenericToolDefinition keyed by their
versioned type; a toolset carries no name, so its type stands in for the
name the schema requires.

The instrumentation records the attribute only when
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is set, matching
genai-openai. spans.yaml marks the attribute opt_in and places it in the
attributes.gen_ai.content group, so following the content setting is the
narrower reading.

Known gaps: genai-langchain, genai-agno, genai-portkey and
genai-smolagents set tool_definitions regardless of that setting, so one
call yields a different attribute depending on the provider. Separately,
get_content_attributes in util-genai serializes description and
parameters on its capture-disabled path, against the registry note that
non-required properties stay out by default. Both belong in util-genai and are
tracked in #655; once that lands, this gate can move or drop. Tools reached
through mcp_servers stay uncovered, since they never appear in the tools
array.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How has this been tested?

Unit tests cover get_tool_definitions across the custom, server-tool and
unnamed-toolset shapes, tools that expose attributes instead of dict keys, and
the empty and None cases. Sync and async tests assert the serialized
attribute on create and stream spans with content capture on, and its
absence with capture off alongside the input and output messages. Both pairs
replay existing cassettes, since the attribute comes from the request and the
recorded response does not affect it.

  • anthropic oldest leg (anthropic==0.51.0): 184 passed, 7 skipped
  • anthropic latest leg (anthropic==1.4.0): 191 passed
  • anthropic conformance: 5 scenarios pass weaver live-check
  • tox -e precommit (ruff, ruff-format, rstcheck) and pyright clean

Checklist

  • Followed the style guidelines of this project
  • Changelog updated if the change requires an entry
  • Unit tests added
  • Documentation updated

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.

🟡 Changes recommended

The current implementation can exhaust a one-shot tools iterator before the underlying create() call reads it, potentially changing request behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the opentelemetry-instrumentation-genai-anthropic package to record gen_ai.tool.definitions on Anthropic spans by extracting the tools request parameter and mapping it into util-genai ToolDefinition models, with coverage across sync/async and streaming/non-streaming code paths.

Changes:

  • Extend request parameter extraction to retain tools and add a get_tool_definitions() mapper for Anthropic tool shapes.
  • Populate InferenceInvocation.tool_definitions (gated by content-capture mode) so spans can include gen_ai.tool.definitions.
  • Add unit/integration tests (sync + async) asserting presence/absence of the attribute based on content capture, plus mapping tests for tool variants.
File summaries
File Description
instrumentation/opentelemetry-instrumentation-genai-anthropic/src/opentelemetry/instrumentation/genai/anthropic/messages_extractors.py Adds tools to extracted params and introduces get_tool_definitions() mapping logic.
instrumentation/opentelemetry-instrumentation-genai-anthropic/src/opentelemetry/instrumentation/genai/anthropic/patch.py Threads mapped tool definitions into the invocation when content capture is enabled.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_messages_extractors.py Adds focused unit tests for extract_params(...tools=...) and get_tool_definitions() mappings.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_sync_messages.py Adds sync streaming + create assertions for gen_ai.tool.definitions and its omission when capture is off.
instrumentation/opentelemetry-instrumentation-genai-anthropic/tests/test_async_messages.py Adds async streaming + create assertions for gen_ai.tool.definitions and its omission when capture is off.
instrumentation/opentelemetry-instrumentation-genai-anthropic/.changelog/652.fixed Adds a changelog fragment for recording gen_ai.tool.definitions.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Pull request dashboard status

Merged · refreshed 2026-09-14 01:46 UTC

Status above doesn't look right?
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

invocation.tool_definitions = (
get_tool_definitions(params.tools) if capture_content else None
)
invocation.attributes = attributes

@lmolkova lmolkova Sep 8, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The gen_ai.tool.definitions attribute is recommended (not opt-in). Per the semconv schema, only the large optional properties (description and parameters) should be excluded by default, while name and type are required.

Please populate invocation.tool_definitions unconditionally with the required properties (name, type), while keeping the content-capture guard around description and parameters in the instrumentation for performance reasons.

@sfc-gh-zeningchen sfc-gh-zeningchen Sep 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It seems like gen_ai.tool.definitions is still opt-in at this moment (semconv), I'm wondering if we're moving this to recommended in the near future.
I think one inconsistency here is that gen_ai.tool.definitions is opt-in but description and parameters have a nested level of opt-in based on that. But ContentCapturingMode isn't granular enough to distinguish this opt-in and opt-in's opt-in.
Maybe in this PR, I can proceed with only emitting name and type when content capture is off, then open an issue to discuss more granularity for ContentCapturingMode? Or we can put this PR on hold until we have a solution

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry for the confusion, you're absolutely right. I created an issue to follow up in the conventions - open-telemetry/semantic-conventions-genai#512

I think your PR is correct as is.

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.

🟢 Approval recommended

The change is narrowly scoped, preserves SDK behavior (including one-shot iterables), and is backed by targeted sync/async tests covering the new telemetry attribute.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@lmolkova lmolkova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, please resolve conflicts and we should be good to go.

Thanks!

…definitions

A tool-calling request produced a span with no trace of the tools it offered
the model. extract_params reads the request arguments one by one and had no
slot for tools, so the list never reached the code that records telemetry.

Add the slot and map Anthropic's tool shapes onto the semconv models. A custom
tool carries input_schema and becomes a FunctionToolDefinition. Server tools
and toolsets become a GenericToolDefinition keyed by their versioned type; a
toolset carries no name, so its type stands in for the name the schema
requires.

Recording follows OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, matching
genai-openai. The attribute is opt_in in spans.yaml and sits in the
attributes.gen_ai.content group.

Assisted-by: Claude Opus 5
…ools iterator

tools is typed as an iterable, so a caller may pass a generator. Reading tool
definitions drained it before the SDK serialized the request, and the request
went out with an empty tool list.

Materialize it in the wrappers, ahead of both readers. The SDK materializes the
value itself when it builds the request body, so the request is unchanged.

Placement matters and differs between the two entry points: create reads the
request after the wrapper runs, but stream serializes it while building the
manager, before the invocation is created. Doing this while building the
invocation would leave stream recording no tools.

Assisted-by: Claude Opus 5
@sfc-gh-zeningchen
sfc-gh-zeningchen force-pushed the fix/ob-66110-anthropic-tool-definitions branch from c04e6b4 to dfd6b8e Compare September 14, 2026 00:20
@sfc-gh-zeningchen

Copy link
Copy Markdown
Contributor Author

LGTM, please resolve conflicts and we should be good to go.

Thanks!

Thanks! I've resolved the conflict

@lmolkova
lmolkova added this pull request to the merge queue Sep 14, 2026
Merged via the queue into open-telemetry:main with commit b537fa8 Sep 14, 2026
75 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants