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,471 @@
1
+ from typing import Any, List
2
+
3
+ from opentelemetry.context import attach, detach
4
+ from opentelemetry.trace import set_span_in_context
5
+ from opentelemetry.sdk.trace import Span as SDKSpan
6
+
7
+ from agentops.logging import logger
8
+ from agentops.sdk.core import tracer
9
+ from agentops.semconv import AgentOpsSpanKindValues, SpanAttributes
10
+
11
+ from dspy.utils.callback import BaseCallback
12
+ import dspy
13
+
14
+ DSPY_INPUT = "dspy.input.{key}"
15
+ DSPY_OUTPUT = "dspy.output.{key}"
16
+ DSPY_ATTRIBUTE = "dspy.attribute.{key}"
17
+ DSPY_EVALUATE = "evaluate"
18
+
19
+
20
+ class DSPyCallbackHandler(BaseCallback):
21
+ """
22
+ AgentOps callback handler for DSPy.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ api_key: str | None = None,
28
+ tags: List[str] | None = None,
29
+ cache: bool = True,
30
+ auto_session: bool = True,
31
+ ):
32
+ self.active_spans: dict[str, SDKSpan] = {}
33
+ self.api_key = api_key
34
+ self.tags = tags or []
35
+ self.session_span = None
36
+ self.session_token = None
37
+ self.context_tokens = {}
38
+ self.token_counts = {}
39
+
40
+ if auto_session:
41
+ self._initialize_agentops()
42
+
43
+ # Configure caching
44
+ dspy.configure_cache(
45
+ enable_disk_cache=cache,
46
+ enable_memory_cache=cache,
47
+ )
48
+
49
+ # not entirely sure if this works
50
+ def _initialize_agentops(self):
51
+ """Initialize AgentOps"""
52
+ import agentops
53
+
54
+ if not tracer.initialized:
55
+ init_kwargs = {"auto_start_session": False, "instrument_llm_calls": True, "api_key": None}
56
+
57
+ if self.api_key:
58
+ init_kwargs["api_key"] = self.api_key
59
+
60
+ agentops.init(**init_kwargs)
61
+ logger.debug("AgentOps initialized from DSPy callback handler")
62
+
63
+ if not tracer.initialized:
64
+ logger.warning("AgentOps not initialized, session span will not be created")
65
+ return
66
+
67
+ otel_tracer = tracer.get_tracer()
68
+
69
+ span_name = f"session.{AgentOpsSpanKindValues.SESSION.value}"
70
+
71
+ attributes = {
72
+ SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.SESSION.value,
73
+ "session.tags": self.tags,
74
+ "agentops.operation.name": "session",
75
+ "span.kind": AgentOpsSpanKindValues.SESSION.value,
76
+ }
77
+
78
+ # Create a root session span
79
+ self.session_span = otel_tracer.start_span(span_name, attributes=attributes)
80
+
81
+ # Attach session span to current context
82
+ self.session_token = attach(set_span_in_context(self.session_span))
83
+
84
+ logger.debug("Created trace as root span for DSPy")
85
+
86
+ def _create_span(
87
+ self,
88
+ operation_name: str,
89
+ span_kind: str,
90
+ run_id: Any = None,
91
+ attributes: dict[str, Any] | None = None,
92
+ parent_run_id: str | None = None,
93
+ inputs: dict[str, Any] | None = None,
94
+ ):
95
+ """
96
+ Create a span for the operation.
97
+
98
+ Args:
99
+ operation_name: Name of the operation
100
+ span_kind: Type of span
101
+ run_id: Unique identifier for the operation
102
+ attributes: Additional attributes for the span
103
+ parent_run_id: The run_id of the parent span if this is a child span
104
+ inputs: The DSPy input dictionary
105
+
106
+ Returns:
107
+ The created span
108
+ """
109
+ if not tracer.initialized:
110
+ logger.warning("AgentOps not initialized, spans will not be created")
111
+ return # No valid context for non-recording span
112
+
113
+ otel_tracer = tracer.get_tracer()
114
+
115
+ span_name = f"{operation_name}.{span_kind}"
116
+
117
+ if attributes is None:
118
+ logger.warning(f"No attributes recorded on span {run_id}")
119
+ attributes = {}
120
+
121
+ if inputs is None:
122
+ logger.warning(f"No inputs recorded on span {run_id}")
123
+ inputs = {}
124
+
125
+ inputs = {DSPY_INPUT.format(key=key): value for key, value in inputs.items()}
126
+
127
+ attributes = {**attributes, **inputs}
128
+ attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = span_kind
129
+ attributes["agentops.operation.name"] = operation_name
130
+
131
+ if run_id is None:
132
+ run_id = id(attributes)
133
+
134
+ # parent_span = None
135
+ if parent_run_id is not None and parent_run_id in self.active_spans:
136
+ # Get parent span from active spans
137
+ parent_span = self.active_spans[parent_run_id]
138
+ # Create context with parent span
139
+ parent_ctx = set_span_in_context(parent_span)
140
+ # Start span with parent context
141
+ span = otel_tracer.start_span(span_name, context=parent_ctx, attributes=attributes)
142
+ logger.debug(f"Start span: {span_name} with parent: {parent_run_id}")
143
+ else:
144
+ if not self.session_span:
145
+ logger.warning(f"Root session span not set. Starting {span_name} as root span.")
146
+ self.session_span = otel_tracer.start_span(span_name, attributes=attributes)
147
+ parent_ctx = set_span_in_context(self.session_span)
148
+ # Start span with session as parent context
149
+ span = otel_tracer.start_span(span_name, context=parent_ctx, attributes=attributes)
150
+ logger.debug(f"Started span: {span_name} with session as parent")
151
+
152
+ if isinstance(span, SDKSpan):
153
+ self.active_spans[run_id] = span
154
+ else:
155
+ logger.warning(f"Span type warning: generated {type(span)}")
156
+
157
+ # Store token to detach later
158
+ token = attach(set_span_in_context(span))
159
+ self.context_tokens[run_id] = token
160
+
161
+ return span
162
+
163
+ def _end_span(
164
+ self,
165
+ run_id: str,
166
+ outputs: Any | None,
167
+ exception: Exception | None = None,
168
+ ):
169
+ """
170
+ End the span associated with the run_id.
171
+
172
+ Args:
173
+ run_id: Unique identifier for the operation
174
+ outputs: The DSPy output
175
+ exception: The DSPy exception
176
+ """
177
+ if run_id not in self.active_spans:
178
+ logger.warning(f"No span found for call {run_id}")
179
+ return
180
+
181
+ span: SDKSpan = self.active_spans.pop(run_id)
182
+ token = self.context_tokens.pop(run_id, None)
183
+
184
+ if exception:
185
+ logger.warning(f"Exception {str(exception)}")
186
+ span.add_event(
187
+ name="exception",
188
+ attributes={"exception.type": exception.__class__.__name__, "exception.message": str(exception)},
189
+ )
190
+
191
+ if isinstance(outputs, dict):
192
+ outputs = {DSPY_OUTPUT.format(key=key): value for key, value in outputs.items()}
193
+ span.set_attributes(outputs)
194
+
195
+ if token is not None:
196
+ detach(token)
197
+
198
+ try:
199
+ span.end()
200
+ logger.debug(f"Ended span: {span.update_name('test')}") # ugh
201
+ except Exception as e:
202
+ logger.warning(f"Error ending span: {e}")
203
+
204
+ # Clean up token counts if present
205
+ if run_id in self.token_counts:
206
+ del self.token_counts[run_id]
207
+
208
+ # does this type check break on things?
209
+ def _get_span_kind(self, instance: dspy.Module) -> str:
210
+ if isinstance(instance, (dspy.ReAct, dspy.ProgramOfThought)):
211
+ return AgentOpsSpanKindValues.AGENT.value
212
+ elif isinstance(instance, (dspy.ChainOfThought, dspy.MultiChainComparison, dspy.BestOfN, dspy.Refine)):
213
+ return AgentOpsSpanKindValues.WORKFLOW.value
214
+ elif isinstance(instance, dspy.Predict):
215
+ return AgentOpsSpanKindValues.CHAIN.value
216
+ elif isinstance(instance, dspy.LM):
217
+ return AgentOpsSpanKindValues.LLM.value
218
+ else:
219
+ logger.warning(f"Instance's span type not found: {instance}")
220
+ return AgentOpsSpanKindValues.UNKNOWN.value
221
+
222
+ def on_module_start(
223
+ self,
224
+ call_id: str,
225
+ instance: Any,
226
+ inputs: dict[str, Any],
227
+ ):
228
+ """A handler triggered when forward() method of a module (subclass of dspy.Module) is called.
229
+
230
+ Args:
231
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
232
+ instance: The Module instance.
233
+ inputs: The inputs to the module's forward() method. Each arguments is stored as
234
+ a key-value pair in a dictionary.
235
+ """
236
+ span_kind = self._get_span_kind(instance)
237
+ attributes = {"instance": instance.__dict__}
238
+
239
+ self._create_span(
240
+ operation_name=f"{instance.__class__.__name__}",
241
+ span_kind=span_kind,
242
+ run_id=call_id,
243
+ inputs=inputs,
244
+ attributes=attributes,
245
+ )
246
+
247
+ def on_module_end(
248
+ self,
249
+ call_id: str,
250
+ outputs: Any | None,
251
+ exception: Exception | None = None,
252
+ ):
253
+ # not collecting?
254
+ # why was it collecting on the other one?
255
+ """A handler triggered after forward() method of a module (subclass of dspy.Module) is executed.
256
+
257
+ Args:
258
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
259
+ outputs: The outputs of the module's forward() method. If the method is interrupted by
260
+ an exception, this will be None.
261
+ exception: If an exception is raised during the execution, it will be stored here.
262
+ """
263
+ if isinstance(outputs, dspy.Prediction):
264
+ outputs = outputs.toDict()
265
+
266
+ self._end_span(call_id, outputs, exception)
267
+
268
+ def on_lm_start(
269
+ self,
270
+ call_id: str,
271
+ instance: Any,
272
+ inputs: dict[str, Any],
273
+ ):
274
+ """A handler triggered when __call__ method of dspy.LM instance is called.
275
+
276
+ Args:
277
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
278
+ instance: The LM instance.
279
+ inputs: The inputs to the LM's __call__ method. Each arguments is stored as
280
+ a key-value pair in a dictionary.
281
+ """
282
+ span_kind = self._get_span_kind(instance)
283
+ attributes = {"instance": instance.__dict__}
284
+
285
+ self._create_span(
286
+ operation_name=f"{instance.__class__.__name__}",
287
+ span_kind=span_kind,
288
+ run_id=call_id,
289
+ inputs=inputs,
290
+ attributes=attributes,
291
+ )
292
+
293
+ def on_lm_end(
294
+ self,
295
+ call_id: str,
296
+ outputs: dict[str, Any] | None,
297
+ exception: Exception | None = None,
298
+ ):
299
+ """A handler triggered after __call__ method of dspy.LM instance is executed.
300
+
301
+ Args:
302
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
303
+ outputs: The outputs of the LM's __call__ method. If the method is interrupted by
304
+ an exception, this will be None.
305
+ exception: If an exception is raised during the execution, it will be stored here.
306
+ """
307
+ self._end_span(call_id, outputs, exception)
308
+
309
+ def on_adapter_format_start(
310
+ self,
311
+ call_id: str,
312
+ instance: Any,
313
+ inputs: dict[str, Any],
314
+ ):
315
+ """A handler triggered when format() method of an adapter (subclass of dspy.Adapter) is called.
316
+
317
+ Args:
318
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
319
+ instance: The Adapter instance.
320
+ inputs: The inputs to the Adapter's format() method. Each arguments is stored as
321
+ a key-value pair in a dictionary.
322
+ """
323
+ span_kind = AgentOpsSpanKindValues.OPERATION.value
324
+ attributes = {"instance": instance.__dict__}
325
+
326
+ self._create_span(
327
+ operation_name=f"{instance.__class__.__name__}",
328
+ span_kind=span_kind,
329
+ run_id=call_id,
330
+ inputs=inputs,
331
+ attributes=attributes,
332
+ )
333
+
334
+ def on_adapter_format_end(
335
+ self,
336
+ call_id: str,
337
+ outputs: dict[str, Any] | None,
338
+ exception: Exception | None = None,
339
+ ):
340
+ """A handler triggered after format() method of an adapter (subclass of dspy.Adapter) is called..
341
+
342
+ Args:
343
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
344
+ outputs: The outputs of the Adapter's format() method. If the method is interrupted
345
+ by an exception, this will be None.
346
+ exception: If an exception is raised during the execution, it will be stored here.
347
+ """
348
+ self._end_span(call_id, outputs, exception)
349
+
350
+ def on_adapter_parse_start(
351
+ self,
352
+ call_id: str,
353
+ instance: Any,
354
+ inputs: dict[str, Any],
355
+ ):
356
+ """A handler triggered when parse() method of an adapter (subclass of dspy.Adapter) is called.
357
+
358
+ Args:
359
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
360
+ instance: The Adapter instance.
361
+ inputs: The inputs to the Adapter's parse() method. Each arguments is stored as
362
+ a key-value pair in a dictionary.
363
+ """
364
+ span_kind = AgentOpsSpanKindValues.OPERATION.value
365
+ attributes = {"instance": instance.__dict__}
366
+
367
+ self._create_span(
368
+ operation_name=f"{instance.__class__.__name__}",
369
+ span_kind=span_kind,
370
+ run_id=call_id,
371
+ inputs=inputs,
372
+ attributes=attributes,
373
+ )
374
+
375
+ def on_adapter_parse_end(
376
+ self,
377
+ call_id: str,
378
+ outputs: dict[str, Any] | None,
379
+ exception: Exception | None = None,
380
+ ):
381
+ """A handler triggered after parse() method of an adapter (subclass of dspy.Adapter) is called.
382
+
383
+ Args:
384
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
385
+ outputs: The outputs of the Adapter's parse() method. If the method is interrupted
386
+ by an exception, this will be None.
387
+ exception: If an exception is raised during the execution, it will be stored here.
388
+ """
389
+ self._end_span(call_id, outputs, exception)
390
+
391
+ def on_tool_start(
392
+ self,
393
+ call_id: str,
394
+ instance: Any,
395
+ inputs: dict[str, Any],
396
+ ):
397
+ """A handler triggered when a tool is called.
398
+
399
+ Args:
400
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
401
+ instance: The Tool instance.
402
+ inputs: The inputs to the Tool's __call__ method. Each arguments is stored as
403
+ a key-value pair in a dictionary.
404
+ """
405
+ span_kind = AgentOpsSpanKindValues.TOOL.value
406
+ attributes = {"instance": instance.__dict__}
407
+
408
+ self._create_span(
409
+ operation_name=f"{instance.__class__.__name__}",
410
+ span_kind=span_kind,
411
+ run_id=call_id,
412
+ inputs=inputs,
413
+ attributes=attributes,
414
+ )
415
+
416
+ def on_tool_end(
417
+ self,
418
+ call_id: str,
419
+ outputs: dict[str, Any] | None,
420
+ exception: Exception | None = None,
421
+ ):
422
+ """A handler triggered after a tool is executed.
423
+
424
+ Args:
425
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
426
+ outputs: The outputs of the Tool's __call__ method. If the method is interrupted by
427
+ an exception, this will be None.
428
+ exception: If an exception is raised during the execution, it will be stored here.
429
+ """
430
+ self._end_span(call_id, outputs, exception)
431
+
432
+ def on_evaluate_start(
433
+ self,
434
+ call_id: str,
435
+ instance: Any,
436
+ inputs: dict[str, Any],
437
+ ):
438
+ """A handler triggered when evaluation is started.
439
+
440
+ Args:
441
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
442
+ instance: The Evaluate instance.
443
+ inputs: The inputs to the Evaluate's __call__ method. Each arguments is stored as
444
+ a key-value pair in a dictionary.
445
+ """
446
+ span_kind = DSPY_EVALUATE
447
+ attributes = {"instance": instance.__dict__}
448
+
449
+ self._create_span(
450
+ operation_name=f"{instance.__class__.__name__}",
451
+ span_kind=span_kind,
452
+ run_id=call_id,
453
+ inputs=inputs,
454
+ attributes=attributes,
455
+ )
456
+
457
+ def on_evaluate_end(
458
+ self,
459
+ call_id: str,
460
+ outputs: Any | None,
461
+ exception: Exception | None = None,
462
+ ):
463
+ """A handler triggered after evaluation is executed.
464
+
465
+ Args:
466
+ call_id: A unique identifier for the call. Can be used to connect start/end handlers.
467
+ outputs: The outputs of the Evaluate's __call__ method. If the method is interrupted by
468
+ an exception, this will be None.
469
+ exception: If an exception is raised during the execution, it will be stored here.
470
+ """
471
+ self._end_span(call_id, outputs, exception)
@@ -0,0 +1,59 @@
1
+ # AgentOps LangChain Callback Handler
2
+
3
+ This callback handler enables seamless integration between LangChain and AgentOps for tracing and monitoring LLM applications.
4
+
5
+ ## Features
6
+
7
+ - **Complete Coverage**: Supports all LangChain callback methods
8
+ - **Session Tracking**: Creates a session span that serves as the root for all operations
9
+ - **Proper Hierarchy**: Maintains parent-child relationships between operations
10
+ - **Complete Instrumentation**: Tracks LLMs, chains, tools, and agent actions
11
+ - **Error Tracking**: Records errors from LLMs, chains, and tools
12
+ - **Streaming Support**: Handles token streaming for real-time insights
13
+ - **Attribute Capture**: Records inputs, outputs, and metadata for all operations
14
+ - **Error Resilience**: Handles errors gracefully to ensure spans are always properly closed
15
+
16
+ ## Supported Callbacks
17
+
18
+ The handler implements all LangChain callback methods:
19
+
20
+ | Method | Description | Span Kind | Attributes |
21
+ |--------|-------------|-----------|------------|
22
+ | `on_llm_start` | Start of an LLM call | `llm` | Model, prompts, parameters |
23
+ | `on_llm_end` | End of an LLM call | `llm` | Completions, token usage |
24
+ | `on_llm_new_token` | Streaming token received | N/A | Token count, last token |
25
+ | `on_llm_error` | LLM call error | `llm` | Error details |
26
+ | `on_chat_model_start` | Start of a chat model call | `llm` | Model, messages, parameters |
27
+ | `on_chain_start` | Start of a chain | `task` | Chain type, inputs |
28
+ | `on_chain_end` | End of a chain | `task` | Outputs |
29
+ | `on_chain_error` | Chain execution error | `task` | Error details |
30
+ | `on_tool_start` | Start of a tool call | `tool` | Tool name, input |
31
+ | `on_tool_end` | End of a tool call | `tool` | Output |
32
+ | `on_tool_error` | Tool execution error | `tool` | Error details |
33
+ | `on_agent_action` | Agent taking an action | `agent` | Tool, input, log |
34
+ | `on_agent_finish` | Agent completing a task | `agent` | Output, log |
35
+ | `on_text` | Arbitrary text event | `text` | Text content |
36
+
37
+ All spans have appropriate attributes such as:
38
+ - Model information for LLM spans
39
+ - Input/output for all operations
40
+ - Tool names and types
41
+ - Chain types and configurations
42
+ - Error details for failed operations
43
+
44
+ ## Troubleshooting
45
+
46
+ If you're not seeing data in AgentOps:
47
+
48
+ 1. Check that your API key is correctly configured
49
+ 2. Ensure you're passing the handler to all relevant components
50
+ 3. Verify that all operations are properly ending/closing
51
+
52
+ ## How It Works
53
+
54
+ The callback handler:
55
+ 1. Creates a session span when initialized
56
+ 2. Intercepts LangChain callbacks for various operations
57
+ 3. Creates appropriate spans with meaningful attributes
58
+ 4. Maintains proper parent-child relationships
59
+ 5. Automatically cleans up and ends spans when operations complete
@@ -0,0 +1,15 @@
1
+ """
2
+ LangChain integration for AgentOps.
3
+
4
+ This module provides the AgentOps LangChain integration, including callbacks and utilities.
5
+ """
6
+
7
+ from agentops.integration.callbacks.langchain.callback import (
8
+ LangchainCallbackHandler,
9
+ AsyncLangchainCallbackHandler,
10
+ )
11
+
12
+ __all__ = [
13
+ "LangchainCallbackHandler",
14
+ "AsyncLangchainCallbackHandler",
15
+ ]