mseep-agentops 0.4.18__py3-none-any.whl → 0.4.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentops/__init__.py +0 -0
- agentops/client/api/base.py +28 -30
- agentops/client/api/versions/v3.py +29 -25
- agentops/client/api/versions/v4.py +87 -46
- agentops/client/client.py +98 -29
- agentops/client/http/README.md +87 -0
- agentops/client/http/http_client.py +126 -172
- agentops/config.py +8 -2
- agentops/instrumentation/OpenTelemetry.md +133 -0
- agentops/instrumentation/README.md +167 -0
- agentops/instrumentation/__init__.py +13 -1
- agentops/instrumentation/agentic/ag2/__init__.py +18 -0
- agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
- agentops/instrumentation/agentic/agno/__init__.py +19 -0
- agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
- agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
- agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
- agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
- agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
- agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
- agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
- agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
- agentops/instrumentation/agentic/crewai/LICENSE +201 -0
- agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
- agentops/instrumentation/agentic/crewai/__init__.py +6 -0
- agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
- agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
- agentops/instrumentation/agentic/crewai/version.py +1 -0
- agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
- agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
- agentops/instrumentation/agentic/google_adk/patch.py +767 -0
- agentops/instrumentation/agentic/haystack/__init__.py +1 -0
- agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
- agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
- agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
- agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
- agentops/instrumentation/agentic/langgraph/version.py +1 -0
- agentops/instrumentation/agentic/openai_agents/README.md +156 -0
- agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
- agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
- agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
- agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
- agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
- agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
- agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
- agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
- agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
- agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
- agentops/instrumentation/agentic/smolagents/README.md +88 -0
- agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
- agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
- agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
- agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
- agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
- agentops/instrumentation/agentic/xpander/__init__.py +15 -0
- agentops/instrumentation/agentic/xpander/context.py +112 -0
- agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
- agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
- agentops/instrumentation/agentic/xpander/version.py +3 -0
- agentops/instrumentation/common/README.md +65 -0
- agentops/instrumentation/common/attributes.py +1 -2
- agentops/instrumentation/providers/anthropic/__init__.py +24 -0
- agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
- agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
- agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
- agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
- agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
- agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
- agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
- agentops/instrumentation/providers/google_genai/README.md +33 -0
- agentops/instrumentation/providers/google_genai/__init__.py +24 -0
- agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
- agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
- agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
- agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
- agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
- agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
- agentops/instrumentation/providers/mem0/__init__.py +45 -0
- agentops/instrumentation/providers/mem0/common.py +377 -0
- agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
- agentops/instrumentation/providers/mem0/memory.py +430 -0
- agentops/instrumentation/providers/openai/__init__.py +21 -0
- agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
- agentops/instrumentation/providers/openai/attributes/common.py +55 -0
- agentops/instrumentation/providers/openai/attributes/response.py +607 -0
- agentops/instrumentation/providers/openai/config.py +36 -0
- agentops/instrumentation/providers/openai/instrumentor.py +312 -0
- agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
- agentops/instrumentation/providers/openai/utils.py +44 -0
- agentops/instrumentation/providers/openai/v0.py +176 -0
- agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
- agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
- agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
- agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
- agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
- agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
- agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
- agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
- agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
- agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
- agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
- agentops/integration/callbacks/dspy/__init__.py +11 -0
- agentops/integration/callbacks/dspy/callback.py +471 -0
- agentops/integration/callbacks/langchain/README.md +59 -0
- agentops/integration/callbacks/langchain/__init__.py +15 -0
- agentops/integration/callbacks/langchain/callback.py +791 -0
- agentops/integration/callbacks/langchain/utils.py +54 -0
- agentops/legacy/crewai.md +121 -0
- agentops/logging/instrument_logging.py +4 -0
- agentops/sdk/README.md +220 -0
- agentops/sdk/core.py +75 -32
- agentops/sdk/descriptors/classproperty.py +28 -0
- agentops/sdk/exporters.py +152 -33
- agentops/semconv/README.md +125 -0
- agentops/semconv/span_kinds.py +0 -2
- agentops/validation.py +102 -63
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/METADATA +30 -40
- mseep_agentops-0.4.23.dist-info/RECORD +178 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/WHEEL +1 -2
- mseep_agentops-0.4.18.dist-info/RECORD +0 -94
- mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
- tests/conftest.py +0 -10
- tests/unit/client/__init__.py +0 -1
- tests/unit/client/test_http_adapter.py +0 -221
- tests/unit/client/test_http_client.py +0 -206
- tests/unit/conftest.py +0 -54
- tests/unit/sdk/__init__.py +0 -1
- tests/unit/sdk/instrumentation_tester.py +0 -207
- tests/unit/sdk/test_attributes.py +0 -392
- tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
- tests/unit/sdk/test_decorators.py +0 -763
- tests/unit/sdk/test_exporters.py +0 -241
- tests/unit/sdk/test_factory.py +0 -1188
- tests/unit/sdk/test_internal_span_processor.py +0 -397
- tests/unit/sdk/test_resource_attributes.py +0 -35
- tests/unit/test_config.py +0 -82
- tests/unit/test_context_manager.py +0 -777
- tests/unit/test_events.py +0 -27
- tests/unit/test_host_env.py +0 -54
- tests/unit/test_init_py.py +0 -501
- tests/unit/test_serialization.py +0 -433
- tests/unit/test_session.py +0 -676
- tests/unit/test_user_agent.py +0 -34
- tests/unit/test_validation.py +0 -405
- {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
- /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,231 @@
|
|
1
|
+
"""Tool-related attribute extraction for Anthropic API."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
from typing import Dict, Any, List, Optional
|
5
|
+
|
6
|
+
from agentops.logging import logger
|
7
|
+
from agentops.semconv import SpanAttributes, MessageAttributes, ToolAttributes, ToolStatus
|
8
|
+
from agentops.instrumentation.common.attributes import AttributeMap
|
9
|
+
|
10
|
+
|
11
|
+
def extract_tool_definitions(tools: List[Dict[str, Any]]) -> AttributeMap:
|
12
|
+
"""Extract attributes from tool definitions.
|
13
|
+
|
14
|
+
Processes a list of Anthropic tool definitions and converts them into
|
15
|
+
standardized attributes for OpenTelemetry instrumentation. This captures
|
16
|
+
information about each tool's name, description, and input schema.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
tools: List of tool definition objects
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
Dictionary of tool-related attributes
|
23
|
+
"""
|
24
|
+
attributes = {}
|
25
|
+
|
26
|
+
try:
|
27
|
+
if not tools:
|
28
|
+
return attributes
|
29
|
+
|
30
|
+
for i, tool in enumerate(tools):
|
31
|
+
name = tool.get("name", "unknown")
|
32
|
+
description = tool.get("description", "")
|
33
|
+
|
34
|
+
attributes[MessageAttributes.TOOL_CALL_NAME.format(i=i)] = name
|
35
|
+
attributes[MessageAttributes.TOOL_CALL_TYPE.format(i=i)] = "function"
|
36
|
+
|
37
|
+
if description:
|
38
|
+
attributes[MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=i)] = description
|
39
|
+
|
40
|
+
if "input_schema" in tool:
|
41
|
+
attributes[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=i)] = json.dumps(tool["input_schema"])
|
42
|
+
|
43
|
+
tool_id = tool.get("id", f"tool-{i}")
|
44
|
+
attributes[MessageAttributes.TOOL_CALL_ID.format(i=i)] = tool_id
|
45
|
+
attributes[MessageAttributes.TOOL_CALL_NAME.format(i=i)] = name
|
46
|
+
if description:
|
47
|
+
attributes[MessageAttributes.TOOL_CALL_DESCRIPTION.format(i=i)] = description
|
48
|
+
|
49
|
+
tool_names = [tool.get("name", "unknown") for tool in tools]
|
50
|
+
attributes[SpanAttributes.LLM_REQUEST_FUNCTIONS] = json.dumps(tool_names)
|
51
|
+
|
52
|
+
tool_schemas = []
|
53
|
+
for tool in tools:
|
54
|
+
schema = {"name": tool.get("name", "unknown"), "schema": {}}
|
55
|
+
|
56
|
+
if "description" in tool:
|
57
|
+
schema["schema"]["description"] = tool["description"]
|
58
|
+
if "input_schema" in tool:
|
59
|
+
schema["schema"]["input_schema"] = tool["input_schema"]
|
60
|
+
|
61
|
+
tool_schemas.append(schema)
|
62
|
+
|
63
|
+
attributes["anthropic.tools.schemas"] = json.dumps(tool_schemas)
|
64
|
+
|
65
|
+
except Exception as e:
|
66
|
+
logger.debug(f"[agentops.instrumentation.anthropic] Error extracting tool definitions: {e}")
|
67
|
+
|
68
|
+
return attributes
|
69
|
+
|
70
|
+
|
71
|
+
def extract_tool_use_blocks(content_blocks: List[Any]) -> Optional[List[Dict[str, Any]]]:
|
72
|
+
"""Extract tool use blocks from message content.
|
73
|
+
|
74
|
+
Analyzes message content blocks to find and extract tool use information.
|
75
|
+
This is used to track which tools the model called and with what parameters.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
content_blocks: List of content blocks from a Message
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
List of tool use information or None if no tools used
|
82
|
+
"""
|
83
|
+
if not content_blocks:
|
84
|
+
return None
|
85
|
+
|
86
|
+
try:
|
87
|
+
tool_uses = []
|
88
|
+
|
89
|
+
for block in content_blocks:
|
90
|
+
if hasattr(block, "type") and block.type == "tool_use":
|
91
|
+
tool_use = {
|
92
|
+
"name": block.name if hasattr(block, "name") else "unknown",
|
93
|
+
"id": block.id if hasattr(block, "id") else "unknown",
|
94
|
+
}
|
95
|
+
|
96
|
+
if hasattr(block, "input"):
|
97
|
+
try:
|
98
|
+
if isinstance(block.input, dict):
|
99
|
+
tool_use["input"] = block.input
|
100
|
+
elif isinstance(block.input, str):
|
101
|
+
tool_use["input"] = json.loads(block.input)
|
102
|
+
else:
|
103
|
+
tool_use["input"] = {"raw": str(block.input)}
|
104
|
+
except Exception:
|
105
|
+
tool_use["input"] = {"raw": str(block.input)}
|
106
|
+
|
107
|
+
tool_uses.append(tool_use)
|
108
|
+
|
109
|
+
return tool_uses if tool_uses else None
|
110
|
+
|
111
|
+
except Exception as e:
|
112
|
+
logger.debug(f"[agentops.instrumentation.anthropic] Error extracting tool use blocks: {e}")
|
113
|
+
return None
|
114
|
+
|
115
|
+
|
116
|
+
def extract_tool_results(content_blocks: List[Any]) -> Optional[List[Dict[str, Any]]]:
|
117
|
+
"""Extract tool result blocks from message content.
|
118
|
+
|
119
|
+
Analyzes message content blocks to find and extract tool result information.
|
120
|
+
This is used to track the outputs returned from tool executions.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
content_blocks: List of content blocks from a Message
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
List of tool result information or None if no tool results
|
127
|
+
"""
|
128
|
+
if not content_blocks:
|
129
|
+
return None
|
130
|
+
|
131
|
+
try:
|
132
|
+
tool_results = []
|
133
|
+
|
134
|
+
for block in content_blocks:
|
135
|
+
if hasattr(block, "type") and block.type == "tool_result":
|
136
|
+
tool_result = {
|
137
|
+
"tool_use_id": block.tool_use_id if hasattr(block, "tool_use_id") else "unknown",
|
138
|
+
}
|
139
|
+
|
140
|
+
if hasattr(block, "content"):
|
141
|
+
try:
|
142
|
+
if isinstance(block.content, dict):
|
143
|
+
tool_result["content"] = block.content
|
144
|
+
elif isinstance(block.content, str):
|
145
|
+
tool_result["content"] = json.loads(block.content)
|
146
|
+
else:
|
147
|
+
tool_result["content"] = {"raw": str(block.content)}
|
148
|
+
except Exception:
|
149
|
+
tool_result["content"] = {"raw": str(block.content)}
|
150
|
+
|
151
|
+
tool_results.append(tool_result)
|
152
|
+
|
153
|
+
return tool_results if tool_results else None
|
154
|
+
|
155
|
+
except Exception as e:
|
156
|
+
logger.debug(f"[agentops.instrumentation.anthropic] Error extracting tool results: {e}")
|
157
|
+
return None
|
158
|
+
|
159
|
+
|
160
|
+
def get_tool_attributes(message_content: List[Any]) -> AttributeMap:
|
161
|
+
"""Extract tool-related attributes from message content.
|
162
|
+
|
163
|
+
Processes message content to extract comprehensive information about
|
164
|
+
tool usage, including both tool calls and tool results. This creates a
|
165
|
+
standardized set of attributes representing the tool interaction flow.
|
166
|
+
|
167
|
+
Args:
|
168
|
+
message_content: List of content blocks from a Message
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
Dictionary of tool-related attributes
|
172
|
+
"""
|
173
|
+
attributes = {}
|
174
|
+
|
175
|
+
try:
|
176
|
+
tool_uses = extract_tool_use_blocks(message_content)
|
177
|
+
if tool_uses:
|
178
|
+
for j, tool_use in enumerate(tool_uses):
|
179
|
+
tool_name = tool_use.get("name", "unknown")
|
180
|
+
tool_id = tool_use.get("id", f"tool-call-{j}")
|
181
|
+
|
182
|
+
attributes[MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=j)] = tool_id
|
183
|
+
attributes[MessageAttributes.COMPLETION_TOOL_CALL_NAME.format(i=0, j=j)] = tool_name
|
184
|
+
attributes[MessageAttributes.COMPLETION_TOOL_CALL_TYPE.format(i=0, j=j)] = "function"
|
185
|
+
|
186
|
+
tool_input = tool_use.get("input", {})
|
187
|
+
if isinstance(tool_input, dict):
|
188
|
+
input_str = json.dumps(tool_input)
|
189
|
+
else:
|
190
|
+
input_str = str(tool_input)
|
191
|
+
attributes[MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS.format(i=0, j=j)] = input_str
|
192
|
+
|
193
|
+
attributes[MessageAttributes.TOOL_CALL_ID.format(i=j)] = tool_id
|
194
|
+
attributes[MessageAttributes.TOOL_CALL_NAME.format(i=j)] = tool_name
|
195
|
+
attributes[MessageAttributes.TOOL_CALL_ARGUMENTS.format(i=j)] = input_str
|
196
|
+
attributes[f"{ToolAttributes.TOOL_STATUS}.{j}"] = ToolStatus.EXECUTING.value
|
197
|
+
|
198
|
+
attributes["anthropic.tool_calls.count"] = len(tool_uses)
|
199
|
+
|
200
|
+
tool_results = extract_tool_results(message_content)
|
201
|
+
if tool_results:
|
202
|
+
attributes["anthropic.tool_results"] = json.dumps(tool_results)
|
203
|
+
attributes["anthropic.tool_results.count"] = len(tool_results)
|
204
|
+
|
205
|
+
for j, tool_result in enumerate(tool_results):
|
206
|
+
tool_use_id = tool_result.get("tool_use_id", "unknown")
|
207
|
+
|
208
|
+
tool_index = None
|
209
|
+
for k in range(attributes.get("anthropic.tool_calls.count", 0)):
|
210
|
+
if attributes.get(MessageAttributes.COMPLETION_TOOL_CALL_ID.format(i=0, j=k)) == tool_use_id:
|
211
|
+
tool_index = k
|
212
|
+
break
|
213
|
+
|
214
|
+
if tool_index is not None:
|
215
|
+
attributes[MessageAttributes.COMPLETION_TOOL_CALL_STATUS.format(i=0, j=tool_index)] = "complete"
|
216
|
+
|
217
|
+
content = tool_result.get("content", {})
|
218
|
+
if isinstance(content, dict):
|
219
|
+
content_str = json.dumps(content)
|
220
|
+
else:
|
221
|
+
content_str = str(content)
|
222
|
+
|
223
|
+
attributes[f"{ToolAttributes.TOOL_STATUS}.{tool_index}"] = ToolStatus.SUCCEEDED.value
|
224
|
+
attributes[f"{ToolAttributes.TOOL_RESULT}.{tool_index}"] = content_str
|
225
|
+
|
226
|
+
attributes[f"anthropic.tool_result.{tool_index}.content"] = content_str
|
227
|
+
|
228
|
+
except Exception as e:
|
229
|
+
logger.debug(f"[agentops.instrumentation.anthropic] Error extracting tool attributes: {e}")
|
230
|
+
|
231
|
+
return attributes
|
@@ -0,0 +1,90 @@
|
|
1
|
+
"""Event handler wrapper for Anthropic's streaming API.
|
2
|
+
|
3
|
+
This module provides a wrapper for Anthropic's event handlers to
|
4
|
+
track events and metrics during streaming.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Any, Dict, Optional
|
9
|
+
|
10
|
+
from opentelemetry.trace import Span
|
11
|
+
from agentops.semconv import CoreAttributes
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class EventHandleWrapper:
|
17
|
+
"""Wrapper for Anthropic's EventHandler.
|
18
|
+
|
19
|
+
This wrapper forwards all events to the original handler while also
|
20
|
+
capturing metrics and adding them to the provided span.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, original_handler: Optional[Any], span: Span):
|
24
|
+
"""Initialize the wrapper with the original handler and a span.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
original_handler: The original Anthropic event handler (or None)
|
28
|
+
span: The OpenTelemetry span to record metrics to
|
29
|
+
"""
|
30
|
+
self._original_handler = original_handler
|
31
|
+
self._span = span
|
32
|
+
|
33
|
+
def _forward_event(self, method_name: str, *args, **kwargs) -> None:
|
34
|
+
"""Forward an event to the original handler if it exists.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
method_name: Name of the method to call on the original handler
|
38
|
+
*args: Positional arguments to pass to the method
|
39
|
+
**kwargs: Keyword arguments to pass to the method
|
40
|
+
"""
|
41
|
+
try:
|
42
|
+
if self._original_handler is not None and hasattr(self._original_handler, method_name):
|
43
|
+
method = getattr(self._original_handler, method_name)
|
44
|
+
method(*args, **kwargs)
|
45
|
+
except Exception as e:
|
46
|
+
logger.debug(f"Error in event handler {method_name}: {e}")
|
47
|
+
|
48
|
+
def on_event(self, event: Dict[str, Any]) -> None:
|
49
|
+
"""Handle any event by forwarding it to the original handler."""
|
50
|
+
self._forward_event("on_event", event)
|
51
|
+
|
52
|
+
def on_text_delta(self, delta: Dict[str, Any], snapshot: Dict[str, Any]) -> None:
|
53
|
+
"""Handle a text delta event."""
|
54
|
+
self._forward_event("on_text_delta", delta, snapshot)
|
55
|
+
|
56
|
+
def on_content_block_start(self, content_block_start: Dict[str, Any]) -> None:
|
57
|
+
"""Handle a content block start event."""
|
58
|
+
self._forward_event("on_content_block_start", content_block_start)
|
59
|
+
|
60
|
+
def on_content_block_delta(self, delta: Dict[str, Any], snapshot: Dict[str, Any]) -> None:
|
61
|
+
"""Handle a content block delta event."""
|
62
|
+
self._forward_event("on_content_block_delta", delta, snapshot)
|
63
|
+
|
64
|
+
def on_content_block_stop(self, content_block_stop: Dict[str, Any]) -> None:
|
65
|
+
"""Handle a content block stop event."""
|
66
|
+
self._forward_event("on_content_block_stop", content_block_stop)
|
67
|
+
|
68
|
+
def on_message_start(self, message_start: Dict[str, Any]) -> None:
|
69
|
+
"""Handle a message start event."""
|
70
|
+
self._forward_event("on_message_start", message_start)
|
71
|
+
|
72
|
+
def on_message_delta(self, delta: Dict[str, Any], snapshot: Dict[str, Any]) -> None:
|
73
|
+
"""Handle a message delta event."""
|
74
|
+
self._forward_event("on_message_delta", delta, snapshot)
|
75
|
+
|
76
|
+
def on_message_stop(self, message_stop: Dict[str, Any]) -> None:
|
77
|
+
"""Handle a message stop event."""
|
78
|
+
self._forward_event("on_message_stop", message_stop)
|
79
|
+
|
80
|
+
def on_error(self, error: Exception) -> None:
|
81
|
+
"""Handle an error event."""
|
82
|
+
try:
|
83
|
+
self._span.record_exception(error)
|
84
|
+
self._span.set_attribute(CoreAttributes.ERROR_MESSAGE, str(error))
|
85
|
+
self._span.set_attribute(CoreAttributes.ERROR_TYPE, error.__class__.__name__)
|
86
|
+
|
87
|
+
if self._original_handler is not None and hasattr(self._original_handler, "on_error"):
|
88
|
+
self._original_handler.on_error(error)
|
89
|
+
except Exception as e:
|
90
|
+
logger.debug(f"Error in event handler on_error: {e}")
|
@@ -0,0 +1,146 @@
|
|
1
|
+
"""Anthropic API Instrumentation for AgentOps
|
2
|
+
|
3
|
+
This module provides instrumentation for the Anthropic API, implementing OpenTelemetry
|
4
|
+
instrumentation for Claude model requests and responses.
|
5
|
+
|
6
|
+
We focus on instrumenting the following key endpoints:
|
7
|
+
- Client.messages.create - The main completion endpoint
|
8
|
+
- Client.messages.stream - Streaming API for messages
|
9
|
+
- Client.completions.create - The legacy completion endpoint
|
10
|
+
- Streaming responses - Special handling for streaming responses
|
11
|
+
- Tool-using completions - Capturing tool usage information
|
12
|
+
|
13
|
+
The instrumentation captures:
|
14
|
+
1. Request parameters (model, max_tokens, temperature, etc.)
|
15
|
+
2. Response data (completion content, token usage, etc.)
|
16
|
+
3. Timing information (latency, time to first token, etc.)
|
17
|
+
4. Tool usage information (tool calls, tool outputs)
|
18
|
+
|
19
|
+
1. Standard Method Wrapping:
|
20
|
+
- Uses the common wrappers module to wrap methods with tracers
|
21
|
+
- Applies to both sync and async methods
|
22
|
+
- Captures request/response attributes via attribute extractors
|
23
|
+
|
24
|
+
2. Streaming Approach:
|
25
|
+
- Special handling for streaming responses
|
26
|
+
- Uses direct wrapt.wrap_function_wrapper for stream methods
|
27
|
+
- Captures events as they arrive rather than waiting for completion
|
28
|
+
- Maintains span context across multiple events
|
29
|
+
"""
|
30
|
+
|
31
|
+
from typing import Dict, Any
|
32
|
+
from wrapt import wrap_function_wrapper
|
33
|
+
|
34
|
+
from agentops.logging import logger
|
35
|
+
from agentops.instrumentation.common import CommonInstrumentor, InstrumentorConfig, WrapConfig, StandardMetrics
|
36
|
+
from agentops.instrumentation.providers.anthropic import LIBRARY_NAME, LIBRARY_VERSION
|
37
|
+
from agentops.instrumentation.providers.anthropic.attributes.message import (
|
38
|
+
get_message_attributes,
|
39
|
+
get_completion_attributes,
|
40
|
+
)
|
41
|
+
from agentops.instrumentation.providers.anthropic.stream_wrapper import (
|
42
|
+
messages_stream_wrapper,
|
43
|
+
messages_stream_async_wrapper,
|
44
|
+
)
|
45
|
+
from opentelemetry.metrics import Meter
|
46
|
+
from opentelemetry.instrumentation.utils import unwrap as otel_unwrap
|
47
|
+
|
48
|
+
|
49
|
+
class AnthropicInstrumentor(CommonInstrumentor):
|
50
|
+
"""An instrumentor for Anthropic's Claude API.
|
51
|
+
|
52
|
+
This class provides instrumentation for Anthropic's Claude API by wrapping key methods
|
53
|
+
in the client library and capturing telemetry data. It supports both synchronous and
|
54
|
+
asynchronous API calls, including streaming responses.
|
55
|
+
|
56
|
+
The instrumentor wraps the following methods:
|
57
|
+
- messages.create: For the modern Messages API
|
58
|
+
- completions.create: For the legacy Completions API
|
59
|
+
- messages.stream: For streaming responses
|
60
|
+
|
61
|
+
It captures metrics including token usage, operation duration, and exceptions.
|
62
|
+
"""
|
63
|
+
|
64
|
+
def __init__(self):
|
65
|
+
# Define wrapped methods
|
66
|
+
wrapped_methods = [
|
67
|
+
# Main messages.create (modern API)
|
68
|
+
WrapConfig(
|
69
|
+
trace_name="anthropic.messages.create",
|
70
|
+
package="anthropic.resources.messages",
|
71
|
+
class_name="Messages",
|
72
|
+
method_name="create",
|
73
|
+
handler=get_message_attributes,
|
74
|
+
),
|
75
|
+
# Async variant
|
76
|
+
WrapConfig(
|
77
|
+
trace_name="anthropic.messages.create",
|
78
|
+
package="anthropic.resources.messages",
|
79
|
+
class_name="AsyncMessages",
|
80
|
+
method_name="create",
|
81
|
+
handler=get_message_attributes,
|
82
|
+
is_async=True,
|
83
|
+
),
|
84
|
+
# Legacy completions API
|
85
|
+
WrapConfig(
|
86
|
+
trace_name="anthropic.completions.create",
|
87
|
+
package="anthropic.resources.completions",
|
88
|
+
class_name="Completions",
|
89
|
+
method_name="create",
|
90
|
+
handler=get_completion_attributes,
|
91
|
+
),
|
92
|
+
# Async variant of legacy API
|
93
|
+
WrapConfig(
|
94
|
+
trace_name="anthropic.completions.create",
|
95
|
+
package="anthropic.resources.completions",
|
96
|
+
class_name="AsyncCompletions",
|
97
|
+
method_name="create",
|
98
|
+
handler=get_completion_attributes,
|
99
|
+
is_async=True,
|
100
|
+
),
|
101
|
+
]
|
102
|
+
|
103
|
+
# Create instrumentor config
|
104
|
+
config = InstrumentorConfig(
|
105
|
+
library_name=LIBRARY_NAME,
|
106
|
+
library_version=LIBRARY_VERSION,
|
107
|
+
wrapped_methods=wrapped_methods,
|
108
|
+
metrics_enabled=True,
|
109
|
+
dependencies=["anthropic >= 0.7.0"],
|
110
|
+
)
|
111
|
+
|
112
|
+
super().__init__(config)
|
113
|
+
|
114
|
+
def _create_metrics(self, meter: Meter) -> Dict[str, Any]:
|
115
|
+
"""Create metrics for Anthropic instrumentation."""
|
116
|
+
# Use standard metrics from common module
|
117
|
+
return StandardMetrics.create_standard_metrics(meter)
|
118
|
+
|
119
|
+
def _custom_wrap(self, **kwargs):
|
120
|
+
"""Perform custom wrapping for streaming methods."""
|
121
|
+
# Special handling for streaming responses
|
122
|
+
# Uses direct wrapt.wrap_function_wrapper for stream methods
|
123
|
+
# This approach captures events as they arrive rather than waiting for completion
|
124
|
+
try:
|
125
|
+
wrap_function_wrapper(
|
126
|
+
"anthropic.resources.messages.messages",
|
127
|
+
"Messages.stream",
|
128
|
+
messages_stream_wrapper(self._tracer),
|
129
|
+
)
|
130
|
+
|
131
|
+
wrap_function_wrapper(
|
132
|
+
"anthropic.resources.messages.messages",
|
133
|
+
"AsyncMessages.stream",
|
134
|
+
messages_stream_async_wrapper(self._tracer),
|
135
|
+
)
|
136
|
+
except (AttributeError, ModuleNotFoundError):
|
137
|
+
logger.debug("Failed to wrap Anthropic streaming methods")
|
138
|
+
|
139
|
+
def _custom_unwrap(self, **kwargs):
|
140
|
+
"""Perform custom unwrapping for streaming methods."""
|
141
|
+
# Unwrap streaming methods
|
142
|
+
try:
|
143
|
+
otel_unwrap("anthropic.resources.messages.messages", "Messages.stream")
|
144
|
+
otel_unwrap("anthropic.resources.messages.messages", "AsyncMessages.stream")
|
145
|
+
except (AttributeError, ModuleNotFoundError):
|
146
|
+
logger.debug("Failed to unwrap Anthropic streaming methods")
|