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
mantisdk/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .adapter import *
6
+ from .algorithm import *
7
+ from .client import MantisdkClient, DevTaskLoader # deprecated # type: ignore
8
+ from .config import *
9
+ from .emitter import *
10
+ from .env_var import *
11
+ from .execution import *
12
+ from .litagent import *
13
+ from .llm_proxy import *
14
+ from .logging import configure_logger # deprecated # type: ignore
15
+ from .logging import setup as setup_logging # type: ignore
16
+ from .logging import setup_module as setup_module_logging # type: ignore
17
+ from .runner import *
18
+ from .server import MantisdkServer # deprecated # type: ignore
19
+ from .store import *
20
+ from .tracer import *
21
+ from .trainer import *
22
+ from .types import *
@@ -0,0 +1,15 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from .base import Adapter, OtelTraceAdapter, TraceAdapter
4
+ from .messages import TraceToMessages
5
+ from .triplet import LlmProxyTraceToTriplet, TracerTraceToTriplet, TraceToTripletBase
6
+
7
+ __all__ = [
8
+ "TraceAdapter",
9
+ "OtelTraceAdapter",
10
+ "Adapter",
11
+ "TraceToTripletBase",
12
+ "TracerTraceToTriplet",
13
+ "LlmProxyTraceToTriplet",
14
+ "TraceToMessages",
15
+ ]
@@ -0,0 +1,94 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from typing import Generic, Sequence, TypeVar
4
+
5
+ from opentelemetry.sdk.trace import ReadableSpan
6
+
7
+ from mantisdk.types import Span
8
+
9
+ T_from = TypeVar("T_from")
10
+ T_to = TypeVar("T_to")
11
+
12
+
13
+ class Adapter(Generic[T_from, T_to]):
14
+ """Base class for synchronous adapters that convert data from one format to another.
15
+
16
+ The class defines a minimal protocol so that adapters can be treated like callables while
17
+ still allowing subclasses to supply the concrete transformation logic.
18
+
19
+ !!! note
20
+ Subclasses must override [`adapt()`][mantisdk.Adapter.adapt] to provide
21
+ the actual conversion.
22
+
23
+ Type Variables:
24
+
25
+ T_from: Source data type supplied to the adapter.
26
+
27
+ T_to: Target data type produced by the adapter.
28
+
29
+ Examples:
30
+ >>> class IntToStrAdapter(Adapter[int, str]):
31
+ ... def adapt(self, source: int) -> str:
32
+ ... return str(source)
33
+ ...
34
+ >>> adapter = IntToStrAdapter()
35
+ >>> adapter(42)
36
+ '42'
37
+ """
38
+
39
+ def __call__(self, source: T_from, /) -> T_to:
40
+ """Convert the data to the target format.
41
+
42
+ This method delegates to [`adapt()`][mantisdk.Adapter.adapt] so that an
43
+ instance of [`Adapter`][mantisdk.Adapter] can be used like a standard
44
+ function.
45
+
46
+ Args:
47
+ source: Input data in the source format.
48
+
49
+ Returns:
50
+ Data converted to the target format.
51
+ """
52
+ return self.adapt(source)
53
+
54
+ def adapt(self, source: T_from, /) -> T_to:
55
+ """Convert the data to the target format.
56
+
57
+ Subclasses must override this method with the concrete transformation logic. The base
58
+ implementation raises `NotImplementedError` to make the requirement explicit.
59
+
60
+ Args:
61
+ source: Input data in the source format.
62
+
63
+ Returns:
64
+ Data converted to the target format.
65
+ """
66
+ raise NotImplementedError("Adapter.adapt() is not implemented")
67
+
68
+
69
+ class OtelTraceAdapter(Adapter[Sequence[ReadableSpan], T_to], Generic[T_to]):
70
+ """Base class for adapters that convert OpenTelemetry trace spans into other formats.
71
+
72
+ This specialization of [`Adapter`][mantisdk.Adapter] expects a list of
73
+ `opentelemetry.sdk.trace.ReadableSpan` instances and produces any target format, such as
74
+ reinforcement learning trajectories, structured logs, or analytics-ready payloads.
75
+
76
+ Examples:
77
+ >>> class TraceToDictAdapter(OtelTraceAdapter[dict]):
78
+ ... def adapt(self, spans: List[ReadableSpan]) -> dict:
79
+ ... return {"count": len(spans)}
80
+ ...
81
+ >>> adapter = TraceToDictAdapter()
82
+ >>> adapter([span1, span2])
83
+ {'count': 2}
84
+ """
85
+
86
+
87
+ class TraceAdapter(Adapter[Sequence[Span], T_to], Generic[T_to]):
88
+ """Base class for adapters that convert trace spans into other formats.
89
+
90
+ This class specializes [`Adapter`][mantisdk.Adapter] for working with
91
+ [`Span`][mantisdk.Span] instances emitted by Mantisdk instrumentation.
92
+ Subclasses receive entire trace slices and return a format suited for the downstream consumer,
93
+ for example reinforcement learning training data or observability metrics.
94
+ """
@@ -0,0 +1,270 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections import defaultdict
7
+ from typing import TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Optional, Sequence, TypedDict, Union, cast
8
+
9
+ from pydantic import TypeAdapter
10
+
11
+ from mantisdk.types import Span
12
+
13
+ from .base import TraceAdapter
14
+
15
+ if TYPE_CHECKING:
16
+ from openai.types.chat import (
17
+ ChatCompletionFunctionToolParam,
18
+ ChatCompletionMessageFunctionToolCallParam,
19
+ ChatCompletionMessageParam,
20
+ )
21
+
22
+
23
+ class OpenAIMessages(TypedDict):
24
+ """OpenAI-style chat messages with optional tool definitions.
25
+
26
+ Attributes:
27
+ messages: Ordered chat messages that describe the conversation.
28
+ tools: Tool specifications available to the assistant, if any.
29
+ """
30
+
31
+ messages: List[ChatCompletionMessageParam]
32
+ tools: Optional[List[ChatCompletionFunctionToolParam]]
33
+
34
+
35
+ class _RawSpanInfo(TypedDict):
36
+ """Intermediate representation parsed from a span.
37
+
38
+ Attributes:
39
+ prompt: Prompt messages reconstructed from span attributes.
40
+ completion: Assistant completions following tool invocations.
41
+ request: Request payload recorded in the trace.
42
+ response: Response payload recorded in the trace.
43
+ tools: Tool call metadata extracted from child spans.
44
+ """
45
+
46
+ prompt: List[Dict[str, Any]]
47
+ completion: List[Dict[str, Any]]
48
+ request: Dict[str, Any]
49
+ response: Dict[str, Any]
50
+ tools: List[Dict[str, Any]]
51
+
52
+
53
+ def group_genai_dict(data: Dict[str, Any], prefix: str) -> Union[Dict[str, Any], List[Any]]:
54
+ """Convert flattened trace attributes into nested structures.
55
+
56
+ Attributes emitted by the tracing pipeline often arrive as dotted paths (for example
57
+ `gen_ai.prompt.0.role`). This helper groups those keys into nested dictionaries or lists so that
58
+ downstream processing can operate on structured data.
59
+
60
+ Args:
61
+ data: Flat dictionary whose keys are dotted paths.
62
+ prefix: Top-level key (for example `gen_ai.prompt`) that determines which attributes are
63
+ grouped.
64
+
65
+ Returns:
66
+ A nested dictionary (no numeric index detected) or list (numeric indices detected) containing
67
+ the grouped values.
68
+ """
69
+ result: Union[Dict[str, Any], List[Any]] = {}
70
+
71
+ # Collect keys that match the prefix
72
+ relevant = {k[len(prefix) + 1 :]: v for k, v in data.items() if k.startswith(prefix + ".")}
73
+
74
+ # Detect if we have numeric indices (-> list) or not (-> dict)
75
+ indexed = any(part.split(".")[0].isdigit() for part in relevant.keys())
76
+
77
+ if indexed:
78
+ # Group by index
79
+ grouped: Dict[int, Dict[str, Any]] = defaultdict(dict)
80
+ for k, v in relevant.items():
81
+ parts = k.split(".")
82
+ if not parts[0].isdigit():
83
+ continue
84
+ idx, rest = int(parts[0]), ".".join(parts[1:])
85
+ grouped[idx][rest] = v
86
+ # Recursively build
87
+ result = []
88
+ for i in sorted(grouped.keys()):
89
+ result.append(group_genai_dict({f"{prefix}.{rest}": val for rest, val in grouped[i].items()}, prefix))
90
+ else:
91
+ # No indices: build dict
92
+ nested: Dict[str, Any] = defaultdict(dict)
93
+ for k, v in relevant.items():
94
+ if "." in k:
95
+ head, _tail = k.split(".", 1)
96
+ nested[head][f"{prefix}.{k}"] = v
97
+ else:
98
+ result[k] = v
99
+ # Recurse into nested dicts
100
+ for head, subdict in nested.items():
101
+ result[head] = group_genai_dict(subdict, prefix + "." + head)
102
+
103
+ return result
104
+
105
+
106
+ def convert_to_openai_messages(prompt_completion_list: List[_RawSpanInfo]) -> Generator[OpenAIMessages, None, None]:
107
+ """Convert raw trace payloads into OpenAI-style chat messages.
108
+
109
+ The function consumes an iterable produced by
110
+ [`TraceToMessages.adapt()`][mantisdk.TraceToMessages.adapt] and yields
111
+ structures that match the OpenAI fine-tuning JSONL schema, including tool definitions.
112
+
113
+ Args:
114
+ prompt_completion_list: Raw prompt/completion/tool payloads extracted from a trace.
115
+
116
+ Returns:
117
+ A generator that yields [`OpenAIMessages`][mantisdk.adapter.messages.OpenAIMessages]
118
+ entries compatible with the OpenAI Functions fine-tuning format.
119
+ """
120
+
121
+ # Import locally to avoid legacy OpenAI version type import errors
122
+ from openai.types.chat import (
123
+ ChatCompletionAssistantMessageParam,
124
+ ChatCompletionFunctionToolParam,
125
+ ChatCompletionMessageFunctionToolCallParam,
126
+ ChatCompletionMessageParam,
127
+ )
128
+
129
+ for pc_entry in prompt_completion_list:
130
+ messages: List[ChatCompletionMessageParam] = []
131
+
132
+ # Extract messages
133
+ for msg in pc_entry["prompt"]:
134
+ role = msg["role"]
135
+
136
+ if role == "assistant" and "tool_calls" in msg:
137
+ # Use the tool_calls directly
138
+ # This branch is usually not used in the wild.
139
+ tool_calls: List[ChatCompletionMessageFunctionToolCallParam] = [
140
+ ChatCompletionMessageFunctionToolCallParam(
141
+ id=call["id"],
142
+ type="function",
143
+ function={"name": call["name"], "arguments": call["arguments"]},
144
+ )
145
+ for call in msg["tool_calls"]
146
+ ]
147
+ messages.append(
148
+ ChatCompletionAssistantMessageParam(role="assistant", content=None, tool_calls=tool_calls)
149
+ )
150
+ else:
151
+ # Normal user/system/tool content
152
+ message = cast(
153
+ ChatCompletionMessageParam,
154
+ TypeAdapter(ChatCompletionMessageParam).validate_python(
155
+ dict(role=role, content=msg.get("content", ""), tool_call_id=msg.get("tool_call_id", None))
156
+ ),
157
+ )
158
+ messages.append(message)
159
+
160
+ # Extract completions (assistant outputs after tool responses)
161
+ for comp in pc_entry["completion"]:
162
+ if comp.get("role") == "assistant":
163
+ content = comp.get("content")
164
+ if pc_entry["tools"]:
165
+ tool_calls = [
166
+ ChatCompletionMessageFunctionToolCallParam(
167
+ id=tool["call"]["id"],
168
+ type=tool["call"]["type"],
169
+ function={"name": tool["name"], "arguments": tool["parameters"]},
170
+ )
171
+ for tool in pc_entry["tools"]
172
+ ]
173
+ messages.append(
174
+ ChatCompletionAssistantMessageParam(role="assistant", content=content, tool_calls=tool_calls)
175
+ )
176
+ else:
177
+ messages.append(ChatCompletionAssistantMessageParam(role="assistant", content=content))
178
+
179
+ # Build tools definitions (if available)
180
+ if "functions" in pc_entry["request"]:
181
+ tools = [
182
+ ChatCompletionFunctionToolParam(
183
+ type="function",
184
+ function={
185
+ "name": fn["name"],
186
+ "description": fn.get("description", ""),
187
+ "parameters": (
188
+ json.loads(fn["parameters"]) if isinstance(fn["parameters"], str) else fn["parameters"]
189
+ ),
190
+ },
191
+ )
192
+ for fn in pc_entry["request"]["functions"]
193
+ ]
194
+ yield OpenAIMessages(messages=messages, tools=tools)
195
+ else:
196
+ yield OpenAIMessages(messages=messages, tools=None)
197
+
198
+
199
+ class TraceToMessages(TraceAdapter[List[OpenAIMessages]]):
200
+ """Convert trace spans into OpenAI-compatible conversation messages.
201
+
202
+ The adapter reconstructs prompts, completions, tool calls, and function definitions from
203
+ `gen_ai.*` span attributes. The resulting objects match the JSONL structure expected by the
204
+ OpenAI fine-tuning pipeline.
205
+
206
+ !!! warning
207
+ The adapter assumes all spans share a common trace and that tool call spans are direct
208
+ children of the associated completion span.
209
+ """
210
+
211
+ def get_tool_calls(self, completion: Span, all_spans: Sequence[Span], /) -> Iterable[Dict[str, Any]]:
212
+ """Yield tool call payloads for a completion span.
213
+
214
+ Args:
215
+ completion: The completion span whose descendants should be inspected.
216
+ all_spans: The complete span list belonging to the trace.
217
+
218
+ Yields:
219
+ Dictionaries describing tool calls with identifiers, names, and arguments.
220
+
221
+ Raises:
222
+ ValueError: If a candidate tool span cannot be converted into a dictionary.
223
+ """
224
+ # Get all the spans that are children of the completion span
225
+ children = [span for span in all_spans if span.parent_id == completion.span_id]
226
+ # Get the tool calls from the children
227
+ for maybe_tool_call in children:
228
+ tool_call = group_genai_dict(maybe_tool_call.attributes, "tool")
229
+ if not isinstance(tool_call, dict):
230
+ raise ValueError(f"Extracted tool call from trace is not a dict: {tool_call}")
231
+ if tool_call:
232
+ yield tool_call
233
+
234
+ def adapt(self, source: Sequence[Span], /) -> List[OpenAIMessages]:
235
+ """Transform trace spans into OpenAI chat payloads.
236
+
237
+ Args:
238
+ source: Spans containing `gen_ai.*` attributes emitted by the tracing pipeline.
239
+
240
+ Returns:
241
+ A list of [`OpenAIMessages`][mantisdk.adapter.messages.OpenAIMessages] entries that
242
+ capture prompts, completions, tools, and metadata.
243
+ """
244
+ raw_prompt_completions: List[_RawSpanInfo] = []
245
+
246
+ for span in source:
247
+ attributes = {k: v for k, v in span.attributes.items()}
248
+
249
+ # Get all related information from the trace span
250
+ prompt = group_genai_dict(attributes, "gen_ai.prompt") or []
251
+ completion = group_genai_dict(attributes, "gen_ai.completion") or []
252
+ request = group_genai_dict(attributes, "gen_ai.request") or {}
253
+ response = group_genai_dict(attributes, "gen_ai.response") or {}
254
+ if not isinstance(prompt, list):
255
+ raise ValueError(f"Extracted prompt from trace is not a list: {prompt}")
256
+ if not isinstance(completion, list):
257
+ raise ValueError(f"Extracted completion from trace is not a list: {completion}")
258
+ if not isinstance(request, dict):
259
+ raise ValueError(f"Extracted request from trace is not a dict: {request}")
260
+ if not isinstance(response, dict):
261
+ raise ValueError(f"Extracted response from trace is not a dict: {response}")
262
+ if prompt or completion or request or response:
263
+ tools = list(self.get_tool_calls(span, source)) or []
264
+ raw_prompt_completions.append(
265
+ _RawSpanInfo(
266
+ prompt=prompt or [], completion=completion, request=request, response=response, tools=tools
267
+ )
268
+ )
269
+
270
+ return list(convert_to_openai_messages(raw_prompt_completions))