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,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")