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
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
|
+
]
|
mantisdk/adapter/base.py
ADDED
|
@@ -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))
|