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,515 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+
7
+ """Data models that mirror OpenTelemetry spans for Mantisdk."""
8
+
9
+ import json
10
+ from enum import Enum
11
+ from typing import Any, Dict, List, Literal, Optional, Protocol, Sequence, Union
12
+
13
+ from opentelemetry import trace as trace_api
14
+ from opentelemetry.sdk.resources import Resource
15
+ from opentelemetry.sdk.trace import Event as OtelEvent
16
+ from opentelemetry.sdk.trace import ReadableSpan
17
+ from opentelemetry.sdk.trace.id_generator import RandomIdGenerator
18
+ from opentelemetry.trace.status import Status as OtelStatus
19
+ from pydantic import BaseModel, ConfigDict
20
+
21
+ from mantisdk.semconv import AGL_VIRTUAL
22
+
23
+ __all__ = [
24
+ "AttributeValue",
25
+ "Attributes",
26
+ "TraceState",
27
+ "SpanContext",
28
+ "TraceStatus",
29
+ "Event",
30
+ "Link",
31
+ "OtelResource",
32
+ "Span",
33
+ "SpanNames",
34
+ "SpanAttributeNames",
35
+ "SpanLike",
36
+ "StatusCode",
37
+ "SpanCoreFields",
38
+ "SpanRecordingContext",
39
+ ]
40
+
41
+
42
+ def convert_timestamp(timestamp: Optional[int]) -> Optional[float]:
43
+ """Normalize OpenTelemetry timestamps to seconds.
44
+
45
+ Args:
46
+ timestamp: Timestamp expressed either in seconds or nanoseconds.
47
+
48
+ Returns:
49
+ Timestamp in seconds when `timestamp` is provided; otherwise `None`.
50
+ """
51
+ if not timestamp:
52
+ return None
53
+ return timestamp / 1_000_000_000 if timestamp > 1e12 else timestamp
54
+
55
+
56
+ def extract_extra_fields(src: Any, excluded_fields: List[str]) -> Dict[str, Any]:
57
+ """Capture custom attributes from an OpenTelemetry object.
58
+
59
+ Args:
60
+ src: Object that exposes a `__dict__` of potential attributes.
61
+ excluded_fields: Attribute names that should be removed from the output.
62
+
63
+ Returns:
64
+ Dictionary containing JSON-serializable representations of the remaining fields.
65
+ """
66
+ excluded_fields_set = set(excluded_fields) | set(["_" + k for k in excluded_fields])
67
+ # Exclude the function fields
68
+ excluded_fields_set |= set(src.__class__.__dict__.keys())
69
+ stripped_dict = {k.lstrip("_"): v for k, v in src.__dict__.items()}
70
+ candidates = {k: v for k, v in stripped_dict.items() if k not in excluded_fields_set and not k.startswith("_")}
71
+ # This should strip or flatten the unserializable fields
72
+ candidates_serialized = json.dumps(candidates, default=str)
73
+ return json.loads(candidates_serialized)
74
+
75
+
76
+ AttributeValue = Union[
77
+ str,
78
+ bool,
79
+ int,
80
+ float,
81
+ Sequence[str],
82
+ Sequence[bool],
83
+ Sequence[int],
84
+ Sequence[float],
85
+ ]
86
+ """Possible values for OpenTelemetry attributes."""
87
+ Attributes = Dict[str, AttributeValue]
88
+ """Mapping from attribute names to their values. Same as OpenTelemetry `Attributes` type."""
89
+ TraceState = Dict[str, str]
90
+ """Mapping from trace state key to its value. Same as OpenTelemetry `TraceState` type."""
91
+ StatusCode = Literal["UNSET", "OK", "ERROR"]
92
+ """The status code of the span."""
93
+
94
+
95
+ class SpanContext(BaseModel):
96
+ """Pydantic representation of `opentelemetry.trace.SpanContext` values."""
97
+
98
+ trace_id: str
99
+ """The trace ID of the span."""
100
+ span_id: str
101
+ """The span ID of the span."""
102
+ is_remote: bool
103
+ """Whether the span is remote."""
104
+ trace_state: TraceState
105
+ """Mapping from trace state key to its value."""
106
+
107
+ model_config = ConfigDict(extra="allow")
108
+
109
+ @classmethod
110
+ def from_opentelemetry(cls, src: trace_api.SpanContext) -> "SpanContext":
111
+ """Construct a [`SpanContext`][mantisdk.SpanContext] from OpenTelemetry data."""
112
+
113
+ return cls(
114
+ trace_id=trace_api.format_trace_id(src.trace_id),
115
+ span_id=trace_api.format_span_id(src.span_id),
116
+ is_remote=src.is_remote,
117
+ trace_state={k: v for k, v in src.trace_state.items()} if src.trace_state else {},
118
+ **extract_extra_fields(src, ["trace_id", "span_id", "is_remote", "trace_state"]),
119
+ )
120
+
121
+
122
+ class TraceStatus(BaseModel):
123
+ """Serializable variant of `opentelemetry.trace.Status`."""
124
+
125
+ status_code: StatusCode
126
+ """The status code of the span. Same as OpenTelemetry `Status.status_code` type."""
127
+ description: Optional[str] = None
128
+ """The description of the span. Same as OpenTelemetry `Status.description` type."""
129
+
130
+ model_config = ConfigDict(extra="allow")
131
+
132
+ @classmethod
133
+ def from_opentelemetry(cls, src: OtelStatus) -> "TraceStatus":
134
+ """Create a [`TraceStatus`][mantisdk.TraceStatus] from OpenTelemetry metadata."""
135
+
136
+ return cls(
137
+ status_code=src.status_code.name,
138
+ description=src.description,
139
+ **extract_extra_fields(src, ["status_code", "description"]),
140
+ )
141
+
142
+
143
+ class Event(BaseModel):
144
+ """Serializable representation of OpenTelemetry `Event` values."""
145
+
146
+ name: str
147
+ """The name of the event."""
148
+ attributes: Attributes
149
+ """Mapping from attribute names to their values. Same as OpenTelemetry `Attributes` type."""
150
+ timestamp: Optional[float] = None
151
+ """The timestamp of the event. Same as OpenTelemetry `Event.timestamp` type."""
152
+
153
+ model_config = ConfigDict(extra="allow")
154
+
155
+ @classmethod
156
+ def from_opentelemetry(cls, src: OtelEvent) -> "Event":
157
+ """Create an [`Event`][mantisdk.Event] from an OpenTelemetry event."""
158
+
159
+ return cls(
160
+ name=src.name,
161
+ attributes=dict(src.attributes) if src.attributes else {},
162
+ timestamp=convert_timestamp(src.timestamp),
163
+ **extract_extra_fields(src, ["name", "attributes", "timestamp"]),
164
+ )
165
+
166
+
167
+ class Link(BaseModel):
168
+ """Serializable representation of OpenTelemetry `Link` values."""
169
+
170
+ context: SpanContext
171
+ """The context of the link."""
172
+ attributes: Optional[Attributes] = None
173
+ """Optional attributes."""
174
+
175
+ model_config = ConfigDict(extra="allow")
176
+
177
+ @classmethod
178
+ def from_opentelemetry(cls, src: trace_api.Link) -> "Link":
179
+ """Create a [`Link`][mantisdk.Link] from an OpenTelemetry link."""
180
+
181
+ return cls(
182
+ context=SpanContext.from_opentelemetry(src.context),
183
+ attributes=dict(src.attributes) if src.attributes else None,
184
+ **extract_extra_fields(src, ["context", "attributes"]),
185
+ )
186
+
187
+
188
+ class OtelResource(BaseModel):
189
+ """Serializable representation of OpenTelemetry `Resource` values.
190
+
191
+ Named as `OtelResource` to avoid confusion with the [`Resource`][mantisdk.Resource] class.
192
+ Users will very rarely need to construct this class directly. Most of the times,
193
+ they deal with the [`Resource`][mantisdk.Resource] class instead, which describes
194
+ a very different concept.
195
+ """
196
+
197
+ attributes: Attributes
198
+ """Mapping from attribute names to their values. Same as OpenTelemetry `Attributes` type."""
199
+ schema_url: str
200
+ """The schema URL of the resource."""
201
+
202
+ @classmethod
203
+ def from_opentelemetry(cls, src: Resource) -> "OtelResource":
204
+ """Create a [`Resource`][mantisdk.Resource] from an OpenTelemetry resource."""
205
+
206
+ return cls(
207
+ attributes=dict(src.attributes) if src.attributes else {},
208
+ schema_url=src.schema_url if src.schema_url else "",
209
+ **extract_extra_fields(src, ["attributes", "schema_url"]),
210
+ )
211
+
212
+
213
+ class SpanCoreFields(BaseModel):
214
+ """Core fields of a span. Used by span creators who don't care about the full span model.
215
+
216
+ If the spans are managed by some OTel tracer provider, it's not advised to create spans via this path.
217
+ """
218
+
219
+ name: str
220
+ """The name of the span."""
221
+ status: TraceStatus
222
+ """The status of the span."""
223
+ attributes: Attributes
224
+ """The attributes of the span."""
225
+ start_time: Optional[float]
226
+ """The start time of the span."""
227
+ end_time: Optional[float]
228
+ """The end time of the span."""
229
+
230
+
231
+ class SpanRecordingContext(Protocol):
232
+ """Context for recording operations on a span. It doesn't have to finalize the span; the caller will do it."""
233
+
234
+ def record_exception(self, exception: BaseException) -> None:
235
+ """Record an exception on the span."""
236
+ raise NotImplementedError()
237
+
238
+ def record_attributes(self, attributes: Attributes) -> None:
239
+ """Record attributes on the span."""
240
+ raise NotImplementedError()
241
+
242
+ def record_status(self, status_code: StatusCode, description: Optional[str] = None) -> None:
243
+ """Record the status of the span."""
244
+ raise NotImplementedError()
245
+
246
+ def get_recorded_span(self) -> SpanCoreFields:
247
+ """Get the recording of the span."""
248
+ raise NotImplementedError()
249
+
250
+
251
+ class Span(BaseModel):
252
+ """Mantisdk's canonical span model used for persistence and analytics.
253
+
254
+ The model captures the most relevant fields from
255
+ `opentelemetry.sdk.trace.ReadableSpan` instances while preserving unmodeled
256
+ attributes in Pydantic `BaseModel`'s extra storage. This keeps the serialized format
257
+ stable even as upstream OpenTelemetry types evolve.
258
+ """
259
+
260
+ model_config = ConfigDict(extra="allow")
261
+
262
+ rollout_id: str
263
+ """The rollout which this span belongs to."""
264
+ attempt_id: str
265
+ """The attempt which this span belongs to."""
266
+ sequence_id: int
267
+ """The ID to make spans ordered within a single attempt."""
268
+
269
+ # Current ID (in hex, formatted via trace_api.format_*)
270
+ trace_id: str # one rollout can have traces coming from multiple places
271
+ """The trace ID of the span. One rollout/attempt can have multiple traces.
272
+ This ID comes from the OpenTelemetry trace ID generator.
273
+ """
274
+ span_id: str
275
+ """The span ID of the span. This ID comes from the OpenTelemetry span ID generator."""
276
+ parent_id: Optional[str]
277
+ """The parent span ID of the span."""
278
+
279
+ # Core ReadableSpan fields
280
+ name: str
281
+ """The name of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
282
+ status: TraceStatus
283
+ """The status of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
284
+ attributes: Attributes
285
+ """The attributes of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
286
+ events: List[Event]
287
+ """The events of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
288
+ links: List[Link]
289
+ """The links of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
290
+
291
+ # Timestamps
292
+ start_time: Optional[float]
293
+ """The start time of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
294
+ end_time: Optional[float]
295
+ """The end time of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
296
+
297
+ # Other parsable fields
298
+ context: Optional[SpanContext]
299
+ """The context of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
300
+ parent: Optional[SpanContext]
301
+ """The parent context of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
302
+ resource: OtelResource
303
+ """The resource of the span. See [OpenTelemetry docs](https://opentelemetry.io/docs/concepts/signals/traces/)."""
304
+
305
+ # Preserve other fields in the readable span as extra fields
306
+ # Make sure that are json serializable (so no bytes, complex objects, ...)
307
+
308
+ @classmethod
309
+ def from_opentelemetry(
310
+ cls,
311
+ src: ReadableSpan,
312
+ rollout_id: str,
313
+ attempt_id: str,
314
+ sequence_id: int,
315
+ ) -> "Span":
316
+ """Convert an OpenTelemetry span into the Mantisdk data model.
317
+
318
+ Args:
319
+ src: Span captured by OpenTelemetry.
320
+ rollout_id: Identifier for the rollout that produced the span.
321
+ attempt_id: Identifier of the attempt within the rollout.
322
+ sequence_id: Monotonically increasing identifier assigned to the span.
323
+
324
+ Returns:
325
+ Parsed [`Span`][mantisdk.Span] instance suitable for persistence.
326
+ """
327
+ context = src.get_span_context()
328
+ if context is None:
329
+ trace_id = span_id = 0
330
+ else:
331
+ trace_id = context.trace_id
332
+ span_id = context.span_id
333
+ return cls(
334
+ rollout_id=rollout_id,
335
+ attempt_id=attempt_id,
336
+ sequence_id=sequence_id,
337
+ trace_id=trace_api.format_trace_id(trace_id),
338
+ span_id=trace_api.format_span_id(span_id),
339
+ parent_id=(trace_api.format_span_id(src.parent.span_id) if src.parent else None),
340
+ name=src.name,
341
+ status=TraceStatus.from_opentelemetry(src.status),
342
+ attributes=dict(src.attributes) if src.attributes else {},
343
+ events=[Event.from_opentelemetry(event) for event in src.events] if src.events else [],
344
+ links=[Link.from_opentelemetry(link) for link in src.links] if src.links else [],
345
+ start_time=convert_timestamp(src.start_time),
346
+ end_time=convert_timestamp(src.end_time),
347
+ context=SpanContext.from_opentelemetry(context) if context else None,
348
+ parent=(SpanContext.from_opentelemetry(src.parent) if src.parent else None),
349
+ resource=OtelResource.from_opentelemetry(src.resource),
350
+ **extract_extra_fields(
351
+ src,
352
+ [
353
+ "name",
354
+ "context",
355
+ "parent",
356
+ "resource",
357
+ "attributes",
358
+ "events",
359
+ "links",
360
+ "start_time",
361
+ "end_time",
362
+ "status",
363
+ "span_processor",
364
+ "rollout_id",
365
+ "attempt_id",
366
+ "trace_id",
367
+ "span_id",
368
+ "parent_id",
369
+ ],
370
+ ),
371
+ )
372
+
373
+ @classmethod
374
+ def from_attributes(
375
+ cls,
376
+ *,
377
+ attributes: Attributes,
378
+ rollout_id: Optional[str] = None,
379
+ attempt_id: Optional[str] = None,
380
+ sequence_id: Optional[int] = None,
381
+ name: Optional[str] = None,
382
+ trace_id: Optional[str] = None,
383
+ span_id: Optional[str] = None,
384
+ parent_id: Optional[str] = None,
385
+ start_time: Optional[float] = None,
386
+ end_time: Optional[float] = None,
387
+ resource: Optional[OtelResource] = None,
388
+ status: Optional[TraceStatus] = None,
389
+ ) -> "Span":
390
+ """Build a synthetic span from raw attributes.
391
+ Different from the [`from_opentelemetry`][mantisdk.Span.from_opentelemetry] method,
392
+ all parameters other than `attributes` are optional and will be generated if not provided.
393
+
394
+ Args:
395
+ attributes: Span attributes to persist.
396
+ rollout_id: Optional rollout identifier associated with the span.
397
+ attempt_id: Optional attempt identifier associated with the span.
398
+ sequence_id: Optional sequence number to preserve ordering.
399
+ name: Optional human-readable span name.
400
+ trace_id: Custom trace identifier. When omitted, a random identifier is generated.
401
+ span_id: Custom span identifier. When omitted, a random identifier is generated.
402
+ parent_id: Optional parent span identifier.
403
+ start_time: Span start timestamp in seconds.
404
+ end_time: Span end timestamp in seconds.
405
+ resource: Explicit resource information to attach to the span.
406
+ status: Optional status of the span.
407
+
408
+ Returns:
409
+ [`Span`][mantisdk.Span] populated with the provided attributes.
410
+ """
411
+
412
+ id_generator = RandomIdGenerator()
413
+ trace_id = trace_id or trace_api.format_trace_id(id_generator.generate_trace_id())
414
+ span_id = span_id or trace_api.format_span_id(id_generator.generate_span_id())
415
+
416
+ return cls(
417
+ rollout_id=rollout_id or "",
418
+ attempt_id=attempt_id or "",
419
+ sequence_id=sequence_id or 0,
420
+ trace_id=trace_id,
421
+ span_id=span_id,
422
+ parent_id=parent_id,
423
+ start_time=start_time,
424
+ end_time=end_time,
425
+ context=SpanContext(
426
+ trace_id=trace_id,
427
+ span_id=span_id,
428
+ is_remote=False,
429
+ trace_state={},
430
+ ),
431
+ name=name or AGL_VIRTUAL,
432
+ resource=resource or OtelResource(attributes={}, schema_url=""),
433
+ attributes=attributes,
434
+ status=status or TraceStatus(status_code="OK"),
435
+ events=[],
436
+ links=[],
437
+ parent=(
438
+ SpanContext(
439
+ trace_id=trace_id,
440
+ span_id=parent_id,
441
+ is_remote=False,
442
+ trace_state={},
443
+ )
444
+ if parent_id
445
+ else None
446
+ ),
447
+ )
448
+
449
+ @classmethod
450
+ def from_core_fields(
451
+ cls,
452
+ core: SpanCoreFields,
453
+ *,
454
+ rollout_id: Optional[str] = None,
455
+ attempt_id: Optional[str] = None,
456
+ sequence_id: Optional[int] = None,
457
+ trace_id: Optional[str] = None,
458
+ ) -> Span:
459
+ """Build a span from a core span.
460
+
461
+ Args:
462
+ core: Core span to build from.
463
+ rollout_id: Optional rollout identifier associated with the span.
464
+ attempt_id: Optional attempt identifier associated with the span.
465
+ sequence_id: Optional sequence number to preserve ordering.
466
+ trace_id: Optional trace identifier. When omitted, a random identifier is generated.
467
+
468
+ Returns:
469
+ [`Span`][mantisdk.Span] populated with the provided attributes.
470
+ """
471
+ return cls.from_attributes(
472
+ attributes=core.attributes,
473
+ rollout_id=rollout_id,
474
+ attempt_id=attempt_id,
475
+ sequence_id=sequence_id,
476
+ trace_id=trace_id,
477
+ name=core.name,
478
+ start_time=core.start_time or time.time(),
479
+ end_time=core.end_time,
480
+ status=core.status,
481
+ )
482
+
483
+
484
+ class SpanNames(str, Enum):
485
+ """Enumerated span names recognised by Mantisdk. Deprecated in favor of [semconv][mantisdk.semconv]."""
486
+
487
+ REWARD = "mantisdk.reward"
488
+ """The name of the reward span."""
489
+ MESSAGE = "mantisdk.message"
490
+ """The name of the message span."""
491
+ OBJECT = "mantisdk.object"
492
+ """The name of the object span."""
493
+ EXCEPTION = "mantisdk.exception"
494
+ """The name of the exception span."""
495
+ VIRTUAL = "mantisdk.virtual"
496
+ """The name of the virtual span. It represents derived spans without concrete operations."""
497
+ ROLLOUT_ID = "mantisdk.rollout_id"
498
+ """The name of the rollout ID."""
499
+ ATTEMPT_ID = "mantisdk.attempt_id"
500
+ """The name of the attempt ID."""
501
+ SPAN_SEQUENCE_ID = "mantisdk.span_sequence_id"
502
+ """The name of the span sequence ID."""
503
+
504
+
505
+ class SpanAttributeNames(str, Enum):
506
+ """Canonical attribute names written by Mantisdk emitters. Deprecated in favor of [semconv][mantisdk.semconv]."""
507
+
508
+ MESSAGE = "message"
509
+ """The name of the message attribute."""
510
+ OBJECT = "object"
511
+ """The name of the object attribute."""
512
+
513
+
514
+ SpanLike = Union[ReadableSpan, Span]
515
+ """Union type of OpenTelemetry `ReadableSpan` and Mantisdk [`Span`][mantisdk.Span]."""