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,377 @@
|
|
1
|
+
"""Common utilities and base wrapper functions for Mem0 instrumentation."""
|
2
|
+
|
3
|
+
from typing import Dict, Any
|
4
|
+
from opentelemetry import context as context_api
|
5
|
+
from opentelemetry.trace import SpanKind, Status, StatusCode
|
6
|
+
|
7
|
+
from agentops.instrumentation.common.attributes import AttributeMap
|
8
|
+
from agentops.semconv import SpanAttributes, LLMRequestTypeValues
|
9
|
+
|
10
|
+
|
11
|
+
def get_common_attributes() -> AttributeMap:
|
12
|
+
"""Get common instrumentation attributes for Mem0 operations.
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
Dictionary of common Mem0 attributes
|
16
|
+
"""
|
17
|
+
attributes = {}
|
18
|
+
attributes[SpanAttributes.LLM_SYSTEM] = "Mem0"
|
19
|
+
return attributes
|
20
|
+
|
21
|
+
|
22
|
+
def _extract_common_kwargs_attributes(kwargs: Dict[str, Any]) -> AttributeMap:
|
23
|
+
"""Extract common attributes from kwargs that apply to multiple operations.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
kwargs: Keyword arguments from the method call
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
Dictionary of extracted common attributes
|
30
|
+
"""
|
31
|
+
attributes = {}
|
32
|
+
|
33
|
+
# Extract user/agent/run IDs
|
34
|
+
for id_type in ["user_id", "agent_id", "run_id"]:
|
35
|
+
if id_type in kwargs and kwargs[id_type]:
|
36
|
+
# Use the new mem0-specific attributes
|
37
|
+
if id_type == "user_id":
|
38
|
+
attributes["mem0.user_id"] = str(kwargs[id_type])
|
39
|
+
elif id_type == "agent_id":
|
40
|
+
attributes["mem0.agent_id"] = str(kwargs[id_type])
|
41
|
+
elif id_type == "run_id":
|
42
|
+
attributes["mem0.run_id"] = str(kwargs[id_type])
|
43
|
+
|
44
|
+
# Extract metadata
|
45
|
+
if "metadata" in kwargs:
|
46
|
+
metadata = kwargs["metadata"]
|
47
|
+
if isinstance(metadata, dict):
|
48
|
+
for key, value in metadata.items():
|
49
|
+
attributes[f"mem0.metadata.{key}"] = str(value)
|
50
|
+
|
51
|
+
return attributes
|
52
|
+
|
53
|
+
|
54
|
+
def _extract_memory_response_attributes(return_value: Any) -> AttributeMap:
|
55
|
+
"""Extract attributes from memory operation response.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
return_value: The response from the memory operation
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Dictionary of extracted response attributes
|
62
|
+
"""
|
63
|
+
attributes = {}
|
64
|
+
|
65
|
+
if return_value:
|
66
|
+
if isinstance(return_value, dict):
|
67
|
+
# Check if this is an update/delete response (simple message format)
|
68
|
+
if "message" in return_value and len(return_value) == 1:
|
69
|
+
# Handle update/delete operation response
|
70
|
+
attributes["mem0.operation.message"] = return_value["message"]
|
71
|
+
return attributes
|
72
|
+
|
73
|
+
# Check if this is a single memory object (like from get method)
|
74
|
+
if "id" in return_value and "memory" in return_value and "results" not in return_value:
|
75
|
+
# Handle single memory object
|
76
|
+
attributes["mem0.memory_id"] = return_value["id"]
|
77
|
+
attributes["mem0.memory.0.id"] = return_value["id"]
|
78
|
+
attributes["mem0.memory.0.content"] = return_value["memory"]
|
79
|
+
attributes["mem0.results_count"] = 1
|
80
|
+
|
81
|
+
# Extract hash
|
82
|
+
if "hash" in return_value:
|
83
|
+
attributes["mem0.memory.0.hash"] = return_value["hash"]
|
84
|
+
|
85
|
+
# Extract score (might be None for get operations)
|
86
|
+
if "score" in return_value and return_value["score"] is not None:
|
87
|
+
attributes["mem0.memory.0.score"] = str(return_value["score"])
|
88
|
+
|
89
|
+
# Extract metadata
|
90
|
+
if "metadata" in return_value and isinstance(return_value["metadata"], dict):
|
91
|
+
for key, value in return_value["metadata"].items():
|
92
|
+
attributes[f"mem0.memory.0.metadata.{key}"] = str(value)
|
93
|
+
|
94
|
+
# Extract timestamps
|
95
|
+
if "created_at" in return_value:
|
96
|
+
attributes["mem0.memory.0.created_at"] = return_value["created_at"]
|
97
|
+
|
98
|
+
if "updated_at" in return_value and return_value["updated_at"]:
|
99
|
+
attributes["mem0.memory.0.updated_at"] = return_value["updated_at"]
|
100
|
+
|
101
|
+
# Extract user_id
|
102
|
+
if "user_id" in return_value:
|
103
|
+
attributes["mem0.memory.0.user_id"] = return_value["user_id"]
|
104
|
+
attributes["mem0.user_ids"] = return_value["user_id"]
|
105
|
+
|
106
|
+
return attributes
|
107
|
+
|
108
|
+
# Extract status if present
|
109
|
+
if "status" in return_value:
|
110
|
+
attributes["mem0.status"] = str(return_value["status"])
|
111
|
+
|
112
|
+
# Extract results array - this is the main structure from mem0 (add/search operations)
|
113
|
+
if "results" in return_value and isinstance(return_value["results"], list):
|
114
|
+
results = return_value["results"]
|
115
|
+
attributes["mem0.results_count"] = len(results)
|
116
|
+
|
117
|
+
# Extract event types
|
118
|
+
event_types = set()
|
119
|
+
memory_ids = []
|
120
|
+
memory_contents = []
|
121
|
+
scores = []
|
122
|
+
user_ids = set()
|
123
|
+
|
124
|
+
for i, result in enumerate(results):
|
125
|
+
if isinstance(result, dict):
|
126
|
+
# Extract event type
|
127
|
+
if "event" in result:
|
128
|
+
event_types.add(result["event"])
|
129
|
+
|
130
|
+
# Extract memory ID
|
131
|
+
if "id" in result:
|
132
|
+
memory_ids.append(result["id"])
|
133
|
+
# Set individual memory ID attributes
|
134
|
+
attributes[f"mem0.memory.{i}.id"] = result["id"]
|
135
|
+
|
136
|
+
# Extract memory content
|
137
|
+
if "memory" in result:
|
138
|
+
memory_contents.append(result["memory"])
|
139
|
+
# Set individual memory content attributes
|
140
|
+
attributes[f"mem0.memory.{i}.content"] = result["memory"]
|
141
|
+
|
142
|
+
# Extract event for individual result
|
143
|
+
if "event" in result:
|
144
|
+
attributes[f"mem0.memory.{i}.event"] = result["event"]
|
145
|
+
|
146
|
+
# Extract hash
|
147
|
+
if "hash" in result:
|
148
|
+
attributes[f"mem0.memory.{i}.hash"] = result["hash"]
|
149
|
+
|
150
|
+
# Extract score (for search results)
|
151
|
+
if "score" in result:
|
152
|
+
scores.append(result["score"])
|
153
|
+
attributes[f"mem0.memory.{i}.score"] = str(result["score"])
|
154
|
+
|
155
|
+
# Extract metadata
|
156
|
+
if "metadata" in result and isinstance(result["metadata"], dict):
|
157
|
+
for key, value in result["metadata"].items():
|
158
|
+
attributes[f"mem0.memory.{i}.metadata.{key}"] = str(value)
|
159
|
+
|
160
|
+
# Extract timestamps
|
161
|
+
if "created_at" in result:
|
162
|
+
attributes[f"mem0.memory.{i}.created_at"] = result["created_at"]
|
163
|
+
|
164
|
+
if "updated_at" in result and result["updated_at"]:
|
165
|
+
attributes[f"mem0.memory.{i}.updated_at"] = result["updated_at"]
|
166
|
+
|
167
|
+
# Extract user_id
|
168
|
+
if "user_id" in result:
|
169
|
+
user_ids.add(result["user_id"])
|
170
|
+
attributes[f"mem0.memory.{i}.user_id"] = result["user_id"]
|
171
|
+
|
172
|
+
# Set aggregated attributes
|
173
|
+
if event_types:
|
174
|
+
attributes["mem0.event_types"] = ",".join(event_types)
|
175
|
+
|
176
|
+
if memory_ids:
|
177
|
+
# Set primary memory ID (first one) as the main memory ID
|
178
|
+
attributes["mem0.memory_id"] = memory_ids[0]
|
179
|
+
# Set all memory IDs as a comma-separated list
|
180
|
+
attributes["mem0.memory.ids"] = ",".join(memory_ids)
|
181
|
+
|
182
|
+
if memory_contents:
|
183
|
+
# Set all memory contents as a combined attribute
|
184
|
+
attributes["mem0.memory.contents"] = " | ".join(memory_contents)
|
185
|
+
|
186
|
+
if scores:
|
187
|
+
# Set average and max scores for search results
|
188
|
+
attributes["mem0.search.avg_score"] = str(sum(scores) / len(scores))
|
189
|
+
attributes["mem0.search.max_score"] = str(max(scores))
|
190
|
+
attributes["mem0.search.min_score"] = str(min(scores))
|
191
|
+
|
192
|
+
if user_ids:
|
193
|
+
# Set user IDs (typically should be the same user for all results)
|
194
|
+
attributes["mem0.user_ids"] = ",".join(user_ids)
|
195
|
+
|
196
|
+
# Extract relations count if present (for backward compatibility)
|
197
|
+
if "relations" in return_value:
|
198
|
+
attributes["mem0.relations_count"] = len(return_value["relations"])
|
199
|
+
|
200
|
+
elif isinstance(return_value, list):
|
201
|
+
# For operations that return lists directly (like search, get_all)
|
202
|
+
attributes["mem0.results_count"] = len(return_value)
|
203
|
+
|
204
|
+
# If it's a list of memory objects, extract similar attributes
|
205
|
+
for i, item in enumerate(return_value):
|
206
|
+
if isinstance(item, dict):
|
207
|
+
if "id" in item:
|
208
|
+
attributes[f"mem0.memory.{i}.id"] = item["id"]
|
209
|
+
if "memory" in item:
|
210
|
+
attributes[f"mem0.memory.{i}.content"] = item["memory"]
|
211
|
+
if "event" in item:
|
212
|
+
attributes[f"mem0.memory.{i}.event"] = item["event"]
|
213
|
+
if "hash" in item:
|
214
|
+
attributes[f"mem0.memory.{i}.hash"] = item["hash"]
|
215
|
+
if "score" in item:
|
216
|
+
attributes[f"mem0.memory.{i}.score"] = str(item["score"])
|
217
|
+
if "user_id" in item:
|
218
|
+
attributes[f"mem0.memory.{i}.user_id"] = item["user_id"]
|
219
|
+
|
220
|
+
return attributes
|
221
|
+
|
222
|
+
|
223
|
+
def create_mem0_wrapper(operation_name: str, attribute_extractor):
|
224
|
+
"""Create a wrapper function for Mem0 operations that ensures proper span hierarchy.
|
225
|
+
|
226
|
+
This function creates wrappers that explicitly use the current context to ensure
|
227
|
+
mem0 spans are properly nested within the current AgentOps session or OpenAI spans.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
operation_name: Name of the mem0 operation (add, search, etc.)
|
231
|
+
attribute_extractor: Function to extract attributes for this operation
|
232
|
+
|
233
|
+
Returns:
|
234
|
+
A wrapper function that creates properly nested spans
|
235
|
+
"""
|
236
|
+
|
237
|
+
def wrapper(tracer):
|
238
|
+
def actual_wrapper(wrapped, instance, args, kwargs):
|
239
|
+
# Skip instrumentation if suppressed
|
240
|
+
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
241
|
+
|
242
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
243
|
+
return wrapped(*args, **kwargs)
|
244
|
+
|
245
|
+
# Get current context to ensure proper parent-child relationship
|
246
|
+
current_context = context_api.get_current()
|
247
|
+
span = tracer.start_span(
|
248
|
+
f"mem0.memory.{operation_name}",
|
249
|
+
context=current_context,
|
250
|
+
kind=SpanKind.CLIENT,
|
251
|
+
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
252
|
+
)
|
253
|
+
|
254
|
+
return_value = None
|
255
|
+
try:
|
256
|
+
# Add the input attributes to the span before execution
|
257
|
+
attributes = attribute_extractor(args=args, kwargs=kwargs)
|
258
|
+
for key, value in attributes.items():
|
259
|
+
span.set_attribute(key, value)
|
260
|
+
|
261
|
+
return_value = wrapped(*args, **kwargs)
|
262
|
+
# Add the output attributes to the span after execution
|
263
|
+
attributes = attribute_extractor(return_value=return_value)
|
264
|
+
for key, value in attributes.items():
|
265
|
+
span.set_attribute(key, value)
|
266
|
+
|
267
|
+
span.set_status(Status(StatusCode.OK))
|
268
|
+
except Exception as e:
|
269
|
+
# Add everything we have in the case of an error
|
270
|
+
attributes = attribute_extractor(args=args, kwargs=kwargs, return_value=return_value)
|
271
|
+
for key, value in attributes.items():
|
272
|
+
span.set_attribute(key, value)
|
273
|
+
|
274
|
+
span.record_exception(e)
|
275
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
276
|
+
raise
|
277
|
+
finally:
|
278
|
+
span.end()
|
279
|
+
|
280
|
+
return return_value
|
281
|
+
|
282
|
+
return actual_wrapper
|
283
|
+
|
284
|
+
return wrapper
|
285
|
+
|
286
|
+
|
287
|
+
def create_async_mem0_wrapper(operation_name: str, attribute_extractor):
|
288
|
+
"""Create an async wrapper function for Mem0 operations that ensures proper span hierarchy.
|
289
|
+
|
290
|
+
This function creates async wrappers that explicitly use the current context to ensure
|
291
|
+
mem0 spans are properly nested within the current AgentOps session or OpenAI spans.
|
292
|
+
|
293
|
+
Args:
|
294
|
+
operation_name: Name of the mem0 operation (add, search, etc.)
|
295
|
+
attribute_extractor: Function to extract attributes for this operation
|
296
|
+
|
297
|
+
Returns:
|
298
|
+
An async wrapper function that creates properly nested spans
|
299
|
+
"""
|
300
|
+
|
301
|
+
def wrapper(tracer):
|
302
|
+
def actual_wrapper(wrapped, instance, args, kwargs):
|
303
|
+
# Skip instrumentation if suppressed
|
304
|
+
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
305
|
+
|
306
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
307
|
+
return wrapped(*args, **kwargs)
|
308
|
+
|
309
|
+
async def async_wrapper():
|
310
|
+
# Get current context to ensure proper parent-child relationship
|
311
|
+
current_context = context_api.get_current()
|
312
|
+
span = tracer.start_span(
|
313
|
+
f"mem0.AsyncMemory.{operation_name}",
|
314
|
+
context=current_context,
|
315
|
+
kind=SpanKind.CLIENT,
|
316
|
+
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
317
|
+
)
|
318
|
+
|
319
|
+
return_value = None
|
320
|
+
try:
|
321
|
+
# Add the input attributes to the span before execution
|
322
|
+
attributes = attribute_extractor(args=args, kwargs=kwargs)
|
323
|
+
for key, value in attributes.items():
|
324
|
+
span.set_attribute(key, value)
|
325
|
+
|
326
|
+
return_value = await wrapped(*args, **kwargs)
|
327
|
+
|
328
|
+
# Add the output attributes to the span after execution
|
329
|
+
attributes = attribute_extractor(return_value=return_value)
|
330
|
+
for key, value in attributes.items():
|
331
|
+
span.set_attribute(key, value)
|
332
|
+
|
333
|
+
span.set_status(Status(StatusCode.OK))
|
334
|
+
except Exception as e:
|
335
|
+
# Add everything we have in the case of an error
|
336
|
+
attributes = attribute_extractor(args=args, kwargs=kwargs, return_value=return_value)
|
337
|
+
for key, value in attributes.items():
|
338
|
+
span.set_attribute(key, value)
|
339
|
+
|
340
|
+
span.record_exception(e)
|
341
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
342
|
+
raise
|
343
|
+
finally:
|
344
|
+
span.end()
|
345
|
+
|
346
|
+
return return_value
|
347
|
+
|
348
|
+
return async_wrapper()
|
349
|
+
|
350
|
+
return actual_wrapper
|
351
|
+
|
352
|
+
return wrapper
|
353
|
+
|
354
|
+
|
355
|
+
def create_universal_mem0_wrapper(operation_name: str, attribute_extractor):
|
356
|
+
"""Create a universal wrapper that handles both sync and async methods.
|
357
|
+
|
358
|
+
This function detects whether the wrapped method is async and applies the appropriate wrapper.
|
359
|
+
"""
|
360
|
+
|
361
|
+
def wrapper(tracer):
|
362
|
+
def actual_wrapper(wrapped, instance, args, kwargs):
|
363
|
+
import asyncio
|
364
|
+
|
365
|
+
# Check if the wrapped function is async
|
366
|
+
if asyncio.iscoroutinefunction(wrapped):
|
367
|
+
# Use async wrapper
|
368
|
+
async_wrapper_func = create_async_mem0_wrapper(operation_name, attribute_extractor)
|
369
|
+
return async_wrapper_func(tracer)(wrapped, instance, args, kwargs)
|
370
|
+
else:
|
371
|
+
# Use sync wrapper
|
372
|
+
sync_wrapper_func = create_mem0_wrapper(operation_name, attribute_extractor)
|
373
|
+
return sync_wrapper_func(tracer)(wrapped, instance, args, kwargs)
|
374
|
+
|
375
|
+
return actual_wrapper
|
376
|
+
|
377
|
+
return wrapper
|
@@ -0,0 +1,270 @@
|
|
1
|
+
from typing import Dict, Any
|
2
|
+
from wrapt import wrap_function_wrapper
|
3
|
+
from opentelemetry.metrics import Meter
|
4
|
+
|
5
|
+
from agentops.instrumentation.common import CommonInstrumentor, StandardMetrics, InstrumentorConfig
|
6
|
+
from agentops.logging import logger
|
7
|
+
|
8
|
+
# Import from refactored structure
|
9
|
+
from .memory import (
|
10
|
+
mem0_add_wrapper,
|
11
|
+
mem0_search_wrapper,
|
12
|
+
mem0_get_all_wrapper,
|
13
|
+
mem0_delete_wrapper,
|
14
|
+
mem0_update_wrapper,
|
15
|
+
mem0_get_wrapper,
|
16
|
+
mem0_delete_all_wrapper,
|
17
|
+
mem0_history_wrapper,
|
18
|
+
)
|
19
|
+
|
20
|
+
# Library info for tracer/meter
|
21
|
+
LIBRARY_NAME = "agentops.instrumentation.mem0"
|
22
|
+
LIBRARY_VERSION = "0.1.0"
|
23
|
+
|
24
|
+
# Methods to wrap for instrumentation using specialized wrappers
|
25
|
+
WRAPPER_METHODS = [
|
26
|
+
# Sync Memory class methods
|
27
|
+
{
|
28
|
+
"package": "mem0.memory.main",
|
29
|
+
"class_method": "Memory.add",
|
30
|
+
"wrapper": mem0_add_wrapper,
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"package": "mem0.memory.main",
|
34
|
+
"class_method": "Memory.search",
|
35
|
+
"wrapper": mem0_search_wrapper,
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"package": "mem0.memory.main",
|
39
|
+
"class_method": "Memory.get_all",
|
40
|
+
"wrapper": mem0_get_all_wrapper,
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"package": "mem0.memory.main",
|
44
|
+
"class_method": "Memory.get",
|
45
|
+
"wrapper": mem0_get_wrapper,
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"package": "mem0.memory.main",
|
49
|
+
"class_method": "Memory.delete",
|
50
|
+
"wrapper": mem0_delete_wrapper,
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"package": "mem0.memory.main",
|
54
|
+
"class_method": "Memory.delete_all",
|
55
|
+
"wrapper": mem0_delete_all_wrapper,
|
56
|
+
},
|
57
|
+
{
|
58
|
+
"package": "mem0.memory.main",
|
59
|
+
"class_method": "Memory.update",
|
60
|
+
"wrapper": mem0_update_wrapper,
|
61
|
+
},
|
62
|
+
{
|
63
|
+
"package": "mem0.memory.main",
|
64
|
+
"class_method": "Memory.history",
|
65
|
+
"wrapper": mem0_history_wrapper,
|
66
|
+
},
|
67
|
+
# MemoryClient class methods
|
68
|
+
{
|
69
|
+
"package": "mem0.client.main",
|
70
|
+
"class_method": "MemoryClient.add",
|
71
|
+
"wrapper": mem0_add_wrapper,
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"package": "mem0.client.main",
|
75
|
+
"class_method": "MemoryClient.search",
|
76
|
+
"wrapper": mem0_search_wrapper,
|
77
|
+
},
|
78
|
+
{
|
79
|
+
"package": "mem0.client.main",
|
80
|
+
"class_method": "MemoryClient.get_all",
|
81
|
+
"wrapper": mem0_get_all_wrapper,
|
82
|
+
},
|
83
|
+
{
|
84
|
+
"package": "mem0.client.main",
|
85
|
+
"class_method": "MemoryClient.get",
|
86
|
+
"wrapper": mem0_get_wrapper,
|
87
|
+
},
|
88
|
+
{
|
89
|
+
"package": "mem0.client.main",
|
90
|
+
"class_method": "MemoryClient.delete",
|
91
|
+
"wrapper": mem0_delete_wrapper,
|
92
|
+
},
|
93
|
+
{
|
94
|
+
"package": "mem0.client.main",
|
95
|
+
"class_method": "MemoryClient.delete_all",
|
96
|
+
"wrapper": mem0_delete_all_wrapper,
|
97
|
+
},
|
98
|
+
{
|
99
|
+
"package": "mem0.client.main",
|
100
|
+
"class_method": "MemoryClient.update",
|
101
|
+
"wrapper": mem0_update_wrapper,
|
102
|
+
},
|
103
|
+
# AsyncMemoryClient class methods
|
104
|
+
{
|
105
|
+
"package": "mem0.client.main",
|
106
|
+
"class_method": "AsyncMemoryClient.add",
|
107
|
+
"wrapper": mem0_add_wrapper,
|
108
|
+
},
|
109
|
+
{
|
110
|
+
"package": "mem0.client.main",
|
111
|
+
"class_method": "AsyncMemoryClient.search",
|
112
|
+
"wrapper": mem0_search_wrapper,
|
113
|
+
},
|
114
|
+
{
|
115
|
+
"package": "mem0.client.main",
|
116
|
+
"class_method": "AsyncMemoryClient.get_all",
|
117
|
+
"wrapper": mem0_get_all_wrapper,
|
118
|
+
},
|
119
|
+
{
|
120
|
+
"package": "mem0.client.main",
|
121
|
+
"class_method": "AsyncMemoryClient.get",
|
122
|
+
"wrapper": mem0_get_wrapper,
|
123
|
+
},
|
124
|
+
{
|
125
|
+
"package": "mem0.client.main",
|
126
|
+
"class_method": "AsyncMemoryClient.delete",
|
127
|
+
"wrapper": mem0_delete_wrapper,
|
128
|
+
},
|
129
|
+
{
|
130
|
+
"package": "mem0.client.main",
|
131
|
+
"class_method": "AsyncMemoryClient.delete_all",
|
132
|
+
"wrapper": mem0_delete_all_wrapper,
|
133
|
+
},
|
134
|
+
{
|
135
|
+
"package": "mem0.client.main",
|
136
|
+
"class_method": "AsyncMemoryClient.update",
|
137
|
+
"wrapper": mem0_update_wrapper,
|
138
|
+
},
|
139
|
+
# AsyncMemory class methods
|
140
|
+
{
|
141
|
+
"package": "mem0.memory.main",
|
142
|
+
"class_method": "AsyncMemory.add",
|
143
|
+
"wrapper": mem0_add_wrapper,
|
144
|
+
},
|
145
|
+
{
|
146
|
+
"package": "mem0.memory.main",
|
147
|
+
"class_method": "AsyncMemory.search",
|
148
|
+
"wrapper": mem0_search_wrapper,
|
149
|
+
},
|
150
|
+
{
|
151
|
+
"package": "mem0.memory.main",
|
152
|
+
"class_method": "AsyncMemory.get_all",
|
153
|
+
"wrapper": mem0_get_all_wrapper,
|
154
|
+
},
|
155
|
+
{
|
156
|
+
"package": "mem0.memory.main",
|
157
|
+
"class_method": "AsyncMemory.get",
|
158
|
+
"wrapper": mem0_get_wrapper,
|
159
|
+
},
|
160
|
+
{
|
161
|
+
"package": "mem0.memory.main",
|
162
|
+
"class_method": "AsyncMemory.delete",
|
163
|
+
"wrapper": mem0_delete_wrapper,
|
164
|
+
},
|
165
|
+
{
|
166
|
+
"package": "mem0.memory.main",
|
167
|
+
"class_method": "AsyncMemory.delete_all",
|
168
|
+
"wrapper": mem0_delete_all_wrapper,
|
169
|
+
},
|
170
|
+
{
|
171
|
+
"package": "mem0.memory.main",
|
172
|
+
"class_method": "AsyncMemory.update",
|
173
|
+
"wrapper": mem0_update_wrapper,
|
174
|
+
},
|
175
|
+
{
|
176
|
+
"package": "mem0.memory.main",
|
177
|
+
"class_method": "AsyncMemory.history",
|
178
|
+
"wrapper": mem0_history_wrapper,
|
179
|
+
},
|
180
|
+
]
|
181
|
+
|
182
|
+
|
183
|
+
class Mem0Instrumentor(CommonInstrumentor):
|
184
|
+
"""An instrumentor for Mem0's client library.
|
185
|
+
|
186
|
+
This class provides instrumentation for Mem0's memory operations by wrapping key methods
|
187
|
+
in the Memory, AsyncMemory, MemoryClient, and AsyncMemoryClient classes. It captures
|
188
|
+
telemetry data for memory operations including add, search, get, delete, delete_all,
|
189
|
+
update, and history operations.
|
190
|
+
|
191
|
+
The instrumentor gracefully handles missing optional dependencies - if a provider's
|
192
|
+
package is not installed, it will be skipped without causing errors.
|
193
|
+
|
194
|
+
It captures metrics including operation duration, memory counts, and exceptions.
|
195
|
+
"""
|
196
|
+
|
197
|
+
def __init__(self):
|
198
|
+
"""Initialize the Mem0 instrumentor."""
|
199
|
+
config = InstrumentorConfig(
|
200
|
+
library_name=LIBRARY_NAME,
|
201
|
+
library_version=LIBRARY_VERSION,
|
202
|
+
wrapped_methods=[], # We use custom wrapping for Mem0
|
203
|
+
metrics_enabled=True,
|
204
|
+
dependencies=["mem0ai >= 0.1.10"],
|
205
|
+
)
|
206
|
+
super().__init__(config)
|
207
|
+
|
208
|
+
def _create_metrics(self, meter: Meter) -> Dict[str, Any]:
|
209
|
+
"""Create metrics for Mem0 operations.
|
210
|
+
|
211
|
+
Args:
|
212
|
+
meter: The OpenTelemetry meter to use for creating metrics.
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
Dictionary containing the created metrics.
|
216
|
+
"""
|
217
|
+
metrics = StandardMetrics.create_standard_metrics(meter)
|
218
|
+
|
219
|
+
# Add Mem0-specific memory count metric
|
220
|
+
metrics["memory_count_histogram"] = meter.create_histogram(
|
221
|
+
name="mem0.memory.count",
|
222
|
+
unit="memory",
|
223
|
+
description="Number of memories processed in Mem0 operations",
|
224
|
+
)
|
225
|
+
|
226
|
+
return metrics
|
227
|
+
|
228
|
+
def _custom_wrap(self, **kwargs):
|
229
|
+
"""Perform custom wrapping for Mem0 methods.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
**kwargs: Configuration options for instrumentation.
|
233
|
+
"""
|
234
|
+
logger.debug("Starting Mem0 instrumentation...")
|
235
|
+
|
236
|
+
# Use specialized wrappers that ensure proper context hierarchy
|
237
|
+
for method_config in WRAPPER_METHODS:
|
238
|
+
try:
|
239
|
+
package = method_config["package"]
|
240
|
+
class_method = method_config["class_method"]
|
241
|
+
wrapper_func = method_config["wrapper"]
|
242
|
+
wrap_function_wrapper(package, class_method, wrapper_func(self._tracer))
|
243
|
+
except (AttributeError, ModuleNotFoundError) as e:
|
244
|
+
# Use debug level for missing optional packages instead of error
|
245
|
+
# since LLM providers are optional dependencies
|
246
|
+
logger.debug(f"Skipping {package}.{class_method} - package not installed: {e}")
|
247
|
+
except Exception as e:
|
248
|
+
# Log unexpected errors as warnings
|
249
|
+
logger.warning(f"Unexpected error wrapping {package}.{class_method}: {e}")
|
250
|
+
|
251
|
+
logger.info("Mem0 instrumentation enabled")
|
252
|
+
|
253
|
+
def _custom_unwrap(self, **kwargs):
|
254
|
+
"""Remove custom wrapping for Mem0 methods.
|
255
|
+
|
256
|
+
Args:
|
257
|
+
**kwargs: Configuration options for uninstrumentation.
|
258
|
+
"""
|
259
|
+
# Unwrap specialized methods
|
260
|
+
from opentelemetry.instrumentation.utils import unwrap
|
261
|
+
|
262
|
+
for method_config in WRAPPER_METHODS:
|
263
|
+
try:
|
264
|
+
package = method_config["package"]
|
265
|
+
class_method = method_config["class_method"]
|
266
|
+
unwrap(package, class_method)
|
267
|
+
except Exception as e:
|
268
|
+
logger.debug(f"Failed to unwrap {package}.{class_method}: {e}")
|
269
|
+
|
270
|
+
logger.info("Mem0 instrumentation disabled")
|