Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Features

* **anthropic:** instrument the beta tool runner with a parent chain span and tool child spans ([#3392](https://github.com/Arize-ai/openinference/issues/3392))

## [1.1.2](https://github.com/Arize-ai/openinference/compare/python-openinference-instrumentation-anthropic-v1.1.1...python-openinference-instrumentation-anthropic-v1.1.2) (2026-08-07)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Example: tracing Anthropic's beta tool runner with OpenInference.

Runs Anthropic's ``client.beta.messages.tool_runner`` against a local tool
function so that tool use, tool calls, and the final answer flow through
the instrumented session. Spans are exported to a local Phoenix or any
OTLP-compatible collector on port 6006.

Run:

ANTHROPIC_API_KEY=... python tool_runner.py
"""

import anthropic
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

from openinference.instrumentation.anthropic import AnthropicInstrumentor

endpoint = "http://127.0.0.1:6006/v1/traces"
tracer_provider = trace_sdk.TracerProvider()
tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))

AnthropicInstrumentor().instrument(tracer_provider=tracer_provider)


def get_weather(location: str) -> str:
"""A local tool the runner invokes when the model asks for weather."""
return f"The weather in {location} is sunny and 72 degrees."


client = anthropic.Anthropic(api_key="sk-proj-...")
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-6",
max_tokens=1024,
max_iterations=3,
messages=[{"role": "user", "content": "What is the weather in Paris?"}],
tools=[get_weather],
)

for message in runner:
for block in message.content:
if block.type == "text":
print(block.text)
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@

from openinference.instrumentation import OITracer, TraceConfig
from openinference.instrumentation.anthropic._wrappers import (
_AsyncBetaToolRunnerWrapper,
_AsyncCompletionsWrapper,
_AsyncMessagesStreamWrapper,
_AsyncMessageStreamManager,
_AsyncMessagesWrapper,
_AsyncTransformWrapper,
_BetaAsyncMessageStreamManager,
_BetaMessageStreamManager,
_BetaToolRunnerWrapper,
_CompletionsWrapper,
_MessagesStreamWrapper,
_MessageStreamManager,
Expand Down Expand Up @@ -47,6 +49,8 @@ class AnthropicInstrumentor(BaseInstrumentor): # type: ignore[misc]
"_original_async_beta_messages_stream",
"_original_beta_messages_parse",
"_original_async_beta_messages_parse",
"_original_beta_messages_tool_runner",
"_original_async_beta_messages_tool_runner",
"_original_transform",
"_original_async_transform",
"_instruments",
Expand Down Expand Up @@ -217,6 +221,26 @@ def _instrument(self, **kwargs: Any) -> None:
),
)

self._original_beta_messages_tool_runner = BetaMessages.tool_runner
wrap_function_wrapper(
"anthropic.resources.beta.messages",
"Messages.tool_runner",
_BetaToolRunnerWrapper(
tracer=self._tracer, # type: ignore[arg-type]
span_name="beta.messages.tool_runner",
),
)

self._original_async_beta_messages_tool_runner = AsyncBetaMessages.tool_runner
wrap_function_wrapper(
"anthropic.resources.beta.messages",
"AsyncMessages.tool_runner",
_AsyncBetaToolRunnerWrapper(
tracer=self._tracer, # type: ignore[arg-type]
span_name="beta.messages.tool_runner",
),
)

import anthropic._utils._transform as _transform_module

self._original_transform = _transform_module.transform
Expand Down Expand Up @@ -275,6 +299,10 @@ def _uninstrument(self, **kwargs: Any) -> None:
if self._original_async_beta_messages_parse is not None:
AsyncBetaMessages.parse = self._original_async_beta_messages_parse # type: ignore[method-assign]

if self._original_beta_messages_tool_runner is not None:
BetaMessages.tool_runner = self._original_beta_messages_tool_runner # type: ignore[method-assign]
if self._original_async_beta_messages_tool_runner is not None:
AsyncBetaMessages.tool_runner = self._original_async_beta_messages_tool_runner # type: ignore[method-assign]
if self._original_transform is not None:
_transform_module.transform = self._original_transform
if self._original_async_transform is not None:
Expand Down
Loading
Loading