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.
Files changed (77) hide show
  1. synkro/__init__.py +179 -0
  2. synkro/advanced.py +186 -0
  3. synkro/cli.py +128 -0
  4. synkro/core/__init__.py +7 -0
  5. synkro/core/checkpoint.py +250 -0
  6. synkro/core/dataset.py +402 -0
  7. synkro/core/policy.py +337 -0
  8. synkro/errors.py +178 -0
  9. synkro/examples/__init__.py +148 -0
  10. synkro/factory.py +276 -0
  11. synkro/formatters/__init__.py +12 -0
  12. synkro/formatters/qa.py +98 -0
  13. synkro/formatters/sft.py +90 -0
  14. synkro/formatters/tool_call.py +127 -0
  15. synkro/generation/__init__.py +9 -0
  16. synkro/generation/follow_ups.py +134 -0
  17. synkro/generation/generator.py +220 -0
  18. synkro/generation/golden_responses.py +244 -0
  19. synkro/generation/golden_scenarios.py +276 -0
  20. synkro/generation/golden_tool_responses.py +416 -0
  21. synkro/generation/logic_extractor.py +126 -0
  22. synkro/generation/multiturn_responses.py +177 -0
  23. synkro/generation/planner.py +131 -0
  24. synkro/generation/responses.py +189 -0
  25. synkro/generation/scenarios.py +90 -0
  26. synkro/generation/tool_responses.py +376 -0
  27. synkro/generation/tool_simulator.py +114 -0
  28. synkro/interactive/__init__.py +12 -0
  29. synkro/interactive/hitl_session.py +77 -0
  30. synkro/interactive/logic_map_editor.py +173 -0
  31. synkro/interactive/rich_ui.py +205 -0
  32. synkro/llm/__init__.py +7 -0
  33. synkro/llm/client.py +235 -0
  34. synkro/llm/rate_limits.py +95 -0
  35. synkro/models/__init__.py +43 -0
  36. synkro/models/anthropic.py +26 -0
  37. synkro/models/google.py +19 -0
  38. synkro/models/openai.py +31 -0
  39. synkro/modes/__init__.py +15 -0
  40. synkro/modes/config.py +66 -0
  41. synkro/modes/qa.py +18 -0
  42. synkro/modes/sft.py +18 -0
  43. synkro/modes/tool_call.py +18 -0
  44. synkro/parsers.py +442 -0
  45. synkro/pipeline/__init__.py +20 -0
  46. synkro/pipeline/phases.py +592 -0
  47. synkro/pipeline/runner.py +424 -0
  48. synkro/pipelines.py +123 -0
  49. synkro/prompts/__init__.py +57 -0
  50. synkro/prompts/base.py +167 -0
  51. synkro/prompts/golden_templates.py +474 -0
  52. synkro/prompts/interactive_templates.py +65 -0
  53. synkro/prompts/multiturn_templates.py +156 -0
  54. synkro/prompts/qa_templates.py +97 -0
  55. synkro/prompts/templates.py +281 -0
  56. synkro/prompts/tool_templates.py +201 -0
  57. synkro/quality/__init__.py +14 -0
  58. synkro/quality/golden_refiner.py +163 -0
  59. synkro/quality/grader.py +153 -0
  60. synkro/quality/multiturn_grader.py +150 -0
  61. synkro/quality/refiner.py +137 -0
  62. synkro/quality/tool_grader.py +126 -0
  63. synkro/quality/tool_refiner.py +128 -0
  64. synkro/quality/verifier.py +228 -0
  65. synkro/reporting.py +537 -0
  66. synkro/schemas.py +472 -0
  67. synkro/types/__init__.py +41 -0
  68. synkro/types/core.py +126 -0
  69. synkro/types/dataset_type.py +30 -0
  70. synkro/types/logic_map.py +345 -0
  71. synkro/types/tool.py +94 -0
  72. synkro-0.4.12.data/data/examples/__init__.py +148 -0
  73. synkro-0.4.12.dist-info/METADATA +258 -0
  74. synkro-0.4.12.dist-info/RECORD +77 -0
  75. synkro-0.4.12.dist-info/WHEEL +4 -0
  76. synkro-0.4.12.dist-info/entry_points.txt +2 -0
  77. 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
+
@@ -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
+
@@ -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
+