synkro 0.4.12__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.
- synkro/__init__.py +179 -0
- synkro/advanced.py +186 -0
- synkro/cli.py +128 -0
- synkro/core/__init__.py +7 -0
- synkro/core/checkpoint.py +250 -0
- synkro/core/dataset.py +402 -0
- synkro/core/policy.py +337 -0
- synkro/errors.py +178 -0
- synkro/examples/__init__.py +148 -0
- synkro/factory.py +276 -0
- synkro/formatters/__init__.py +12 -0
- synkro/formatters/qa.py +98 -0
- synkro/formatters/sft.py +90 -0
- synkro/formatters/tool_call.py +127 -0
- synkro/generation/__init__.py +9 -0
- synkro/generation/follow_ups.py +134 -0
- synkro/generation/generator.py +220 -0
- synkro/generation/golden_responses.py +244 -0
- synkro/generation/golden_scenarios.py +276 -0
- synkro/generation/golden_tool_responses.py +416 -0
- synkro/generation/logic_extractor.py +126 -0
- synkro/generation/multiturn_responses.py +177 -0
- synkro/generation/planner.py +131 -0
- synkro/generation/responses.py +189 -0
- synkro/generation/scenarios.py +90 -0
- synkro/generation/tool_responses.py +376 -0
- synkro/generation/tool_simulator.py +114 -0
- synkro/interactive/__init__.py +12 -0
- synkro/interactive/hitl_session.py +77 -0
- synkro/interactive/logic_map_editor.py +173 -0
- synkro/interactive/rich_ui.py +205 -0
- synkro/llm/__init__.py +7 -0
- synkro/llm/client.py +235 -0
- synkro/llm/rate_limits.py +95 -0
- synkro/models/__init__.py +43 -0
- synkro/models/anthropic.py +26 -0
- synkro/models/google.py +19 -0
- synkro/models/openai.py +31 -0
- synkro/modes/__init__.py +15 -0
- synkro/modes/config.py +66 -0
- synkro/modes/qa.py +18 -0
- synkro/modes/sft.py +18 -0
- synkro/modes/tool_call.py +18 -0
- synkro/parsers.py +442 -0
- synkro/pipeline/__init__.py +20 -0
- synkro/pipeline/phases.py +592 -0
- synkro/pipeline/runner.py +424 -0
- synkro/pipelines.py +123 -0
- synkro/prompts/__init__.py +57 -0
- synkro/prompts/base.py +167 -0
- synkro/prompts/golden_templates.py +474 -0
- synkro/prompts/interactive_templates.py +65 -0
- synkro/prompts/multiturn_templates.py +156 -0
- synkro/prompts/qa_templates.py +97 -0
- synkro/prompts/templates.py +281 -0
- synkro/prompts/tool_templates.py +201 -0
- synkro/quality/__init__.py +14 -0
- synkro/quality/golden_refiner.py +163 -0
- synkro/quality/grader.py +153 -0
- synkro/quality/multiturn_grader.py +150 -0
- synkro/quality/refiner.py +137 -0
- synkro/quality/tool_grader.py +126 -0
- synkro/quality/tool_refiner.py +128 -0
- synkro/quality/verifier.py +228 -0
- synkro/reporting.py +537 -0
- synkro/schemas.py +472 -0
- synkro/types/__init__.py +41 -0
- synkro/types/core.py +126 -0
- synkro/types/dataset_type.py +30 -0
- synkro/types/logic_map.py +345 -0
- synkro/types/tool.py +94 -0
- synkro-0.4.12.data/data/examples/__init__.py +148 -0
- synkro-0.4.12.dist-info/METADATA +258 -0
- synkro-0.4.12.dist-info/RECORD +77 -0
- synkro-0.4.12.dist-info/WHEEL +4 -0
- synkro-0.4.12.dist-info/entry_points.txt +2 -0
- synkro-0.4.12.dist-info/licenses/LICENSE +21 -0
synkro/factory.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Component factory for dependency injection.
|
|
2
|
+
|
|
3
|
+
This module provides a factory for creating pipeline components,
|
|
4
|
+
enabling testability and flexible configuration.
|
|
5
|
+
|
|
6
|
+
Supports both legacy components and Golden Trace components:
|
|
7
|
+
- Logic Extractor (The Cartographer)
|
|
8
|
+
- Golden Scenario Generator (The Adversary)
|
|
9
|
+
- Golden Response Generator (The Thinker)
|
|
10
|
+
- Trace Verifier (The Auditor)
|
|
11
|
+
- Golden Refiner
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
from synkro.llm.client import LLM
|
|
17
|
+
from synkro.modes.config import ModeConfig
|
|
18
|
+
from synkro.generation.planner import Planner
|
|
19
|
+
from synkro.generation.scenarios import ScenarioGenerator
|
|
20
|
+
from synkro.generation.responses import ResponseGenerator
|
|
21
|
+
from synkro.generation.follow_ups import FollowUpGenerator
|
|
22
|
+
from synkro.generation.multiturn_responses import MultiTurnResponseGenerator
|
|
23
|
+
from synkro.quality.grader import Grader
|
|
24
|
+
from synkro.quality.refiner import Refiner
|
|
25
|
+
from synkro.quality.multiturn_grader import MultiTurnGrader
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from synkro.types.tool import ToolDefinition
|
|
29
|
+
from synkro.generation.tool_simulator import ToolSimulator
|
|
30
|
+
from synkro.generation.tool_responses import ToolCallResponseGenerator
|
|
31
|
+
from synkro.quality.tool_grader import ToolCallGrader
|
|
32
|
+
from synkro.quality.tool_refiner import ToolCallRefiner
|
|
33
|
+
from synkro.generation.logic_extractor import LogicExtractor
|
|
34
|
+
from synkro.generation.golden_scenarios import GoldenScenarioGenerator
|
|
35
|
+
from synkro.generation.golden_responses import GoldenResponseGenerator
|
|
36
|
+
from synkro.generation.golden_tool_responses import GoldenToolCallResponseGenerator
|
|
37
|
+
from synkro.quality.verifier import TraceVerifier
|
|
38
|
+
from synkro.quality.golden_refiner import GoldenRefiner
|
|
39
|
+
from synkro.interactive.logic_map_editor import LogicMapEditor
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ComponentFactory:
|
|
43
|
+
"""
|
|
44
|
+
Factory for creating pipeline components with shared LLM clients.
|
|
45
|
+
|
|
46
|
+
This centralizes component creation and ensures consistent configuration
|
|
47
|
+
across the pipeline.
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
>>> factory = ComponentFactory(gen_llm, grade_llm, mode_config)
|
|
51
|
+
>>> planner = factory.create_planner()
|
|
52
|
+
>>> grader = factory.create_grader()
|
|
53
|
+
|
|
54
|
+
>>> # With tools for tool_call dataset type
|
|
55
|
+
>>> factory = ComponentFactory(gen_llm, grade_llm, mode_config, tools=[...])
|
|
56
|
+
>>> simulator = factory.create_tool_simulator()
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
generation_llm: LLM,
|
|
62
|
+
grading_llm: LLM,
|
|
63
|
+
mode_config: ModeConfig,
|
|
64
|
+
tools: list["ToolDefinition"] | None = None,
|
|
65
|
+
):
|
|
66
|
+
"""
|
|
67
|
+
Initialize the factory.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
generation_llm: LLM client for generation tasks (scenarios, responses, refinement)
|
|
71
|
+
grading_llm: LLM client for grading and planning (typically stronger model)
|
|
72
|
+
mode_config: Configuration for the dataset type (prompts, etc.)
|
|
73
|
+
tools: Optional list of tool definitions for tool_call dataset type
|
|
74
|
+
"""
|
|
75
|
+
self.generation_llm = generation_llm
|
|
76
|
+
self.grading_llm = grading_llm
|
|
77
|
+
self.mode_config = mode_config
|
|
78
|
+
self.tools = tools or []
|
|
79
|
+
|
|
80
|
+
def create_planner(self) -> Planner:
|
|
81
|
+
"""Create a Planner instance."""
|
|
82
|
+
return Planner(llm=self.grading_llm)
|
|
83
|
+
|
|
84
|
+
def create_scenario_generator(self) -> ScenarioGenerator:
|
|
85
|
+
"""Create a ScenarioGenerator with mode-specific prompts."""
|
|
86
|
+
gen = ScenarioGenerator(llm=self.generation_llm)
|
|
87
|
+
gen.prompt_template = self.mode_config.scenario_prompt
|
|
88
|
+
return gen
|
|
89
|
+
|
|
90
|
+
def create_response_generator(self) -> ResponseGenerator:
|
|
91
|
+
"""Create a ResponseGenerator with mode-specific prompts."""
|
|
92
|
+
gen = ResponseGenerator(llm=self.generation_llm)
|
|
93
|
+
gen.prompt_template = self.mode_config.response_prompt
|
|
94
|
+
return gen
|
|
95
|
+
|
|
96
|
+
def create_grader(self) -> "Grader | ToolCallGrader":
|
|
97
|
+
"""
|
|
98
|
+
Create a Grader with mode-specific prompts.
|
|
99
|
+
|
|
100
|
+
Auto-selects ToolCallGrader when tools are configured.
|
|
101
|
+
"""
|
|
102
|
+
if self.has_tools:
|
|
103
|
+
from synkro.quality.tool_grader import ToolCallGrader
|
|
104
|
+
return ToolCallGrader(llm=self.grading_llm, tools=self.tools)
|
|
105
|
+
|
|
106
|
+
grader = Grader(llm=self.grading_llm)
|
|
107
|
+
grader.prompt_template = self.mode_config.grade_prompt
|
|
108
|
+
return grader
|
|
109
|
+
|
|
110
|
+
def create_refiner(self) -> "Refiner | ToolCallRefiner":
|
|
111
|
+
"""
|
|
112
|
+
Create a Refiner with mode-specific prompts.
|
|
113
|
+
|
|
114
|
+
Auto-selects ToolCallRefiner when tools are configured.
|
|
115
|
+
This ensures tool_calls format is preserved during refinement.
|
|
116
|
+
"""
|
|
117
|
+
if self.has_tools:
|
|
118
|
+
from synkro.quality.tool_refiner import ToolCallRefiner
|
|
119
|
+
simulator = self.create_tool_simulator()
|
|
120
|
+
return ToolCallRefiner(
|
|
121
|
+
llm=self.generation_llm,
|
|
122
|
+
tools=self.tools,
|
|
123
|
+
simulator=simulator,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
refiner = Refiner(llm=self.generation_llm)
|
|
127
|
+
refiner.prompt_template = self.mode_config.refine_prompt
|
|
128
|
+
return refiner
|
|
129
|
+
|
|
130
|
+
def create_tool_simulator(self) -> "ToolSimulator":
|
|
131
|
+
"""Create a ToolSimulator instance for tool_call dataset type."""
|
|
132
|
+
from synkro.generation.tool_simulator import ToolSimulator
|
|
133
|
+
|
|
134
|
+
if not self.tools:
|
|
135
|
+
raise ValueError("Cannot create ToolSimulator without tools")
|
|
136
|
+
|
|
137
|
+
return ToolSimulator(tools=self.tools, llm=self.generation_llm)
|
|
138
|
+
|
|
139
|
+
def create_tool_call_response_generator(self) -> "ToolCallResponseGenerator":
|
|
140
|
+
"""
|
|
141
|
+
Create a ToolCallResponseGenerator for generating proper tool call traces.
|
|
142
|
+
|
|
143
|
+
This generator uses JSON mode to produce structured tool calls in
|
|
144
|
+
OpenAI function calling format.
|
|
145
|
+
"""
|
|
146
|
+
from synkro.generation.tool_responses import ToolCallResponseGenerator
|
|
147
|
+
|
|
148
|
+
if not self.tools:
|
|
149
|
+
raise ValueError("Cannot create ToolCallResponseGenerator without tools")
|
|
150
|
+
|
|
151
|
+
# Create simulator for generating tool responses
|
|
152
|
+
simulator = self.create_tool_simulator()
|
|
153
|
+
|
|
154
|
+
return ToolCallResponseGenerator(
|
|
155
|
+
tools=self.tools,
|
|
156
|
+
llm=self.generation_llm,
|
|
157
|
+
simulator=simulator,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def get_tools_description(self) -> str:
|
|
161
|
+
"""Get formatted description of all available tools."""
|
|
162
|
+
if not self.tools:
|
|
163
|
+
return "No tools available"
|
|
164
|
+
|
|
165
|
+
descriptions = []
|
|
166
|
+
for tool in self.tools:
|
|
167
|
+
descriptions.append(tool.to_system_prompt())
|
|
168
|
+
return "\n\n".join(descriptions)
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def has_tools(self) -> bool:
|
|
172
|
+
"""Check if tools are configured."""
|
|
173
|
+
return bool(self.tools)
|
|
174
|
+
|
|
175
|
+
def create_follow_up_generator(self) -> FollowUpGenerator:
|
|
176
|
+
"""Create a FollowUpGenerator for multi-turn conversations."""
|
|
177
|
+
return FollowUpGenerator(llm=self.generation_llm)
|
|
178
|
+
|
|
179
|
+
def create_multi_turn_response_generator(self) -> MultiTurnResponseGenerator:
|
|
180
|
+
"""Create a MultiTurnResponseGenerator for multi-turn trace generation."""
|
|
181
|
+
return MultiTurnResponseGenerator(llm=self.generation_llm)
|
|
182
|
+
|
|
183
|
+
def create_multi_turn_grader(self) -> MultiTurnGrader:
|
|
184
|
+
"""Create a MultiTurnGrader for per-turn and overall conversation grading."""
|
|
185
|
+
return MultiTurnGrader(llm=self.grading_llm)
|
|
186
|
+
|
|
187
|
+
# =========================================================================
|
|
188
|
+
# GOLDEN TRACE COMPONENTS
|
|
189
|
+
# =========================================================================
|
|
190
|
+
|
|
191
|
+
def create_logic_extractor(self) -> "LogicExtractor":
|
|
192
|
+
"""
|
|
193
|
+
Create a LogicExtractor (The Cartographer).
|
|
194
|
+
|
|
195
|
+
Uses the grading LLM (stronger model) for accurate rule extraction.
|
|
196
|
+
"""
|
|
197
|
+
from synkro.generation.logic_extractor import LogicExtractor
|
|
198
|
+
return LogicExtractor(llm=self.grading_llm)
|
|
199
|
+
|
|
200
|
+
def create_golden_scenario_generator(self) -> "GoldenScenarioGenerator":
|
|
201
|
+
"""
|
|
202
|
+
Create a GoldenScenarioGenerator (The Adversary).
|
|
203
|
+
|
|
204
|
+
Generates typed scenarios (positive, negative, edge_case, irrelevant)
|
|
205
|
+
with rule targeting.
|
|
206
|
+
"""
|
|
207
|
+
from synkro.generation.golden_scenarios import GoldenScenarioGenerator
|
|
208
|
+
return GoldenScenarioGenerator(llm=self.generation_llm)
|
|
209
|
+
|
|
210
|
+
def create_golden_response_generator(self) -> "GoldenResponseGenerator":
|
|
211
|
+
"""
|
|
212
|
+
Create a GoldenResponseGenerator (The Thinker).
|
|
213
|
+
|
|
214
|
+
Generates traces with grounded Chain-of-Thought reasoning
|
|
215
|
+
and rule citations.
|
|
216
|
+
"""
|
|
217
|
+
from synkro.generation.golden_responses import GoldenResponseGenerator
|
|
218
|
+
return GoldenResponseGenerator(llm=self.generation_llm)
|
|
219
|
+
|
|
220
|
+
def create_golden_tool_call_generator(self) -> "GoldenToolCallResponseGenerator":
|
|
221
|
+
"""
|
|
222
|
+
Create a GoldenToolCallResponseGenerator (The Thinker for Tools).
|
|
223
|
+
|
|
224
|
+
Generates tool call traces with rule citations for tool selection
|
|
225
|
+
decisions. Requires tools to be configured.
|
|
226
|
+
"""
|
|
227
|
+
from synkro.generation.golden_tool_responses import GoldenToolCallResponseGenerator
|
|
228
|
+
|
|
229
|
+
if not self.tools:
|
|
230
|
+
raise ValueError("Cannot create GoldenToolCallResponseGenerator without tools")
|
|
231
|
+
|
|
232
|
+
simulator = self.create_tool_simulator()
|
|
233
|
+
return GoldenToolCallResponseGenerator(
|
|
234
|
+
tools=self.tools,
|
|
235
|
+
llm=self.generation_llm,
|
|
236
|
+
simulator=simulator,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def create_verifier(self) -> "TraceVerifier":
|
|
240
|
+
"""
|
|
241
|
+
Create a TraceVerifier (The Auditor).
|
|
242
|
+
|
|
243
|
+
Verifies traces against the Logic Map to ensure:
|
|
244
|
+
- No skipped rules
|
|
245
|
+
- No hallucinated rules
|
|
246
|
+
- No contradictions
|
|
247
|
+
- DAG compliance
|
|
248
|
+
|
|
249
|
+
Uses the grading LLM (stronger model) for accurate verification.
|
|
250
|
+
"""
|
|
251
|
+
from synkro.quality.verifier import TraceVerifier
|
|
252
|
+
return TraceVerifier(llm=self.grading_llm)
|
|
253
|
+
|
|
254
|
+
def create_golden_refiner(self) -> "GoldenRefiner":
|
|
255
|
+
"""
|
|
256
|
+
Create a GoldenRefiner.
|
|
257
|
+
|
|
258
|
+
Refines traces that fail verification, using Logic Map context
|
|
259
|
+
to fix skipped rules, hallucinations, and contradictions.
|
|
260
|
+
"""
|
|
261
|
+
from synkro.quality.golden_refiner import GoldenRefiner
|
|
262
|
+
return GoldenRefiner(llm=self.generation_llm)
|
|
263
|
+
|
|
264
|
+
def create_logic_map_editor(self) -> "LogicMapEditor":
|
|
265
|
+
"""
|
|
266
|
+
Create a LogicMapEditor for Human-in-the-Loop sessions.
|
|
267
|
+
|
|
268
|
+
The editor uses the grading LLM (stronger model) to interpret
|
|
269
|
+
natural language feedback and refine Logic Maps.
|
|
270
|
+
"""
|
|
271
|
+
from synkro.interactive.logic_map_editor import LogicMapEditor
|
|
272
|
+
return LogicMapEditor(llm=self.grading_llm)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
__all__ = ["ComponentFactory"]
|
|
276
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Output formatters for different training data formats."""
|
|
2
|
+
|
|
3
|
+
from synkro.formatters.sft import SFTFormatter
|
|
4
|
+
from synkro.formatters.qa import QAFormatter
|
|
5
|
+
from synkro.formatters.tool_call import ToolCallFormatter
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"SFTFormatter",
|
|
9
|
+
"QAFormatter",
|
|
10
|
+
"ToolCallFormatter",
|
|
11
|
+
]
|
|
12
|
+
|
synkro/formatters/qa.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""QA (Question-Answer) formatter."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from synkro.types.core import Trace
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QAFormatter:
|
|
12
|
+
"""
|
|
13
|
+
Format traces for Question-Answer datasets.
|
|
14
|
+
|
|
15
|
+
Outputs OpenAI-compatible messages format for finetuning compatibility
|
|
16
|
+
with OpenAI, TogetherAI, and similar platforms.
|
|
17
|
+
|
|
18
|
+
Example output:
|
|
19
|
+
{"messages": [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}
|
|
20
|
+
|
|
21
|
+
For multi-turn traces, all messages are included in the output.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, include_context: bool = True, include_metadata: bool = False):
|
|
25
|
+
"""
|
|
26
|
+
Initialize the QA formatter.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
include_context: If True, include context in system message
|
|
30
|
+
include_metadata: If True, include scenario metadata
|
|
31
|
+
"""
|
|
32
|
+
self.include_context = include_context
|
|
33
|
+
self.include_metadata = include_metadata
|
|
34
|
+
|
|
35
|
+
def format(self, traces: list["Trace"]) -> list[dict]:
|
|
36
|
+
"""
|
|
37
|
+
Format traces as OpenAI-compatible messages format.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
traces: List of traces to format
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
List of examples with 'messages' key containing role/content dicts
|
|
44
|
+
"""
|
|
45
|
+
examples = []
|
|
46
|
+
|
|
47
|
+
for trace in traces:
|
|
48
|
+
# Build messages list from trace
|
|
49
|
+
messages = []
|
|
50
|
+
for msg in trace.messages:
|
|
51
|
+
message_dict = {
|
|
52
|
+
"role": msg.role,
|
|
53
|
+
"content": msg.content or "",
|
|
54
|
+
}
|
|
55
|
+
messages.append(message_dict)
|
|
56
|
+
|
|
57
|
+
example = {"messages": messages}
|
|
58
|
+
|
|
59
|
+
if self.include_metadata:
|
|
60
|
+
example["metadata"] = {
|
|
61
|
+
"scenario": trace.scenario.description,
|
|
62
|
+
"context": trace.scenario.context,
|
|
63
|
+
"category": trace.scenario.category,
|
|
64
|
+
"turn_count": sum(1 for m in trace.messages if m.role == "assistant"),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
examples.append(example)
|
|
68
|
+
|
|
69
|
+
return examples
|
|
70
|
+
|
|
71
|
+
def save(self, traces: list["Trace"], path: str | Path) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Save formatted traces to a JSONL file.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
traces: List of traces to save
|
|
77
|
+
path: Output file path
|
|
78
|
+
"""
|
|
79
|
+
path = Path(path)
|
|
80
|
+
examples = self.format(traces)
|
|
81
|
+
|
|
82
|
+
with open(path, "w") as f:
|
|
83
|
+
for example in examples:
|
|
84
|
+
f.write(json.dumps(example) + "\n")
|
|
85
|
+
|
|
86
|
+
def to_jsonl(self, traces: list["Trace"]) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Convert traces to JSONL string.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
traces: List of traces to convert
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
JSONL formatted string
|
|
95
|
+
"""
|
|
96
|
+
examples = self.format(traces)
|
|
97
|
+
return "\n".join(json.dumps(e) for e in examples)
|
|
98
|
+
|
synkro/formatters/sft.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""SFT (Supervised Fine-Tuning) formatter."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from synkro.types.core import Trace
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SFTFormatter:
|
|
12
|
+
"""
|
|
13
|
+
Format traces for Supervised Fine-Tuning (SFT).
|
|
14
|
+
|
|
15
|
+
SFT format is a simple array of conversations, each with messages.
|
|
16
|
+
This is the standard format used by OpenAI, HuggingFace, and most
|
|
17
|
+
fine-tuning platforms.
|
|
18
|
+
|
|
19
|
+
Example output:
|
|
20
|
+
{"messages": [{"role": "system", "content": "..."}, ...]}
|
|
21
|
+
{"messages": [{"role": "system", "content": "..."}, ...]}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, include_metadata: bool = False):
|
|
25
|
+
"""
|
|
26
|
+
Initialize the SFT formatter.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
include_metadata: If True, include trace metadata in output
|
|
30
|
+
"""
|
|
31
|
+
self.include_metadata = include_metadata
|
|
32
|
+
|
|
33
|
+
def format(self, traces: list["Trace"]) -> list[dict]:
|
|
34
|
+
"""
|
|
35
|
+
Format traces as SFT training examples.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
traces: List of traces to format
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of SFT examples (dicts with 'messages' key)
|
|
42
|
+
"""
|
|
43
|
+
examples = []
|
|
44
|
+
|
|
45
|
+
for trace in traces:
|
|
46
|
+
example = {
|
|
47
|
+
"messages": [
|
|
48
|
+
{"role": m.role, "content": m.content} for m in trace.messages
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if self.include_metadata:
|
|
53
|
+
example["metadata"] = {
|
|
54
|
+
"scenario": trace.scenario.description,
|
|
55
|
+
"category": trace.scenario.category,
|
|
56
|
+
"grade": trace.grade.model_dump() if trace.grade else None,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
examples.append(example)
|
|
60
|
+
|
|
61
|
+
return examples
|
|
62
|
+
|
|
63
|
+
def save(self, traces: list["Trace"], path: str | Path) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Save formatted traces to a JSONL file.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
traces: List of traces to save
|
|
69
|
+
path: Output file path (should end in .jsonl)
|
|
70
|
+
"""
|
|
71
|
+
path = Path(path)
|
|
72
|
+
examples = self.format(traces)
|
|
73
|
+
|
|
74
|
+
with open(path, "w") as f:
|
|
75
|
+
for example in examples:
|
|
76
|
+
f.write(json.dumps(example) + "\n")
|
|
77
|
+
|
|
78
|
+
def to_jsonl(self, traces: list["Trace"]) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Convert traces to JSONL string.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
traces: List of traces to convert
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
JSONL formatted string
|
|
87
|
+
"""
|
|
88
|
+
examples = self.format(traces)
|
|
89
|
+
return "\n".join(json.dumps(e) for e in examples)
|
|
90
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Tool Call formatter for training data."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from synkro.types.core import Trace
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ToolCallFormatter:
|
|
12
|
+
"""
|
|
13
|
+
Format traces with tool calls for fine-tuning.
|
|
14
|
+
|
|
15
|
+
Outputs OpenAI function calling format compatible with most fine-tuning platforms.
|
|
16
|
+
|
|
17
|
+
Example output:
|
|
18
|
+
{
|
|
19
|
+
"messages": [
|
|
20
|
+
{"role": "system", "content": "You have access to: web_search(query)"},
|
|
21
|
+
{"role": "user", "content": "What's the weather in NYC?"},
|
|
22
|
+
{"role": "assistant", "content": null, "tool_calls": [
|
|
23
|
+
{"id": "call_1", "type": "function", "function": {"name": "web_search", "arguments": "{\\"query\\": \\"weather NYC\\"}"}}
|
|
24
|
+
]},
|
|
25
|
+
{"role": "tool", "tool_call_id": "call_1", "content": "NYC: 72°F, sunny"},
|
|
26
|
+
{"role": "assistant", "content": "The weather in NYC is currently 72°F and sunny."}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, include_metadata: bool = False):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the ToolCallFormatter.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
include_metadata: If True, include trace metadata in output
|
|
37
|
+
"""
|
|
38
|
+
self.include_metadata = include_metadata
|
|
39
|
+
|
|
40
|
+
def format(self, traces: list["Trace"]) -> list[dict]:
|
|
41
|
+
"""
|
|
42
|
+
Format traces as tool-calling training examples.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
traces: List of traces to format
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of formatted examples with tool calls
|
|
49
|
+
"""
|
|
50
|
+
examples = []
|
|
51
|
+
|
|
52
|
+
for trace in traces:
|
|
53
|
+
messages = []
|
|
54
|
+
|
|
55
|
+
for m in trace.messages:
|
|
56
|
+
msg = {"role": m.role}
|
|
57
|
+
|
|
58
|
+
# Handle content (can be None for tool-calling assistant messages)
|
|
59
|
+
if m.content is not None:
|
|
60
|
+
msg["content"] = m.content
|
|
61
|
+
elif m.role == "assistant" and m.tool_calls:
|
|
62
|
+
msg["content"] = None
|
|
63
|
+
else:
|
|
64
|
+
msg["content"] = ""
|
|
65
|
+
|
|
66
|
+
# Handle tool calls
|
|
67
|
+
if m.tool_calls:
|
|
68
|
+
msg["tool_calls"] = [
|
|
69
|
+
{
|
|
70
|
+
"id": tc.id,
|
|
71
|
+
"type": tc.type,
|
|
72
|
+
"function": {
|
|
73
|
+
"name": tc.function.name,
|
|
74
|
+
"arguments": tc.function.arguments,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for tc in m.tool_calls
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# Handle tool response
|
|
81
|
+
if m.tool_call_id:
|
|
82
|
+
msg["tool_call_id"] = m.tool_call_id
|
|
83
|
+
|
|
84
|
+
messages.append(msg)
|
|
85
|
+
|
|
86
|
+
example = {"messages": messages}
|
|
87
|
+
|
|
88
|
+
if self.include_metadata:
|
|
89
|
+
example["metadata"] = {
|
|
90
|
+
"scenario": trace.scenario.description,
|
|
91
|
+
"category": trace.scenario.category,
|
|
92
|
+
"grade": trace.grade.model_dump() if trace.grade else None,
|
|
93
|
+
"has_tool_calls": trace.has_tool_calls,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
examples.append(example)
|
|
97
|
+
|
|
98
|
+
return examples
|
|
99
|
+
|
|
100
|
+
def save(self, traces: list["Trace"], path: str | Path) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Save formatted traces to a JSONL file.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
traces: List of traces to save
|
|
106
|
+
path: Output file path (should end in .jsonl)
|
|
107
|
+
"""
|
|
108
|
+
path = Path(path)
|
|
109
|
+
examples = self.format(traces)
|
|
110
|
+
|
|
111
|
+
with open(path, "w") as f:
|
|
112
|
+
for example in examples:
|
|
113
|
+
f.write(json.dumps(example) + "\n")
|
|
114
|
+
|
|
115
|
+
def to_jsonl(self, traces: list["Trace"]) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Convert traces to JSONL string.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
traces: List of traces to convert
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
JSONL formatted string
|
|
124
|
+
"""
|
|
125
|
+
examples = self.format(traces)
|
|
126
|
+
return "\n".join(json.dumps(e) for e in examples)
|
|
127
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Generation components for creating training data."""
|
|
2
|
+
|
|
3
|
+
from synkro.generation.generator import Generator
|
|
4
|
+
from synkro.generation.scenarios import ScenarioGenerator
|
|
5
|
+
from synkro.generation.responses import ResponseGenerator
|
|
6
|
+
from synkro.generation.planner import Planner
|
|
7
|
+
|
|
8
|
+
__all__ = ["Generator", "ScenarioGenerator", "ResponseGenerator", "Planner"]
|
|
9
|
+
|