mseep-agentops 0.4.18__py3-none-any.whl → 0.4.22__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.
Files changed (153) hide show
  1. agentops/__init__.py +0 -0
  2. agentops/client/api/base.py +28 -30
  3. agentops/client/api/versions/v3.py +29 -25
  4. agentops/client/api/versions/v4.py +87 -46
  5. agentops/client/client.py +98 -29
  6. agentops/client/http/README.md +87 -0
  7. agentops/client/http/http_client.py +126 -172
  8. agentops/config.py +8 -2
  9. agentops/instrumentation/OpenTelemetry.md +133 -0
  10. agentops/instrumentation/README.md +167 -0
  11. agentops/instrumentation/__init__.py +13 -1
  12. agentops/instrumentation/agentic/ag2/__init__.py +18 -0
  13. agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
  14. agentops/instrumentation/agentic/agno/__init__.py +19 -0
  15. agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
  16. agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
  17. agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
  18. agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
  19. agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
  20. agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
  21. agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
  22. agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
  23. agentops/instrumentation/agentic/crewai/LICENSE +201 -0
  24. agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
  25. agentops/instrumentation/agentic/crewai/__init__.py +6 -0
  26. agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
  27. agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
  28. agentops/instrumentation/agentic/crewai/version.py +1 -0
  29. agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
  30. agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
  31. agentops/instrumentation/agentic/google_adk/patch.py +767 -0
  32. agentops/instrumentation/agentic/haystack/__init__.py +1 -0
  33. agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
  34. agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
  35. agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
  36. agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
  37. agentops/instrumentation/agentic/langgraph/version.py +1 -0
  38. agentops/instrumentation/agentic/openai_agents/README.md +156 -0
  39. agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
  40. agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
  41. agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
  42. agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
  43. agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
  44. agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
  45. agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
  46. agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
  47. agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
  48. agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
  49. agentops/instrumentation/agentic/smolagents/README.md +88 -0
  50. agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
  51. agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
  52. agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
  53. agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
  54. agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
  55. agentops/instrumentation/agentic/xpander/__init__.py +15 -0
  56. agentops/instrumentation/agentic/xpander/context.py +112 -0
  57. agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
  58. agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
  59. agentops/instrumentation/agentic/xpander/version.py +3 -0
  60. agentops/instrumentation/common/README.md +65 -0
  61. agentops/instrumentation/common/attributes.py +1 -2
  62. agentops/instrumentation/providers/anthropic/__init__.py +24 -0
  63. agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
  64. agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
  65. agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
  66. agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
  67. agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
  68. agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
  69. agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
  70. agentops/instrumentation/providers/google_genai/README.md +33 -0
  71. agentops/instrumentation/providers/google_genai/__init__.py +24 -0
  72. agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
  73. agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
  74. agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
  75. agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
  76. agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
  77. agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
  78. agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
  79. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
  80. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
  81. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
  82. agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
  83. agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
  84. agentops/instrumentation/providers/mem0/__init__.py +45 -0
  85. agentops/instrumentation/providers/mem0/common.py +377 -0
  86. agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
  87. agentops/instrumentation/providers/mem0/memory.py +430 -0
  88. agentops/instrumentation/providers/openai/__init__.py +21 -0
  89. agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
  90. agentops/instrumentation/providers/openai/attributes/common.py +55 -0
  91. agentops/instrumentation/providers/openai/attributes/response.py +607 -0
  92. agentops/instrumentation/providers/openai/config.py +36 -0
  93. agentops/instrumentation/providers/openai/instrumentor.py +312 -0
  94. agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
  95. agentops/instrumentation/providers/openai/utils.py +44 -0
  96. agentops/instrumentation/providers/openai/v0.py +176 -0
  97. agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
  98. agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
  99. agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
  100. agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
  101. agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
  102. agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
  103. agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
  104. agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
  105. agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
  106. agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
  107. agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
  108. agentops/integration/callbacks/dspy/__init__.py +11 -0
  109. agentops/integration/callbacks/dspy/callback.py +471 -0
  110. agentops/integration/callbacks/langchain/README.md +59 -0
  111. agentops/integration/callbacks/langchain/__init__.py +15 -0
  112. agentops/integration/callbacks/langchain/callback.py +791 -0
  113. agentops/integration/callbacks/langchain/utils.py +54 -0
  114. agentops/legacy/crewai.md +121 -0
  115. agentops/logging/instrument_logging.py +4 -0
  116. agentops/sdk/README.md +220 -0
  117. agentops/sdk/core.py +75 -32
  118. agentops/sdk/descriptors/classproperty.py +28 -0
  119. agentops/sdk/exporters.py +152 -33
  120. agentops/semconv/README.md +125 -0
  121. agentops/semconv/span_kinds.py +0 -2
  122. agentops/validation.py +102 -63
  123. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/METADATA +30 -40
  124. mseep_agentops-0.4.22.dist-info/RECORD +178 -0
  125. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/WHEEL +1 -2
  126. mseep_agentops-0.4.18.dist-info/RECORD +0 -94
  127. mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
  128. tests/conftest.py +0 -10
  129. tests/unit/client/__init__.py +0 -1
  130. tests/unit/client/test_http_adapter.py +0 -221
  131. tests/unit/client/test_http_client.py +0 -206
  132. tests/unit/conftest.py +0 -54
  133. tests/unit/sdk/__init__.py +0 -1
  134. tests/unit/sdk/instrumentation_tester.py +0 -207
  135. tests/unit/sdk/test_attributes.py +0 -392
  136. tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
  137. tests/unit/sdk/test_decorators.py +0 -763
  138. tests/unit/sdk/test_exporters.py +0 -241
  139. tests/unit/sdk/test_factory.py +0 -1188
  140. tests/unit/sdk/test_internal_span_processor.py +0 -397
  141. tests/unit/sdk/test_resource_attributes.py +0 -35
  142. tests/unit/test_config.py +0 -82
  143. tests/unit/test_context_manager.py +0 -777
  144. tests/unit/test_events.py +0 -27
  145. tests/unit/test_host_env.py +0 -54
  146. tests/unit/test_init_py.py +0 -501
  147. tests/unit/test_serialization.py +0 -433
  148. tests/unit/test_session.py +0 -676
  149. tests/unit/test_user_agent.py +0 -34
  150. tests/unit/test_validation.py +0 -405
  151. {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
  152. /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
  153. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.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")