Skip to content

Commit a709628

Browse files
valkryptonclaude
andcommitted
fix(anthropic): send tool definitions in standardized format
This change adds gen_ai.tool.definitions to the anthropic integration to align with the new Generative AI semantic conventions. Co-Authored-By: Claude 3.5 Sonnet <noreply@anthropic.com>
1 parent 4cd6752 commit a709628

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

sentry_sdk/consts.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,12 @@ class SPANDATA:
563563
Example: [{"type": "text", "text": "You are a helpful assistant."},{"type": "text", "text": "Be concise and clear."}]
564564
"""
565565

566+
GEN_AI_TOOL_DEFINITIONS = "gen_ai.tool.definitions"
567+
"""
568+
The definitions of the tools available to the model.
569+
Example: [{"name": "get_weather", "description": "Get the weather for a given location", "type": "function", "parameters": {"location": "string"}}]
570+
"""
571+
566572
GEN_AI_REQUEST_MESSAGES = "gen_ai.request.messages"
567573
"""
568574
The messages passed to the model. The "content" can be a string or an array of objects.

sentry_sdk/integrations/anthropic.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
truncate_and_annotate_messages,
1414
get_start_span_function,
1515
transform_anthropic_content_part,
16+
_normalize_data,
1617
)
1718
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
1819
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
@@ -368,6 +369,29 @@ def _transform_system_instructions(
368369
]
369370

370371

372+
def _transform_anthropic_tools(
373+
tools: "Iterable[ToolUnionParam]",
374+
) -> "list[dict[str, Any]]":
375+
transformed_tools = []
376+
for tool in tools:
377+
if not isinstance(tool, dict):
378+
continue
379+
380+
transformed_tool = {
381+
"name": tool.get("name"),
382+
"description": tool.get("description"),
383+
"type": "function",
384+
}
385+
386+
input_schema = tool.get("input_schema")
387+
if input_schema:
388+
transformed_tool["parameters"] = _normalize_data(input_schema, unpack=False)
389+
390+
transformed_tools.append(transformed_tool)
391+
392+
return transformed_tools
393+
394+
371395
def _set_common_input_data(
372396
span: "Span",
373397
integration: "AnthropicIntegration",
@@ -463,6 +487,13 @@ def _set_common_input_data(
463487

464488
if tools is not None and _is_given(tools) and len(tools) > 0: # type: ignore
465489
span.set_data(SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools))
490+
if should_send_default_pii() and integration.include_prompts:
491+
set_data_normalized(
492+
span,
493+
SPANDATA.GEN_AI_TOOL_DEFINITIONS,
494+
_transform_anthropic_tools(tools),
495+
unpack=False,
496+
)
466497

467498

468499
def _set_create_input_data(
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import pytest
2+
from unittest import mock
3+
import json
4+
5+
from anthropic import Anthropic
6+
from anthropic.types.message import Message
7+
from anthropic.types.usage import Usage
8+
9+
try:
10+
from anthropic.types.text_block import TextBlock
11+
except ImportError:
12+
from anthropic.types.content_block import ContentBlock as TextBlock
13+
14+
from sentry_sdk import start_transaction
15+
from sentry_sdk.consts import OP, SPANDATA
16+
from sentry_sdk.integrations.anthropic import AnthropicIntegration
17+
18+
19+
EXAMPLE_MESSAGE = Message(
20+
id="msg_01XFDUDYJgAACzvnptvVoYEL",
21+
model="model",
22+
role="assistant",
23+
content=[TextBlock(type="text", text="Hi, I'm Claude.")],
24+
type="message",
25+
stop_reason="end_turn",
26+
usage=Usage(input_tokens=10, output_tokens=20),
27+
)
28+
29+
TOOLS = [
30+
{
31+
"name": "get_weather",
32+
"description": "Get the current weather in a given location",
33+
"input_schema": {
34+
"type": "object",
35+
"properties": {
36+
"location": {
37+
"type": "string",
38+
"description": "The city and state, e.g. San Francisco, CA",
39+
},
40+
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
41+
},
42+
"required": ["location"],
43+
},
44+
}
45+
]
46+
47+
48+
@pytest.mark.parametrize(
49+
"send_default_pii, include_prompts",
50+
[
51+
(True, True),
52+
(True, False),
53+
(False, True),
54+
(False, False),
55+
],
56+
)
57+
def test_tool_definitions_in_create_message(
58+
sentry_init, capture_events, send_default_pii, include_prompts
59+
):
60+
sentry_init(
61+
integrations=[AnthropicIntegration(include_prompts=include_prompts)],
62+
traces_sample_rate=1.0,
63+
send_default_pii=send_default_pii,
64+
)
65+
events = capture_events()
66+
client = Anthropic(api_key="z")
67+
client.messages._post = mock.Mock(return_value=EXAMPLE_MESSAGE)
68+
69+
messages = [
70+
{
71+
"role": "user",
72+
"content": "What is the weather in San Francisco?",
73+
}
74+
]
75+
76+
with start_transaction(name="anthropic"):
77+
client.messages.create(
78+
max_tokens=1024,
79+
messages=messages,
80+
model="model",
81+
tools=TOOLS,
82+
)
83+
84+
assert len(events) == 1
85+
(event,) = events
86+
(span,) = event["spans"]
87+
88+
assert span["op"] == OP.GEN_AI_CHAT
89+
90+
# Check old available_tools attribute (always present if tools provided)
91+
assert SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS in span["data"]
92+
available_tools = json.loads(span["data"][SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS])
93+
assert available_tools == TOOLS
94+
95+
# Check new tool.definitions attribute (only present if PII and prompts enabled)
96+
if send_default_pii and include_prompts:
97+
assert SPANDATA.GEN_AI_TOOL_DEFINITIONS in span["data"]
98+
tool_definitions = json.loads(span["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS])
99+
assert len(tool_definitions) == 1
100+
assert tool_definitions[0]["name"] == "get_weather"
101+
assert (
102+
tool_definitions[0]["description"]
103+
== "Get the current weather in a given location"
104+
)
105+
assert tool_definitions[0]["type"] == "function"
106+
assert tool_definitions[0]["parameters"] == TOOLS[0]["input_schema"]
107+
else:
108+
assert SPANDATA.GEN_AI_TOOL_DEFINITIONS not in span["data"]

0 commit comments

Comments
 (0)