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
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Logic Map types for Golden Trace generation.
|
|
2
|
+
|
|
3
|
+
The Logic Map represents a policy as a directed acyclic graph (DAG) of rules,
|
|
4
|
+
enabling grounded reasoning and verification of generated traces.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ScenarioType(str, Enum):
|
|
16
|
+
"""Types of scenarios for balanced dataset generation."""
|
|
17
|
+
|
|
18
|
+
POSITIVE = "positive" # Happy path - user meets all criteria
|
|
19
|
+
NEGATIVE = "negative" # Violation - user fails one criterion
|
|
20
|
+
EDGE_CASE = "edge_case" # Boundary - user at exact limit
|
|
21
|
+
IRRELEVANT = "irrelevant" # Not covered by policy
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RuleCategory(str, Enum):
|
|
25
|
+
"""Categories of rules extracted from policy."""
|
|
26
|
+
|
|
27
|
+
CONSTRAINT = "constraint" # Must/must not conditions
|
|
28
|
+
PERMISSION = "permission" # Allowed/can do
|
|
29
|
+
PROCEDURE = "procedure" # Step-by-step processes
|
|
30
|
+
EXCEPTION = "exception" # Special cases/overrides
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Rule(BaseModel):
|
|
34
|
+
"""
|
|
35
|
+
A single rule extracted from the policy document.
|
|
36
|
+
|
|
37
|
+
Rules form nodes in the Logic Map DAG, with dependencies
|
|
38
|
+
indicating which rules must be evaluated first.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
>>> rule = Rule(
|
|
42
|
+
... rule_id="R001",
|
|
43
|
+
... text="Refunds are allowed within 30 days of purchase",
|
|
44
|
+
... condition="purchase date is within 30 days",
|
|
45
|
+
... action="allow refund",
|
|
46
|
+
... dependencies=[],
|
|
47
|
+
... category=RuleCategory.PERMISSION,
|
|
48
|
+
... )
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
rule_id: str = Field(
|
|
52
|
+
description="Unique identifier (e.g., 'R001', 'R002')"
|
|
53
|
+
)
|
|
54
|
+
text: str = Field(
|
|
55
|
+
description="Exact rule text from the policy"
|
|
56
|
+
)
|
|
57
|
+
condition: str = Field(
|
|
58
|
+
description="The 'if' part - when this rule applies"
|
|
59
|
+
)
|
|
60
|
+
action: str = Field(
|
|
61
|
+
description="The 'then' part - what happens when rule applies"
|
|
62
|
+
)
|
|
63
|
+
dependencies: list[str] = Field(
|
|
64
|
+
default_factory=list,
|
|
65
|
+
description="Rule IDs that must be evaluated before this rule"
|
|
66
|
+
)
|
|
67
|
+
category: RuleCategory = Field(
|
|
68
|
+
description="Type of rule (constraint, permission, procedure, exception)"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def __hash__(self) -> int:
|
|
72
|
+
return hash(self.rule_id)
|
|
73
|
+
|
|
74
|
+
def __eq__(self, other: object) -> bool:
|
|
75
|
+
if not isinstance(other, Rule):
|
|
76
|
+
return False
|
|
77
|
+
return self.rule_id == other.rule_id
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LogicMap(BaseModel):
|
|
81
|
+
"""
|
|
82
|
+
Directed Acyclic Graph (DAG) of rules extracted from a policy.
|
|
83
|
+
|
|
84
|
+
The Logic Map is the "Map of Truth" that enables:
|
|
85
|
+
- Grounded scenario generation with rule references
|
|
86
|
+
- Chain-of-thought reasoning with rule citations
|
|
87
|
+
- Verification that traces don't skip or hallucinate rules
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
>>> logic_map = LogicMap(
|
|
91
|
+
... rules=[rule1, rule2, rule3],
|
|
92
|
+
... root_rules=["R001"], # Entry points
|
|
93
|
+
... )
|
|
94
|
+
>>> print(logic_map.get_rule("R001"))
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
rules: list[Rule] = Field(
|
|
98
|
+
description="All rules extracted from the policy"
|
|
99
|
+
)
|
|
100
|
+
root_rules: list[str] = Field(
|
|
101
|
+
default_factory=list,
|
|
102
|
+
description="Rule IDs with no dependencies (entry points)"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def get_rule(self, rule_id: str) -> Rule | None:
|
|
106
|
+
"""Get a rule by its ID."""
|
|
107
|
+
for rule in self.rules:
|
|
108
|
+
if rule.rule_id == rule_id:
|
|
109
|
+
return rule
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def get_dependents(self, rule_id: str) -> list[Rule]:
|
|
113
|
+
"""Get all rules that depend on the given rule."""
|
|
114
|
+
return [r for r in self.rules if rule_id in r.dependencies]
|
|
115
|
+
|
|
116
|
+
def get_dependencies(self, rule_id: str) -> list[Rule]:
|
|
117
|
+
"""Get all rules that the given rule depends on."""
|
|
118
|
+
rule = self.get_rule(rule_id)
|
|
119
|
+
if not rule:
|
|
120
|
+
return []
|
|
121
|
+
return [r for r in self.rules if r.rule_id in rule.dependencies]
|
|
122
|
+
|
|
123
|
+
def get_chain(self, rule_id: str) -> list[Rule]:
|
|
124
|
+
"""
|
|
125
|
+
Get the full dependency chain for a rule (topologically sorted).
|
|
126
|
+
|
|
127
|
+
Returns all rules that must be evaluated before the given rule,
|
|
128
|
+
in the order they should be evaluated.
|
|
129
|
+
"""
|
|
130
|
+
visited = set()
|
|
131
|
+
chain = []
|
|
132
|
+
|
|
133
|
+
def visit(rid: str):
|
|
134
|
+
if rid in visited:
|
|
135
|
+
return
|
|
136
|
+
visited.add(rid)
|
|
137
|
+
rule = self.get_rule(rid)
|
|
138
|
+
if rule:
|
|
139
|
+
for dep_id in rule.dependencies:
|
|
140
|
+
visit(dep_id)
|
|
141
|
+
chain.append(rule)
|
|
142
|
+
|
|
143
|
+
visit(rule_id)
|
|
144
|
+
return chain
|
|
145
|
+
|
|
146
|
+
def validate_dag(self) -> bool:
|
|
147
|
+
"""Verify the rules form a valid DAG (no cycles)."""
|
|
148
|
+
# Track visit state: 0=unvisited, 1=visiting, 2=visited
|
|
149
|
+
state = {r.rule_id: 0 for r in self.rules}
|
|
150
|
+
|
|
151
|
+
def has_cycle(rule_id: str) -> bool:
|
|
152
|
+
if state.get(rule_id, 0) == 1: # Currently visiting = cycle
|
|
153
|
+
return True
|
|
154
|
+
if state.get(rule_id, 0) == 2: # Already visited = ok
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
state[rule_id] = 1 # Mark as visiting
|
|
158
|
+
rule = self.get_rule(rule_id)
|
|
159
|
+
if rule:
|
|
160
|
+
for dep_id in rule.dependencies:
|
|
161
|
+
if has_cycle(dep_id):
|
|
162
|
+
return True
|
|
163
|
+
state[rule_id] = 2 # Mark as visited
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
for rule in self.rules:
|
|
167
|
+
if has_cycle(rule.rule_id):
|
|
168
|
+
return False
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
def get_rules_by_category(self, category: RuleCategory) -> list[Rule]:
|
|
172
|
+
"""Get all rules of a specific category."""
|
|
173
|
+
return [r for r in self.rules if r.category == category]
|
|
174
|
+
|
|
175
|
+
def to_display_string(self) -> str:
|
|
176
|
+
"""Generate a human-readable representation of the Logic Map."""
|
|
177
|
+
lines = [f"Logic Map ({len(self.rules)} rules)"]
|
|
178
|
+
lines.append("=" * 40)
|
|
179
|
+
|
|
180
|
+
# Show root rules first
|
|
181
|
+
lines.append("\nRoot Rules (Entry Points):")
|
|
182
|
+
for rid in self.root_rules:
|
|
183
|
+
rule = self.get_rule(rid)
|
|
184
|
+
if rule:
|
|
185
|
+
lines.append(f" {rid}: {rule.text[:60]}...")
|
|
186
|
+
|
|
187
|
+
# Show dependency chains
|
|
188
|
+
lines.append("\nDependency Chains:")
|
|
189
|
+
processed = set()
|
|
190
|
+
for rule in self.rules:
|
|
191
|
+
if rule.rule_id not in processed and rule.dependencies:
|
|
192
|
+
chain = " -> ".join(r.rule_id for r in self.get_chain(rule.rule_id))
|
|
193
|
+
lines.append(f" {chain}")
|
|
194
|
+
processed.update(r.rule_id for r in self.get_chain(rule.rule_id))
|
|
195
|
+
|
|
196
|
+
return "\n".join(lines)
|
|
197
|
+
|
|
198
|
+
def save(self, path: str | Path) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Save the Logic Map to a JSON file.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
path: File path to save to (e.g., "logic_map.json")
|
|
204
|
+
|
|
205
|
+
Examples:
|
|
206
|
+
>>> logic_map.save("logic_map.json")
|
|
207
|
+
>>> # Later, reload it
|
|
208
|
+
>>> logic_map = LogicMap.load("logic_map.json")
|
|
209
|
+
"""
|
|
210
|
+
path = Path(path)
|
|
211
|
+
with open(path, "w") as f:
|
|
212
|
+
json.dump(self.model_dump(), f, indent=2)
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def load(cls, path: str | Path) -> "LogicMap":
|
|
216
|
+
"""
|
|
217
|
+
Load a Logic Map from a JSON file.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
path: File path to load from
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
LogicMap instance
|
|
224
|
+
|
|
225
|
+
Examples:
|
|
226
|
+
>>> logic_map = LogicMap.load("logic_map.json")
|
|
227
|
+
>>> print(f"Loaded {len(logic_map.rules)} rules")
|
|
228
|
+
"""
|
|
229
|
+
path = Path(path)
|
|
230
|
+
with open(path) as f:
|
|
231
|
+
data = json.load(f)
|
|
232
|
+
return cls.model_validate(data)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class ReasoningStep(BaseModel):
|
|
236
|
+
"""
|
|
237
|
+
A single step in the Chain-of-Thought reasoning.
|
|
238
|
+
|
|
239
|
+
Each step references exactly one rule and explains how it applies
|
|
240
|
+
(or doesn't apply) to the current scenario.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
rule_id: str = Field(
|
|
244
|
+
description="The rule being evaluated in this step"
|
|
245
|
+
)
|
|
246
|
+
rule_text: str = Field(
|
|
247
|
+
description="The text of the rule"
|
|
248
|
+
)
|
|
249
|
+
applies: bool = Field(
|
|
250
|
+
description="Whether this rule applies to the scenario"
|
|
251
|
+
)
|
|
252
|
+
reasoning: str = Field(
|
|
253
|
+
description="Explanation of why the rule does/doesn't apply"
|
|
254
|
+
)
|
|
255
|
+
exclusions: list[str] = Field(
|
|
256
|
+
default_factory=list,
|
|
257
|
+
description="Rule IDs that are excluded because this rule applies"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class GoldenScenario(BaseModel):
|
|
262
|
+
"""
|
|
263
|
+
A scenario with explicit type and rule targeting.
|
|
264
|
+
|
|
265
|
+
Extends the base Scenario concept with:
|
|
266
|
+
- Explicit scenario type (positive, negative, edge_case, irrelevant)
|
|
267
|
+
- Target rule IDs that this scenario is designed to test
|
|
268
|
+
- Expected outcome based on the rules
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
description: str = Field(
|
|
272
|
+
description="The user's request or question"
|
|
273
|
+
)
|
|
274
|
+
context: str = Field(
|
|
275
|
+
default="",
|
|
276
|
+
description="Additional context for the scenario"
|
|
277
|
+
)
|
|
278
|
+
category: str = Field(
|
|
279
|
+
default="",
|
|
280
|
+
description="The policy category this scenario belongs to"
|
|
281
|
+
)
|
|
282
|
+
scenario_type: ScenarioType = Field(
|
|
283
|
+
description="Type of scenario (positive, negative, edge_case, irrelevant)"
|
|
284
|
+
)
|
|
285
|
+
target_rule_ids: list[str] = Field(
|
|
286
|
+
default_factory=list,
|
|
287
|
+
description="Rule IDs this scenario is designed to test"
|
|
288
|
+
)
|
|
289
|
+
expected_outcome: str = Field(
|
|
290
|
+
default="",
|
|
291
|
+
description="Expected response behavior based on rules"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def to_base_scenario(self) -> "Scenario":
|
|
295
|
+
"""Convert to base Scenario type for compatibility."""
|
|
296
|
+
from synkro.types.core import Scenario
|
|
297
|
+
return Scenario(
|
|
298
|
+
description=self.description,
|
|
299
|
+
context=self.context,
|
|
300
|
+
category=self.category,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class VerificationResult(BaseModel):
|
|
305
|
+
"""
|
|
306
|
+
Result of verifying a trace against the Logic Map.
|
|
307
|
+
|
|
308
|
+
The Auditor produces this to indicate whether a trace
|
|
309
|
+
correctly applies all relevant rules without hallucination.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
passed: bool = Field(
|
|
313
|
+
description="Whether the trace passed verification"
|
|
314
|
+
)
|
|
315
|
+
issues: list[str] = Field(
|
|
316
|
+
default_factory=list,
|
|
317
|
+
description="List of issues found (if any)"
|
|
318
|
+
)
|
|
319
|
+
skipped_rules: list[str] = Field(
|
|
320
|
+
default_factory=list,
|
|
321
|
+
description="Rule IDs that should have been applied but weren't"
|
|
322
|
+
)
|
|
323
|
+
hallucinated_rules: list[str] = Field(
|
|
324
|
+
default_factory=list,
|
|
325
|
+
description="Rule IDs cited that don't exist or don't apply"
|
|
326
|
+
)
|
|
327
|
+
contradictions: list[str] = Field(
|
|
328
|
+
default_factory=list,
|
|
329
|
+
description="Logical contradictions found in the trace"
|
|
330
|
+
)
|
|
331
|
+
rules_verified: list[str] = Field(
|
|
332
|
+
default_factory=list,
|
|
333
|
+
description="Rule IDs that were correctly applied"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
__all__ = [
|
|
338
|
+
"ScenarioType",
|
|
339
|
+
"RuleCategory",
|
|
340
|
+
"Rule",
|
|
341
|
+
"LogicMap",
|
|
342
|
+
"ReasoningStep",
|
|
343
|
+
"GoldenScenario",
|
|
344
|
+
"VerificationResult",
|
|
345
|
+
]
|
synkro/types/tool.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Tool-related types for tool call trace generation."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ToolFunction(BaseModel):
|
|
7
|
+
"""Function details within a tool call."""
|
|
8
|
+
|
|
9
|
+
name: str = Field(description="Name of the function to call")
|
|
10
|
+
arguments: str = Field(description="JSON string of function arguments")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ToolCall(BaseModel):
|
|
14
|
+
"""A tool call made by the assistant."""
|
|
15
|
+
|
|
16
|
+
id: str = Field(description="Unique identifier for this tool call")
|
|
17
|
+
type: str = Field(default="function", description="Type of tool call")
|
|
18
|
+
function: ToolFunction = Field(description="Function details")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ToolResult(BaseModel):
|
|
22
|
+
"""Result from a tool execution."""
|
|
23
|
+
|
|
24
|
+
tool_call_id: str = Field(description="ID of the tool call this responds to")
|
|
25
|
+
content: str = Field(description="The tool's response content")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ToolDefinition(BaseModel):
|
|
29
|
+
"""
|
|
30
|
+
Definition of a tool that an agent can use.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
>>> web_search = ToolDefinition(
|
|
34
|
+
... name="web_search",
|
|
35
|
+
... description="Search the web for current information",
|
|
36
|
+
... parameters={
|
|
37
|
+
... "type": "object",
|
|
38
|
+
... "properties": {
|
|
39
|
+
... "query": {"type": "string", "description": "Search query"}
|
|
40
|
+
... },
|
|
41
|
+
... "required": ["query"]
|
|
42
|
+
... },
|
|
43
|
+
... examples=[{"query": "weather in NYC"}],
|
|
44
|
+
... mock_responses=["NYC: 72°F, sunny"]
|
|
45
|
+
... )
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
name: str = Field(description="Name of the tool")
|
|
49
|
+
description: str = Field(description="What the tool does")
|
|
50
|
+
parameters: dict = Field(
|
|
51
|
+
description="JSON Schema for the tool's parameters",
|
|
52
|
+
default_factory=lambda: {"type": "object", "properties": {}}
|
|
53
|
+
)
|
|
54
|
+
examples: list[dict] = Field(
|
|
55
|
+
default_factory=list,
|
|
56
|
+
description="Example tool calls for few-shot learning"
|
|
57
|
+
)
|
|
58
|
+
mock_responses: list[str] = Field(
|
|
59
|
+
default_factory=list,
|
|
60
|
+
description="Example responses for simulation"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def to_openai_format(self) -> dict:
|
|
64
|
+
"""Convert to OpenAI function calling format."""
|
|
65
|
+
return {
|
|
66
|
+
"type": "function",
|
|
67
|
+
"function": {
|
|
68
|
+
"name": self.name,
|
|
69
|
+
"description": self.description,
|
|
70
|
+
"parameters": self.parameters,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def to_system_prompt(self) -> str:
|
|
75
|
+
"""Generate a system prompt description of this tool."""
|
|
76
|
+
params_desc = []
|
|
77
|
+
props = self.parameters.get("properties", {})
|
|
78
|
+
required = self.parameters.get("required", [])
|
|
79
|
+
|
|
80
|
+
for param_name, param_info in props.items():
|
|
81
|
+
param_type = param_info.get("type", "any")
|
|
82
|
+
param_desc = param_info.get("description", "")
|
|
83
|
+
req_marker = " (required)" if param_name in required else ""
|
|
84
|
+
params_desc.append(f" - {param_name}: {param_type}{req_marker} - {param_desc}")
|
|
85
|
+
|
|
86
|
+
params_str = "\n".join(params_desc) if params_desc else " (no parameters)"
|
|
87
|
+
|
|
88
|
+
return f"""**{self.name}**: {self.description}
|
|
89
|
+
Parameters:
|
|
90
|
+
{params_str}"""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = ["ToolDefinition", "ToolCall", "ToolFunction", "ToolResult"]
|
|
94
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Built-in example policies for instant demos."""
|
|
2
|
+
|
|
3
|
+
EXPENSE_POLICY = """# Company Expense Policy
|
|
4
|
+
|
|
5
|
+
## Approval Thresholds
|
|
6
|
+
- Expenses under $50: No approval required
|
|
7
|
+
- Expenses $50-$500: Manager approval required
|
|
8
|
+
- Expenses over $500: VP approval required
|
|
9
|
+
|
|
10
|
+
## Receipt Requirements
|
|
11
|
+
- All expenses over $25 must have a receipt
|
|
12
|
+
- Digital receipts are acceptable
|
|
13
|
+
- Missing receipts require written justification within 48 hours
|
|
14
|
+
|
|
15
|
+
## Categories
|
|
16
|
+
- Travel: Flights, hotels, ground transportation, meals while traveling
|
|
17
|
+
- Meals: Client meals, team events (max $75/person)
|
|
18
|
+
- Software: Must be on pre-approved list, exceptions need IT approval
|
|
19
|
+
- Equipment: Must be on asset tracking list if over $200
|
|
20
|
+
- Office Supplies: Under $100 can be purchased directly
|
|
21
|
+
|
|
22
|
+
## Reimbursement Timeline
|
|
23
|
+
- Submit expenses within 30 days of purchase
|
|
24
|
+
- Reimbursements processed within 14 business days
|
|
25
|
+
- Late submissions require manager exception approval
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
HR_HANDBOOK = """# Employee Handbook
|
|
29
|
+
|
|
30
|
+
## Work Hours
|
|
31
|
+
- Standard work week is 40 hours, Monday through Friday
|
|
32
|
+
- Core hours are 10am to 3pm when all employees should be available
|
|
33
|
+
- Flexible scheduling allowed with manager approval
|
|
34
|
+
|
|
35
|
+
## Time Off
|
|
36
|
+
- Full-time employees receive 15 days PTO per year
|
|
37
|
+
- PTO accrues monthly (1.25 days per month)
|
|
38
|
+
- Unused PTO can roll over up to 5 days
|
|
39
|
+
- PTO requests must be submitted 2 weeks in advance for 3+ days
|
|
40
|
+
|
|
41
|
+
## Remote Work
|
|
42
|
+
- Hybrid schedule: minimum 2 days in office per week
|
|
43
|
+
- Fully remote requires director approval
|
|
44
|
+
- Home office stipend of $500 for remote workers
|
|
45
|
+
|
|
46
|
+
## Performance Reviews
|
|
47
|
+
- Annual reviews conducted in December
|
|
48
|
+
- Mid-year check-ins in June
|
|
49
|
+
- Goals set at start of fiscal year
|
|
50
|
+
- Promotions considered during annual review cycle only
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
REFUND_POLICY = """# Return and Refund Policy
|
|
54
|
+
|
|
55
|
+
## Eligibility
|
|
56
|
+
- Items can be returned within 30 days of purchase
|
|
57
|
+
- Items must be unused and in original packaging
|
|
58
|
+
- Receipt or proof of purchase required
|
|
59
|
+
|
|
60
|
+
## Exceptions
|
|
61
|
+
- Final sale items cannot be returned
|
|
62
|
+
- Personalized items cannot be returned
|
|
63
|
+
- Perishable goods cannot be returned after 7 days
|
|
64
|
+
|
|
65
|
+
## Refund Process
|
|
66
|
+
- Refunds issued to original payment method
|
|
67
|
+
- Processing takes 5-10 business days
|
|
68
|
+
- Shipping costs are non-refundable unless item was defective
|
|
69
|
+
|
|
70
|
+
## Exchanges
|
|
71
|
+
- Exchanges available within 30 days
|
|
72
|
+
- Size exchanges free of charge
|
|
73
|
+
- Different item exchanges treated as return + new purchase
|
|
74
|
+
|
|
75
|
+
## Defective Items
|
|
76
|
+
- Report defects within 14 days
|
|
77
|
+
- Photos required for defect claims
|
|
78
|
+
- Replacement or full refund offered for confirmed defects
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
SUPPORT_GUIDELINES = """# Customer Support Guidelines
|
|
82
|
+
|
|
83
|
+
## Response Times
|
|
84
|
+
- Chat: Respond within 2 minutes
|
|
85
|
+
- Email: Respond within 4 hours during business hours
|
|
86
|
+
- Phone: Answer within 30 seconds, max hold time 3 minutes
|
|
87
|
+
|
|
88
|
+
## Escalation Tiers
|
|
89
|
+
- Tier 1: General questions, password resets, basic troubleshooting
|
|
90
|
+
- Tier 2: Technical issues, billing disputes, account problems
|
|
91
|
+
- Tier 3: Complex technical issues, executive escalations
|
|
92
|
+
|
|
93
|
+
## Refund Authority
|
|
94
|
+
- Tier 1 can issue refunds up to $50
|
|
95
|
+
- Tier 2 can issue refunds up to $200
|
|
96
|
+
- Tier 3 or manager approval needed for refunds over $200
|
|
97
|
+
|
|
98
|
+
## Documentation
|
|
99
|
+
- Log all customer interactions in CRM
|
|
100
|
+
- Include customer sentiment and issue category
|
|
101
|
+
- Note any promised follow-ups with deadlines
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
SECURITY_POLICY = """# Information Security Policy
|
|
105
|
+
|
|
106
|
+
## Password Requirements
|
|
107
|
+
- Minimum 12 characters
|
|
108
|
+
- Must include uppercase, lowercase, number, and symbol
|
|
109
|
+
- Change every 90 days
|
|
110
|
+
- Cannot reuse last 10 passwords
|
|
111
|
+
|
|
112
|
+
## Access Control
|
|
113
|
+
- Principle of least privilege applies
|
|
114
|
+
- Access requests require manager approval
|
|
115
|
+
- Quarterly access reviews mandatory
|
|
116
|
+
- Terminate access within 24 hours of employee departure
|
|
117
|
+
|
|
118
|
+
## Data Classification
|
|
119
|
+
- Public: Marketing materials, job postings
|
|
120
|
+
- Internal: Company announcements, policies
|
|
121
|
+
- Confidential: Customer data, financials
|
|
122
|
+
- Restricted: PII, payment info, credentials
|
|
123
|
+
|
|
124
|
+
## Incident Response
|
|
125
|
+
- Report security incidents within 1 hour
|
|
126
|
+
- Do not attempt to investigate independently
|
|
127
|
+
- Preserve evidence (don't delete logs or files)
|
|
128
|
+
- Security team leads all incident response
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
# All policies available as a list
|
|
132
|
+
ALL_POLICIES = [
|
|
133
|
+
("expense", EXPENSE_POLICY),
|
|
134
|
+
("hr", HR_HANDBOOK),
|
|
135
|
+
("refund", REFUND_POLICY),
|
|
136
|
+
("support", SUPPORT_GUIDELINES),
|
|
137
|
+
("security", SECURITY_POLICY),
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
__all__ = [
|
|
141
|
+
"EXPENSE_POLICY",
|
|
142
|
+
"HR_HANDBOOK",
|
|
143
|
+
"REFUND_POLICY",
|
|
144
|
+
"SUPPORT_GUIDELINES",
|
|
145
|
+
"SECURITY_POLICY",
|
|
146
|
+
"ALL_POLICIES",
|
|
147
|
+
]
|
|
148
|
+
|