mantisdk 0.1.0__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.

Potentially problematic release.


This version of mantisdk might be problematic. Click here for more details.

Files changed (190) hide show
  1. mantisdk/__init__.py +22 -0
  2. mantisdk/adapter/__init__.py +15 -0
  3. mantisdk/adapter/base.py +94 -0
  4. mantisdk/adapter/messages.py +270 -0
  5. mantisdk/adapter/triplet.py +1028 -0
  6. mantisdk/algorithm/__init__.py +39 -0
  7. mantisdk/algorithm/apo/__init__.py +5 -0
  8. mantisdk/algorithm/apo/apo.py +889 -0
  9. mantisdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. mantisdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. mantisdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. mantisdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. mantisdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. mantisdk/algorithm/base.py +162 -0
  15. mantisdk/algorithm/decorator.py +264 -0
  16. mantisdk/algorithm/fast.py +250 -0
  17. mantisdk/algorithm/gepa/__init__.py +59 -0
  18. mantisdk/algorithm/gepa/adapter.py +459 -0
  19. mantisdk/algorithm/gepa/gepa.py +364 -0
  20. mantisdk/algorithm/gepa/lib/__init__.py +18 -0
  21. mantisdk/algorithm/gepa/lib/adapters/README.md +12 -0
  22. mantisdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  23. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  24. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  25. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  26. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  27. mantisdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  28. mantisdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  29. mantisdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  30. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  31. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  32. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  33. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  34. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  35. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +266 -0
  36. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  37. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  38. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  39. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  40. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  41. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  42. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  43. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  44. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  45. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  46. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  47. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  48. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  49. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  50. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +705 -0
  51. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  52. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  53. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  54. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  55. mantisdk/algorithm/gepa/lib/api.py +375 -0
  56. mantisdk/algorithm/gepa/lib/core/__init__.py +0 -0
  57. mantisdk/algorithm/gepa/lib/core/adapter.py +180 -0
  58. mantisdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  59. mantisdk/algorithm/gepa/lib/core/engine.py +356 -0
  60. mantisdk/algorithm/gepa/lib/core/result.py +233 -0
  61. mantisdk/algorithm/gepa/lib/core/state.py +636 -0
  62. mantisdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  63. mantisdk/algorithm/gepa/lib/examples/aime.py +24 -0
  64. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  65. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  66. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  67. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  68. mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  69. mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  70. mantisdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  71. mantisdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +455 -0
  72. mantisdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  73. mantisdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  74. mantisdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +824 -0
  75. mantisdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  76. mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  77. mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  78. mantisdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  79. mantisdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  80. mantisdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  81. mantisdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  82. mantisdk/algorithm/gepa/lib/logging/logger.py +75 -0
  83. mantisdk/algorithm/gepa/lib/logging/utils.py +103 -0
  84. mantisdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  85. mantisdk/algorithm/gepa/lib/proposer/base.py +31 -0
  86. mantisdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  87. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  88. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  89. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  90. mantisdk/algorithm/gepa/lib/py.typed +0 -0
  91. mantisdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  92. mantisdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  93. mantisdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  94. mantisdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  95. mantisdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  96. mantisdk/algorithm/gepa/lib/strategies/instruction_proposal.py +127 -0
  97. mantisdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  98. mantisdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  99. mantisdk/algorithm/gepa/tracing.py +105 -0
  100. mantisdk/algorithm/utils.py +177 -0
  101. mantisdk/algorithm/verl/__init__.py +5 -0
  102. mantisdk/algorithm/verl/interface.py +202 -0
  103. mantisdk/cli/__init__.py +56 -0
  104. mantisdk/cli/prometheus.py +115 -0
  105. mantisdk/cli/store.py +131 -0
  106. mantisdk/cli/vllm.py +29 -0
  107. mantisdk/client.py +408 -0
  108. mantisdk/config.py +348 -0
  109. mantisdk/emitter/__init__.py +43 -0
  110. mantisdk/emitter/annotation.py +370 -0
  111. mantisdk/emitter/exception.py +54 -0
  112. mantisdk/emitter/message.py +61 -0
  113. mantisdk/emitter/object.py +117 -0
  114. mantisdk/emitter/reward.py +320 -0
  115. mantisdk/env_var.py +156 -0
  116. mantisdk/execution/__init__.py +15 -0
  117. mantisdk/execution/base.py +64 -0
  118. mantisdk/execution/client_server.py +443 -0
  119. mantisdk/execution/events.py +69 -0
  120. mantisdk/execution/inter_process.py +16 -0
  121. mantisdk/execution/shared_memory.py +282 -0
  122. mantisdk/instrumentation/__init__.py +119 -0
  123. mantisdk/instrumentation/agentops.py +314 -0
  124. mantisdk/instrumentation/agentops_langchain.py +45 -0
  125. mantisdk/instrumentation/litellm.py +83 -0
  126. mantisdk/instrumentation/vllm.py +81 -0
  127. mantisdk/instrumentation/weave.py +500 -0
  128. mantisdk/litagent/__init__.py +11 -0
  129. mantisdk/litagent/decorator.py +536 -0
  130. mantisdk/litagent/litagent.py +252 -0
  131. mantisdk/llm_proxy.py +1890 -0
  132. mantisdk/logging.py +370 -0
  133. mantisdk/reward.py +7 -0
  134. mantisdk/runner/__init__.py +11 -0
  135. mantisdk/runner/agent.py +845 -0
  136. mantisdk/runner/base.py +182 -0
  137. mantisdk/runner/legacy.py +309 -0
  138. mantisdk/semconv.py +170 -0
  139. mantisdk/server.py +401 -0
  140. mantisdk/store/__init__.py +23 -0
  141. mantisdk/store/base.py +897 -0
  142. mantisdk/store/client_server.py +2092 -0
  143. mantisdk/store/collection/__init__.py +30 -0
  144. mantisdk/store/collection/base.py +587 -0
  145. mantisdk/store/collection/memory.py +970 -0
  146. mantisdk/store/collection/mongo.py +1412 -0
  147. mantisdk/store/collection_based.py +1823 -0
  148. mantisdk/store/insight.py +648 -0
  149. mantisdk/store/listener.py +58 -0
  150. mantisdk/store/memory.py +396 -0
  151. mantisdk/store/mongo.py +165 -0
  152. mantisdk/store/sqlite.py +3 -0
  153. mantisdk/store/threading.py +357 -0
  154. mantisdk/store/utils.py +142 -0
  155. mantisdk/tracer/__init__.py +16 -0
  156. mantisdk/tracer/agentops.py +242 -0
  157. mantisdk/tracer/base.py +287 -0
  158. mantisdk/tracer/dummy.py +106 -0
  159. mantisdk/tracer/otel.py +555 -0
  160. mantisdk/tracer/weave.py +677 -0
  161. mantisdk/trainer/__init__.py +6 -0
  162. mantisdk/trainer/init_utils.py +263 -0
  163. mantisdk/trainer/legacy.py +367 -0
  164. mantisdk/trainer/registry.py +12 -0
  165. mantisdk/trainer/trainer.py +618 -0
  166. mantisdk/types/__init__.py +6 -0
  167. mantisdk/types/core.py +553 -0
  168. mantisdk/types/resources.py +204 -0
  169. mantisdk/types/tracer.py +515 -0
  170. mantisdk/types/tracing.py +218 -0
  171. mantisdk/utils/__init__.py +1 -0
  172. mantisdk/utils/id.py +18 -0
  173. mantisdk/utils/metrics.py +1025 -0
  174. mantisdk/utils/otel.py +578 -0
  175. mantisdk/utils/otlp.py +536 -0
  176. mantisdk/utils/server_launcher.py +1045 -0
  177. mantisdk/utils/system_snapshot.py +81 -0
  178. mantisdk/verl/__init__.py +8 -0
  179. mantisdk/verl/__main__.py +6 -0
  180. mantisdk/verl/async_server.py +46 -0
  181. mantisdk/verl/config.yaml +27 -0
  182. mantisdk/verl/daemon.py +1154 -0
  183. mantisdk/verl/dataset.py +44 -0
  184. mantisdk/verl/entrypoint.py +248 -0
  185. mantisdk/verl/trainer.py +549 -0
  186. mantisdk-0.1.0.dist-info/METADATA +119 -0
  187. mantisdk-0.1.0.dist-info/RECORD +190 -0
  188. mantisdk-0.1.0.dist-info/WHEEL +4 -0
  189. mantisdk-0.1.0.dist-info/entry_points.txt +2 -0
  190. mantisdk-0.1.0.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,242 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ import warnings
8
+ from contextlib import asynccontextmanager, contextmanager
9
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Iterator, List, Optional
10
+
11
+ import agentops
12
+ import agentops.sdk.core
13
+ import opentelemetry.trace as trace_api
14
+ from agentops.sdk.core import TracingCore
15
+ from opentelemetry.sdk.trace import TracerProvider as TracerProviderImpl
16
+ from opentelemetry.trace.status import StatusCode
17
+
18
+ from mantisdk.instrumentation import instrument_all, uninstrument_all
19
+ from mantisdk.store.base import LightningStore
20
+ from mantisdk.utils.otel import get_span_processors, get_tracer_provider
21
+
22
+ from .base import with_active_tracer_context
23
+ from .otel import LightningSpanProcessor, OtelTracer
24
+
25
+ if TYPE_CHECKING:
26
+ from agentops.integration.callbacks.langchain import LangchainCallbackHandler
27
+
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class AgentOpsTracer(OtelTracer):
33
+ """Traces agent execution using AgentOps.
34
+
35
+ This tracer provides functionality to capture execution details using the
36
+ AgentOps library. It manages the AgentOps client initialization, server setup,
37
+ and integration with the OpenTelemetry tracing ecosystem.
38
+
39
+ Attributes:
40
+ agentops_managed: Whether to automatically manage `agentops`.
41
+ When set to true, tracer calls `agentops.init()`
42
+ automatically and launches an agentops endpoint locally.
43
+ If not, you are responsible for calling and using it
44
+ before using the tracer.
45
+ instrument_managed: Whether to automatically manage instrumentation.
46
+ When set to false, you will manage the instrumentation
47
+ yourself and the tracer might not work as expected.
48
+ daemon: Whether the AgentOps server runs as a daemon process.
49
+ Only applicable if `agentops_managed` is True.
50
+ """
51
+
52
+ def __init__(self, *, agentops_managed: bool = True, instrument_managed: bool = True, daemon: bool = True):
53
+ super().__init__()
54
+ self._lightning_span_processor: Optional[LightningSpanProcessor] = None
55
+ self.agentops_managed = agentops_managed
56
+ self.instrument_managed = instrument_managed
57
+ self.daemon = daemon
58
+
59
+ if not self.agentops_managed:
60
+ logger.warning("agentops_managed=False. You are responsible for AgentOps setup.")
61
+ if not self.instrument_managed:
62
+ logger.warning("instrument_managed=False. You are responsible for all instrumentation.")
63
+
64
+ def instrument(self, worker_id: int):
65
+ instrument_all()
66
+
67
+ def uninstrument(self, worker_id: int):
68
+ uninstrument_all()
69
+
70
+ def _initialize_tracer_provider(self, worker_id: int):
71
+ logger.info(f"[Worker {worker_id}] Setting up AgentOps tracer...") # worker_id included in process name
72
+
73
+ if self.instrument_managed:
74
+ self.instrument(worker_id)
75
+ logger.info(f"[Worker {worker_id}] Instrumentation applied.")
76
+
77
+ if self.agentops_managed:
78
+ os.environ.setdefault("AGENTOPS_API_KEY", "dummy")
79
+ if not agentops.get_client().initialized:
80
+ agentops.init(auto_start_session=False) # type: ignore
81
+ logger.info(f"[Worker {worker_id}] AgentOps client initialized.")
82
+ else:
83
+ logger.warning(f"[Worker {worker_id}] AgentOps client was already initialized. Skip initialization.")
84
+
85
+ span_processors = get_span_processors(self._get_tracer_provider(), LightningSpanProcessor)
86
+ if len(span_processors) > 0:
87
+ logger.warning(
88
+ "LightningSpanProcessor already present in TracerProvider. You might have called init_worker() multiple times."
89
+ "Mantisdk will try to reuse the existing LightningSpanProcessor."
90
+ )
91
+ if len(span_processors) > 1:
92
+ logger.error("More than one LightningSpanProcessors present in TracerProvider. This should not happen.")
93
+ self._lightning_span_processor = span_processors[0]
94
+ else:
95
+ self._lightning_span_processor = LightningSpanProcessor()
96
+ self._get_tracer_provider().add_span_processor(self._lightning_span_processor) # type: ignore
97
+
98
+ def teardown_worker(self, worker_id: int) -> None:
99
+ super().teardown_worker(worker_id)
100
+
101
+ if self.instrument_managed:
102
+ self.uninstrument(worker_id)
103
+ logger.info(f"[Worker {worker_id}] Instrumentation removed.")
104
+
105
+ # NOTE: The teardown doesn't try to remove the LightningSpanProcessor from the TracerProvider.
106
+ # Currently there is no stable way to fully restore the AgentOps state to the initial state.
107
+
108
+ @with_active_tracer_context
109
+ @asynccontextmanager
110
+ async def trace_context(
111
+ self,
112
+ name: Optional[str] = None,
113
+ *,
114
+ store: Optional[LightningStore] = None,
115
+ rollout_id: Optional[str] = None,
116
+ attempt_id: Optional[str] = None,
117
+ ) -> AsyncGenerator[trace_api.Tracer, None]:
118
+ """
119
+ Starts a new tracing context. This should be used as a context manager.
120
+
121
+ Args:
122
+ name: Optional name for the tracing context.
123
+ store: Optional store to add the spans to.
124
+ rollout_id: Optional rollout ID to add the spans to.
125
+ attempt_id: Optional attempt ID to add the spans to.
126
+
127
+ Yields:
128
+ The OpenTelemetry tracer instance to collect spans.
129
+ """
130
+ if store is not None:
131
+ warnings.warn(
132
+ "store is deprecated in favor of init_worker(). It will be removed in the future.",
133
+ DeprecationWarning,
134
+ stacklevel=3,
135
+ )
136
+ else:
137
+ store = self._store
138
+ with self._trace_context_sync(name=name, store=store, rollout_id=rollout_id, attempt_id=attempt_id) as tracer:
139
+ yield tracer
140
+
141
+ @contextmanager
142
+ def _trace_context_sync(
143
+ self,
144
+ name: Optional[str] = None,
145
+ *,
146
+ store: Optional[LightningStore] = None,
147
+ rollout_id: Optional[str] = None,
148
+ attempt_id: Optional[str] = None,
149
+ ) -> Iterator[trace_api.Tracer]:
150
+ """Implementation of `trace_context` for synchronous execution."""
151
+ if not self._lightning_span_processor:
152
+ raise RuntimeError("LightningSpanProcessor is not initialized. Call init_worker() first.")
153
+ tracer_provider = self._get_tracer_provider()
154
+
155
+ kwargs: dict[str, Any] = {}
156
+ if name is not None:
157
+ kwargs["trace_name"] = name
158
+ elif rollout_id is not None:
159
+ kwargs["trace_name"] = rollout_id
160
+ if store is not None and rollout_id is not None and attempt_id is not None:
161
+ if store.capabilities.get("otlp_traces", False) is True:
162
+ logger.debug(f"Tracing to LightningStore rollout_id={rollout_id}, attempt_id={attempt_id}")
163
+ self._enable_native_otlp_exporter(store, rollout_id, attempt_id)
164
+ else:
165
+ self._disable_native_otlp_exporter()
166
+ ctx = self._lightning_span_processor.with_context(store=store, rollout_id=rollout_id, attempt_id=attempt_id)
167
+ with ctx:
168
+ # AgentOps end_trace and start_trace must live inside the lightning span processor context.
169
+ # Otherwise some traces might not be recorded.
170
+ with self._agentops_trace_context(rollout_id, attempt_id, kwargs):
171
+ yield trace_api.get_tracer(__name__, tracer_provider=tracer_provider)
172
+ elif store is None and rollout_id is None and attempt_id is None:
173
+ self._disable_native_otlp_exporter()
174
+ with self._lightning_span_processor:
175
+ with self._agentops_trace_context(None, None, kwargs):
176
+ yield trace_api.get_tracer(__name__, tracer_provider=tracer_provider)
177
+ else:
178
+ raise ValueError("store, rollout_id, and attempt_id must be either all provided or all None")
179
+
180
+ @contextmanager
181
+ def _agentops_trace_context(self, rollout_id: Optional[str], attempt_id: Optional[str], kwargs: dict[str, Any]):
182
+ trace = agentops.start_trace(**kwargs)
183
+ status = StatusCode.OK # type: ignore
184
+ try:
185
+ yield
186
+ except Exception as e:
187
+ # This will catch errors in user code.
188
+ status = StatusCode.ERROR # type: ignore
189
+ logger.error(f"Trace failed for rollout_id={rollout_id}, attempt_id={attempt_id}: {e}")
190
+ raise # should reraise the error here so that runner can handle it
191
+ finally:
192
+ agentops.end_trace(trace, end_state=status) # type: ignore
193
+
194
+ def get_langchain_handler(self, tags: List[str] | None = None) -> LangchainCallbackHandler:
195
+ """
196
+ Get the Langchain callback handler for integrating with Langchain.
197
+
198
+ Args:
199
+ tags: Optional list of tags to apply to the Langchain callback handler.
200
+
201
+ Returns:
202
+ An instance of the Langchain callback handler.
203
+ """
204
+ import agentops
205
+ from agentops.integration.callbacks.langchain import LangchainCallbackHandler
206
+
207
+ tags = tags or []
208
+ client_instance = agentops.get_client()
209
+ api_key = None
210
+ if client_instance.initialized:
211
+ api_key = client_instance.config.api_key
212
+ else:
213
+ logger.warning(
214
+ "AgentOps client not initialized when creating LangchainCallbackHandler. API key may be missing."
215
+ )
216
+ return LangchainCallbackHandler(api_key=api_key, tags=tags)
217
+
218
+ get_langchain_callback_handler = get_langchain_handler # alias
219
+
220
+ def _get_tracer_provider(self) -> TracerProviderImpl:
221
+ try:
222
+ # new versions
223
+ instance = agentops.sdk.core.tracer
224
+ if instance.provider is None:
225
+ raise RuntimeError("AgentOps TracerProvider is not initialized.")
226
+
227
+ if get_tracer_provider() is not instance.provider:
228
+ logger.error(
229
+ "Mismatch between global singleton TracerProvider and AgentOps TracerProvider. "
230
+ "AgentOps might not work properly."
231
+ )
232
+
233
+ if not isinstance(instance.provider, TracerProviderImpl): # type: ignore
234
+ raise RuntimeError("Unsupported TracerProvider type for AgentOps instrumentation.")
235
+
236
+ self._tracer_provider = instance.provider
237
+ return self._tracer_provider
238
+ except AttributeError:
239
+ # old versions
240
+ instance = TracingCore.get_instance() # type: ignore
241
+ self._tracer_provider = instance._provider # type: ignore
242
+ return self._tracer_provider # type: ignore
@@ -0,0 +1,287 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import logging
7
+ from contextlib import contextmanager
8
+ from typing import TYPE_CHECKING, Any, AsyncContextManager, Awaitable, Callable, ContextManager, List, Optional, TypeVar
9
+
10
+ from mantisdk.store.base import LightningStore
11
+ from mantisdk.types import Attributes, ParallelWorkerBase, Span, SpanCoreFields, SpanRecordingContext, TraceStatus
12
+
13
+ if TYPE_CHECKING:
14
+ from langchain_core.callbacks.base import BaseCallbackHandler # type: ignore
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ T = TypeVar("T")
20
+
21
+
22
+ _active_tracer: Optional[Tracer] = None
23
+
24
+ T_func = Callable[..., Awaitable[Any]]
25
+
26
+
27
+ class Tracer(ParallelWorkerBase):
28
+ """
29
+ An abstract base class for tracers.
30
+
31
+ This class defines a standard interface for tracing code execution,
32
+ capturing the resulting spans, and providing them for analysis. It is
33
+ designed to be backend-agnostic, allowing for different implementations
34
+ (e.g., for AgentOps, OpenTelemetry, Docker, etc.).
35
+
36
+ The primary interaction pattern is through the [`trace_context`][mantisdk.Tracer.trace_context]
37
+ context manager, which ensures that traces are properly started and captured,
38
+ even in the case of exceptions.
39
+
40
+ A typical workflow:
41
+
42
+ ```python
43
+ tracer = YourTracerImplementation()
44
+
45
+ try:
46
+ async with tracer.trace_context(name="my_traced_task"):
47
+ # ... code to be traced ...
48
+ await run_my_agent_logic()
49
+ except Exception as e:
50
+ print(f"An error occurred: {e}")
51
+
52
+ # Retrieve the trace data after the context block
53
+ spans: list[ReadableSpan] = tracer.get_last_trace()
54
+
55
+ # Process the trace data
56
+ if trace_tree:
57
+ rl_triplets = TracerTraceToTriplet().adapt(spans)
58
+ # ... do something with the triplets
59
+ ```
60
+ """
61
+
62
+ _store: Optional[LightningStore] = None
63
+
64
+ def init_worker(self, worker_id: int, store: Optional[LightningStore] = None) -> None:
65
+ """Initialize the tracer for a worker.
66
+
67
+ Args:
68
+ worker_id: The ID of the worker.
69
+ store: The store to add the spans to. If it's provided, traces will be added to the store when tracing.
70
+ """
71
+ super().init_worker(worker_id)
72
+ self._store = store
73
+
74
+ def trace_context(
75
+ self,
76
+ name: Optional[str] = None,
77
+ *,
78
+ store: Optional[LightningStore] = None,
79
+ rollout_id: Optional[str] = None,
80
+ attempt_id: Optional[str] = None,
81
+ ) -> AsyncContextManager[Any]:
82
+ """
83
+ Starts a new tracing context. This should be used as a context manager.
84
+
85
+ The implementation should handle the setup and teardown of the tracing
86
+ for the enclosed code block. It must ensure that any spans generated
87
+ within the `with` block are collected and made available via
88
+ [`get_last_trace`][mantisdk.Tracer.get_last_trace].
89
+
90
+ Args:
91
+ name: The name for the root span of this trace context.
92
+ store: The store to add the spans to. Deprecated in favor of passing store to init_worker().
93
+ rollout_id: The rollout ID to add the spans to.
94
+ attempt_id: The attempt ID to add the spans to.
95
+ """
96
+ raise NotImplementedError()
97
+
98
+ def _trace_context_sync(
99
+ self,
100
+ name: Optional[str] = None,
101
+ *,
102
+ rollout_id: Optional[str] = None,
103
+ attempt_id: Optional[str] = None,
104
+ ) -> ContextManager[Any]:
105
+ """Internal API for CI backward compatibility."""
106
+ raise NotImplementedError()
107
+
108
+ def get_last_trace(self) -> List[Span]:
109
+ """
110
+ Retrieves the raw list of captured spans from the most recent trace.
111
+
112
+ Returns:
113
+ A list of [`Span`][mantisdk.Span] objects collected during the last trace.
114
+ """
115
+ raise NotImplementedError()
116
+
117
+ def trace_run(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
118
+ """
119
+ A convenience wrapper to trace the execution of a single synchronous function.
120
+
121
+ Deprecated in favor of customizing Runners.
122
+
123
+ Args:
124
+ func: The synchronous function to execute and trace.
125
+ *args: Positional arguments to pass to the function.
126
+ **kwargs: Keyword arguments to pass to the function.
127
+
128
+ Returns:
129
+ The return value of the function.
130
+ """
131
+ with self._trace_context_sync(name=func.__name__):
132
+ return func(*args, **kwargs)
133
+
134
+ def create_span(
135
+ self,
136
+ name: str,
137
+ attributes: Optional[Attributes] = None,
138
+ timestamp: Optional[float] = None,
139
+ status: Optional[TraceStatus] = None,
140
+ ) -> SpanCoreFields:
141
+ """Notify the tracer that a span should be created here.
142
+
143
+ It uses a fire-and-forget approach and doesn't wait for the span to be created.
144
+
145
+ Args:
146
+ name: The name of the span.
147
+ attributes: The attributes of the span.
148
+ timestamp: The timestamp of the span.
149
+ status: The status of the span.
150
+
151
+ Returns:
152
+ The core fields of the span.
153
+ """
154
+ raise NotImplementedError()
155
+
156
+ def operation_context(
157
+ self,
158
+ name: str,
159
+ attributes: Optional[Attributes] = None,
160
+ start_time: Optional[float] = None,
161
+ end_time: Optional[float] = None,
162
+ ) -> ContextManager[SpanRecordingContext]:
163
+ """Start to record an operation to a span.
164
+
165
+ Args:
166
+ name: The name of the operation.
167
+ attributes: The attributes of the operation.
168
+ start_time: The start time of the operation.
169
+ end_time: The end time of the operation.
170
+
171
+ Returns:
172
+ A [`SpanRecordingContext`][mantisdk.SpanRecordingContext] for recording the operation on the span.
173
+ """
174
+ raise NotImplementedError()
175
+
176
+ async def trace_run_async(self, func: Callable[..., Awaitable[Any]], *args: Any, **kwargs: Any) -> Any:
177
+ """
178
+ A convenience wrapper to trace the execution of a single asynchronous function.
179
+
180
+ Deprecated in favor of customizing Runners.
181
+
182
+ Args:
183
+ func: The asynchronous function to execute and trace.
184
+ *args: Positional arguments to pass to the function.
185
+ **kwargs: Keyword arguments to pass to the function.
186
+
187
+ Returns:
188
+ The return value of the function.
189
+ """
190
+ async with self.trace_context(name=func.__name__):
191
+ return await func(*args, **kwargs)
192
+
193
+ def get_langchain_handler(self) -> Optional[BaseCallbackHandler]: # type: ignore
194
+ """Get a handler to install in langchain agent callback.
195
+
196
+ Agents are expected to use this handler in their agents to enable tracing.
197
+ """
198
+ logger.warning(f"{self.__class__.__name__} does not provide a LangChain callback handler.")
199
+ return None
200
+
201
+ @contextmanager
202
+ def lifespan(self, store: Optional[LightningStore] = None):
203
+ """A context manager to manage the lifespan of the tracer.
204
+
205
+ This can be used to set up and tear down any necessary resources
206
+ for the tracer, useful for debugging purposes.
207
+
208
+ Args:
209
+ store: The store to add the spans to. If it's provided, traces will be added to the store when tracing.
210
+ """
211
+ has_init = False
212
+ has_init_worker = False
213
+ try:
214
+ self.init()
215
+ has_init = True
216
+
217
+ self.init_worker(0, store)
218
+ has_init_worker = True
219
+
220
+ yield
221
+
222
+ finally:
223
+ if has_init_worker:
224
+ self.teardown_worker(0)
225
+ if has_init:
226
+ self.teardown()
227
+
228
+
229
+ def set_active_tracer(tracer: Tracer):
230
+ """Set the active tracer for the current process.
231
+
232
+ Args:
233
+ tracer: The tracer to set as active.
234
+ """
235
+ global _active_tracer
236
+ if _active_tracer is not None:
237
+ raise ValueError("An active tracer is already set. Cannot set a new one.")
238
+ _active_tracer = tracer
239
+
240
+
241
+ def clear_active_tracer():
242
+ """Clear the active tracer for the current process."""
243
+ global _active_tracer
244
+ _active_tracer = None
245
+
246
+
247
+ def get_active_tracer() -> Optional[Tracer]:
248
+ """Get the active tracer for the current process.
249
+
250
+ Returns:
251
+ The active tracer, or None if no tracer is active.
252
+ """
253
+ global _active_tracer
254
+ return _active_tracer
255
+
256
+
257
+ class _ActiveTracerAsyncCM(AsyncContextManager[T]):
258
+ def __init__(self, tracer: Tracer, inner: AsyncContextManager[T]):
259
+ self._tracer = tracer
260
+ self._inner = inner
261
+
262
+ async def __aenter__(self) -> T:
263
+ set_active_tracer(self._tracer) # will raise if nested
264
+ try:
265
+ return await self._inner.__aenter__()
266
+ except Exception:
267
+ clear_active_tracer()
268
+ raise
269
+
270
+ async def __aexit__(self, *args: Any, **kwargs: Any) -> Optional[bool]:
271
+ try:
272
+ return await self._inner.__aexit__(*args, **kwargs)
273
+ finally:
274
+ clear_active_tracer()
275
+
276
+
277
+ def with_active_tracer_context(
278
+ func: Callable[..., AsyncContextManager[T]],
279
+ ) -> Callable[..., AsyncContextManager[T]]:
280
+ """Decorate a method returning an AsyncContextManager so tracer is active for the whole `async with`."""
281
+
282
+ @functools.wraps(func)
283
+ def wrapper(self: Tracer, *args: Any, **kwargs: Any) -> AsyncContextManager[T]:
284
+ cm = func(self, *args, **kwargs)
285
+ return _ActiveTracerAsyncCM(self, cm)
286
+
287
+ return wrapper
@@ -0,0 +1,106 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import time
7
+ from contextlib import contextmanager
8
+ from typing import (
9
+ Iterator,
10
+ Optional,
11
+ )
12
+
13
+ from mantisdk.types import (
14
+ Attributes,
15
+ SpanCoreFields,
16
+ SpanRecordingContext,
17
+ StatusCode,
18
+ TraceStatus,
19
+ )
20
+ from mantisdk.utils.otel import format_exception_attributes
21
+
22
+ from .base import Tracer
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class DummySpanRecordingContext(SpanRecordingContext):
28
+ """Context for recording operations on a dummy span, not dependent on any backend tracer."""
29
+
30
+ def __init__(self, name: str, attributes: Optional[Attributes] = None, start_time: Optional[float] = None) -> None:
31
+ self.name = name
32
+ self.attributes = attributes or {}
33
+ self.start_time = start_time or time.time()
34
+ self.end_time = None
35
+ self.status = TraceStatus(status_code="OK")
36
+
37
+ def record_exception(self, exception: BaseException) -> None:
38
+ self.record_status("ERROR", str(exception))
39
+ self.record_attributes(format_exception_attributes(exception))
40
+
41
+ def record_attributes(self, attributes: Attributes) -> None:
42
+ self.attributes.update(attributes)
43
+
44
+ def record_status(self, status_code: StatusCode, description: Optional[str] = None) -> None:
45
+ self.status = TraceStatus(status_code=status_code, description=description)
46
+
47
+ def finalize(self, end_time: Optional[float] = None) -> None:
48
+ self.end_time = end_time or time.time()
49
+
50
+ def get_recorded_span(self) -> SpanCoreFields:
51
+ if self.end_time is None:
52
+ raise ValueError("End time is not set. Call finalize() first.")
53
+ return SpanCoreFields(
54
+ name=self.name,
55
+ attributes=self.attributes,
56
+ start_time=self.start_time,
57
+ end_time=self.end_time,
58
+ status=self.status,
59
+ )
60
+
61
+
62
+ class DummyTracer(Tracer):
63
+ """A dummy tracer that does not trace anything, but it is compatible with the emitter API.
64
+
65
+ It doesn't rely on any backend tracer, and also doesn't use any stores.
66
+ """
67
+
68
+ def create_span(
69
+ self,
70
+ name: str,
71
+ attributes: Optional[Attributes] = None,
72
+ timestamp: Optional[float] = None,
73
+ status: Optional[TraceStatus] = None,
74
+ ) -> SpanCoreFields:
75
+ if attributes is None:
76
+ attributes = {}
77
+ if timestamp is None:
78
+ timestamp = time.time()
79
+ if status is None:
80
+ status = TraceStatus(status_code="OK")
81
+ return SpanCoreFields(
82
+ name=name,
83
+ attributes=attributes,
84
+ start_time=timestamp,
85
+ end_time=timestamp,
86
+ status=status,
87
+ )
88
+
89
+ @contextmanager
90
+ def operation_context(
91
+ self,
92
+ name: str,
93
+ attributes: Optional[Attributes] = None,
94
+ start_time: Optional[float] = None,
95
+ end_time: Optional[float] = None,
96
+ ) -> Iterator[DummySpanRecordingContext]:
97
+ start_time = start_time or time.time()
98
+ recording_context = DummySpanRecordingContext(name, attributes, start_time)
99
+ try:
100
+ yield recording_context
101
+ except Exception as exc:
102
+ recording_context.record_exception(exc)
103
+ recording_context.record_status("ERROR", str(exc))
104
+ raise
105
+ finally:
106
+ recording_context.finalize(end_time)