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.
- mantisdk/__init__.py +22 -0
- mantisdk/adapter/__init__.py +15 -0
- mantisdk/adapter/base.py +94 -0
- mantisdk/adapter/messages.py +270 -0
- mantisdk/adapter/triplet.py +1028 -0
- mantisdk/algorithm/__init__.py +39 -0
- mantisdk/algorithm/apo/__init__.py +5 -0
- mantisdk/algorithm/apo/apo.py +889 -0
- mantisdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
- mantisdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
- mantisdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
- mantisdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
- mantisdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
- mantisdk/algorithm/base.py +162 -0
- mantisdk/algorithm/decorator.py +264 -0
- mantisdk/algorithm/fast.py +250 -0
- mantisdk/algorithm/gepa/__init__.py +59 -0
- mantisdk/algorithm/gepa/adapter.py +459 -0
- mantisdk/algorithm/gepa/gepa.py +364 -0
- mantisdk/algorithm/gepa/lib/__init__.py +18 -0
- mantisdk/algorithm/gepa/lib/adapters/README.md +12 -0
- mantisdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
- mantisdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
- mantisdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +266 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +705 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
- mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
- mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- mantisdk/algorithm/gepa/lib/api.py +375 -0
- mantisdk/algorithm/gepa/lib/core/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/core/adapter.py +180 -0
- mantisdk/algorithm/gepa/lib/core/data_loader.py +74 -0
- mantisdk/algorithm/gepa/lib/core/engine.py +356 -0
- mantisdk/algorithm/gepa/lib/core/result.py +233 -0
- mantisdk/algorithm/gepa/lib/core/state.py +636 -0
- mantisdk/algorithm/gepa/lib/examples/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/examples/aime.py +24 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
- mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
- mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
- mantisdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
- mantisdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +455 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +824 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
- mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
- mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
- mantisdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
- mantisdk/algorithm/gepa/lib/gepa_utils.py +117 -0
- mantisdk/algorithm/gepa/lib/logging/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
- mantisdk/algorithm/gepa/lib/logging/logger.py +75 -0
- mantisdk/algorithm/gepa/lib/logging/utils.py +103 -0
- mantisdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/proposer/base.py +31 -0
- mantisdk/algorithm/gepa/lib/proposer/merge.py +357 -0
- mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
- mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
- mantisdk/algorithm/gepa/lib/py.typed +0 -0
- mantisdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
- mantisdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
- mantisdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
- mantisdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
- mantisdk/algorithm/gepa/lib/strategies/instruction_proposal.py +127 -0
- mantisdk/algorithm/gepa/lib/utils/__init__.py +10 -0
- mantisdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
- mantisdk/algorithm/gepa/tracing.py +105 -0
- mantisdk/algorithm/utils.py +177 -0
- mantisdk/algorithm/verl/__init__.py +5 -0
- mantisdk/algorithm/verl/interface.py +202 -0
- mantisdk/cli/__init__.py +56 -0
- mantisdk/cli/prometheus.py +115 -0
- mantisdk/cli/store.py +131 -0
- mantisdk/cli/vllm.py +29 -0
- mantisdk/client.py +408 -0
- mantisdk/config.py +348 -0
- mantisdk/emitter/__init__.py +43 -0
- mantisdk/emitter/annotation.py +370 -0
- mantisdk/emitter/exception.py +54 -0
- mantisdk/emitter/message.py +61 -0
- mantisdk/emitter/object.py +117 -0
- mantisdk/emitter/reward.py +320 -0
- mantisdk/env_var.py +156 -0
- mantisdk/execution/__init__.py +15 -0
- mantisdk/execution/base.py +64 -0
- mantisdk/execution/client_server.py +443 -0
- mantisdk/execution/events.py +69 -0
- mantisdk/execution/inter_process.py +16 -0
- mantisdk/execution/shared_memory.py +282 -0
- mantisdk/instrumentation/__init__.py +119 -0
- mantisdk/instrumentation/agentops.py +314 -0
- mantisdk/instrumentation/agentops_langchain.py +45 -0
- mantisdk/instrumentation/litellm.py +83 -0
- mantisdk/instrumentation/vllm.py +81 -0
- mantisdk/instrumentation/weave.py +500 -0
- mantisdk/litagent/__init__.py +11 -0
- mantisdk/litagent/decorator.py +536 -0
- mantisdk/litagent/litagent.py +252 -0
- mantisdk/llm_proxy.py +1890 -0
- mantisdk/logging.py +370 -0
- mantisdk/reward.py +7 -0
- mantisdk/runner/__init__.py +11 -0
- mantisdk/runner/agent.py +845 -0
- mantisdk/runner/base.py +182 -0
- mantisdk/runner/legacy.py +309 -0
- mantisdk/semconv.py +170 -0
- mantisdk/server.py +401 -0
- mantisdk/store/__init__.py +23 -0
- mantisdk/store/base.py +897 -0
- mantisdk/store/client_server.py +2092 -0
- mantisdk/store/collection/__init__.py +30 -0
- mantisdk/store/collection/base.py +587 -0
- mantisdk/store/collection/memory.py +970 -0
- mantisdk/store/collection/mongo.py +1412 -0
- mantisdk/store/collection_based.py +1823 -0
- mantisdk/store/insight.py +648 -0
- mantisdk/store/listener.py +58 -0
- mantisdk/store/memory.py +396 -0
- mantisdk/store/mongo.py +165 -0
- mantisdk/store/sqlite.py +3 -0
- mantisdk/store/threading.py +357 -0
- mantisdk/store/utils.py +142 -0
- mantisdk/tracer/__init__.py +16 -0
- mantisdk/tracer/agentops.py +242 -0
- mantisdk/tracer/base.py +287 -0
- mantisdk/tracer/dummy.py +106 -0
- mantisdk/tracer/otel.py +555 -0
- mantisdk/tracer/weave.py +677 -0
- mantisdk/trainer/__init__.py +6 -0
- mantisdk/trainer/init_utils.py +263 -0
- mantisdk/trainer/legacy.py +367 -0
- mantisdk/trainer/registry.py +12 -0
- mantisdk/trainer/trainer.py +618 -0
- mantisdk/types/__init__.py +6 -0
- mantisdk/types/core.py +553 -0
- mantisdk/types/resources.py +204 -0
- mantisdk/types/tracer.py +515 -0
- mantisdk/types/tracing.py +218 -0
- mantisdk/utils/__init__.py +1 -0
- mantisdk/utils/id.py +18 -0
- mantisdk/utils/metrics.py +1025 -0
- mantisdk/utils/otel.py +578 -0
- mantisdk/utils/otlp.py +536 -0
- mantisdk/utils/server_launcher.py +1045 -0
- mantisdk/utils/system_snapshot.py +81 -0
- mantisdk/verl/__init__.py +8 -0
- mantisdk/verl/__main__.py +6 -0
- mantisdk/verl/async_server.py +46 -0
- mantisdk/verl/config.yaml +27 -0
- mantisdk/verl/daemon.py +1154 -0
- mantisdk/verl/dataset.py +44 -0
- mantisdk/verl/entrypoint.py +248 -0
- mantisdk/verl/trainer.py +549 -0
- mantisdk-0.1.0.dist-info/METADATA +119 -0
- mantisdk-0.1.0.dist-info/RECORD +190 -0
- mantisdk-0.1.0.dist-info/WHEEL +4 -0
- mantisdk-0.1.0.dist-info/entry_points.txt +2 -0
- 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
|
mantisdk/tracer/base.py
ADDED
|
@@ -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
|
mantisdk/tracer/dummy.py
ADDED
|
@@ -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)
|