yamlgraph 0.3.9__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.
- examples/__init__.py +1 -0
- examples/codegen/__init__.py +5 -0
- examples/codegen/models/__init__.py +13 -0
- examples/codegen/models/schemas.py +76 -0
- examples/codegen/tests/__init__.py +1 -0
- examples/codegen/tests/test_ai_helpers.py +235 -0
- examples/codegen/tests/test_ast_analysis.py +174 -0
- examples/codegen/tests/test_code_analysis.py +134 -0
- examples/codegen/tests/test_code_context.py +301 -0
- examples/codegen/tests/test_code_nav.py +89 -0
- examples/codegen/tests/test_dependency_tools.py +119 -0
- examples/codegen/tests/test_example_tools.py +185 -0
- examples/codegen/tests/test_git_tools.py +112 -0
- examples/codegen/tests/test_impl_agent_schemas.py +193 -0
- examples/codegen/tests/test_impl_agent_v4_graph.py +94 -0
- examples/codegen/tests/test_jedi_analysis.py +226 -0
- examples/codegen/tests/test_meta_tools.py +250 -0
- examples/codegen/tests/test_plan_discovery_prompt.py +98 -0
- examples/codegen/tests/test_syntax_tools.py +85 -0
- examples/codegen/tests/test_synthesize_prompt.py +94 -0
- examples/codegen/tests/test_template_tools.py +244 -0
- examples/codegen/tools/__init__.py +80 -0
- examples/codegen/tools/ai_helpers.py +420 -0
- examples/codegen/tools/ast_analysis.py +92 -0
- examples/codegen/tools/code_context.py +180 -0
- examples/codegen/tools/code_nav.py +52 -0
- examples/codegen/tools/dependency_tools.py +120 -0
- examples/codegen/tools/example_tools.py +188 -0
- examples/codegen/tools/git_tools.py +151 -0
- examples/codegen/tools/impl_executor.py +614 -0
- examples/codegen/tools/jedi_analysis.py +311 -0
- examples/codegen/tools/meta_tools.py +202 -0
- examples/codegen/tools/syntax_tools.py +26 -0
- examples/codegen/tools/template_tools.py +356 -0
- examples/fastapi_interview.py +167 -0
- examples/npc/api/__init__.py +1 -0
- examples/npc/api/app.py +100 -0
- examples/npc/api/routes/__init__.py +5 -0
- examples/npc/api/routes/encounter.py +182 -0
- examples/npc/api/session.py +330 -0
- examples/npc/demo.py +387 -0
- examples/npc/nodes/__init__.py +5 -0
- examples/npc/nodes/image_node.py +92 -0
- examples/npc/run_encounter.py +230 -0
- examples/shared/__init__.py +0 -0
- examples/shared/replicate_tool.py +238 -0
- examples/storyboard/__init__.py +1 -0
- examples/storyboard/generate_videos.py +335 -0
- examples/storyboard/nodes/__init__.py +12 -0
- examples/storyboard/nodes/animated_character_node.py +248 -0
- examples/storyboard/nodes/animated_image_node.py +138 -0
- examples/storyboard/nodes/character_node.py +162 -0
- examples/storyboard/nodes/image_node.py +118 -0
- examples/storyboard/nodes/replicate_tool.py +49 -0
- examples/storyboard/retry_images.py +118 -0
- scripts/demo_async_executor.py +212 -0
- scripts/demo_interview_e2e.py +200 -0
- scripts/demo_streaming.py +140 -0
- scripts/run_interview_demo.py +94 -0
- scripts/test_interrupt_fix.py +26 -0
- tests/__init__.py +1 -0
- tests/conftest.py +178 -0
- tests/integration/__init__.py +1 -0
- tests/integration/test_animated_storyboard.py +63 -0
- tests/integration/test_cli_commands.py +242 -0
- tests/integration/test_colocated_prompts.py +139 -0
- tests/integration/test_map_demo.py +50 -0
- tests/integration/test_memory_demo.py +283 -0
- tests/integration/test_npc_api/__init__.py +1 -0
- tests/integration/test_npc_api/test_routes.py +357 -0
- tests/integration/test_npc_api/test_session.py +216 -0
- tests/integration/test_pipeline_flow.py +105 -0
- tests/integration/test_providers.py +163 -0
- tests/integration/test_resume.py +75 -0
- tests/integration/test_subgraph_integration.py +295 -0
- tests/integration/test_subgraph_interrupt.py +106 -0
- tests/unit/__init__.py +1 -0
- tests/unit/test_agent_nodes.py +355 -0
- tests/unit/test_async_executor.py +346 -0
- tests/unit/test_checkpointer.py +212 -0
- tests/unit/test_checkpointer_factory.py +212 -0
- tests/unit/test_cli.py +121 -0
- tests/unit/test_cli_package.py +81 -0
- tests/unit/test_compile_graph_map.py +132 -0
- tests/unit/test_conditions_routing.py +253 -0
- tests/unit/test_config.py +93 -0
- tests/unit/test_conversation_memory.py +276 -0
- tests/unit/test_database.py +145 -0
- tests/unit/test_deprecation.py +104 -0
- tests/unit/test_executor.py +172 -0
- tests/unit/test_executor_async.py +179 -0
- tests/unit/test_export.py +149 -0
- tests/unit/test_expressions.py +178 -0
- tests/unit/test_feature_brainstorm.py +194 -0
- tests/unit/test_format_prompt.py +145 -0
- tests/unit/test_generic_report.py +200 -0
- tests/unit/test_graph_commands.py +327 -0
- tests/unit/test_graph_linter.py +627 -0
- tests/unit/test_graph_loader.py +357 -0
- tests/unit/test_graph_schema.py +193 -0
- tests/unit/test_inline_schema.py +151 -0
- tests/unit/test_interrupt_node.py +182 -0
- tests/unit/test_issues.py +164 -0
- tests/unit/test_jinja2_prompts.py +85 -0
- tests/unit/test_json_extract.py +134 -0
- tests/unit/test_langsmith.py +600 -0
- tests/unit/test_langsmith_tools.py +204 -0
- tests/unit/test_llm_factory.py +109 -0
- tests/unit/test_llm_factory_async.py +118 -0
- tests/unit/test_loops.py +403 -0
- tests/unit/test_map_node.py +144 -0
- tests/unit/test_no_backward_compat.py +56 -0
- tests/unit/test_node_factory.py +348 -0
- tests/unit/test_passthrough_node.py +126 -0
- tests/unit/test_prompts.py +324 -0
- tests/unit/test_python_nodes.py +198 -0
- tests/unit/test_reliability.py +298 -0
- tests/unit/test_result_export.py +234 -0
- tests/unit/test_router.py +296 -0
- tests/unit/test_sanitize.py +99 -0
- tests/unit/test_schema_loader.py +295 -0
- tests/unit/test_shell_tools.py +229 -0
- tests/unit/test_state_builder.py +331 -0
- tests/unit/test_state_builder_map.py +104 -0
- tests/unit/test_state_config.py +197 -0
- tests/unit/test_streaming.py +307 -0
- tests/unit/test_subgraph.py +596 -0
- tests/unit/test_template.py +190 -0
- tests/unit/test_tool_call_integration.py +164 -0
- tests/unit/test_tool_call_node.py +178 -0
- tests/unit/test_tool_nodes.py +129 -0
- tests/unit/test_websearch.py +234 -0
- yamlgraph/__init__.py +35 -0
- yamlgraph/builder.py +110 -0
- yamlgraph/cli/__init__.py +159 -0
- yamlgraph/cli/__main__.py +6 -0
- yamlgraph/cli/commands.py +231 -0
- yamlgraph/cli/deprecation.py +92 -0
- yamlgraph/cli/graph_commands.py +541 -0
- yamlgraph/cli/validators.py +37 -0
- yamlgraph/config.py +67 -0
- yamlgraph/constants.py +70 -0
- yamlgraph/error_handlers.py +227 -0
- yamlgraph/executor.py +290 -0
- yamlgraph/executor_async.py +288 -0
- yamlgraph/graph_loader.py +451 -0
- yamlgraph/map_compiler.py +150 -0
- yamlgraph/models/__init__.py +36 -0
- yamlgraph/models/graph_schema.py +181 -0
- yamlgraph/models/schemas.py +124 -0
- yamlgraph/models/state_builder.py +236 -0
- yamlgraph/node_factory.py +768 -0
- yamlgraph/routing.py +87 -0
- yamlgraph/schema_loader.py +240 -0
- yamlgraph/storage/__init__.py +20 -0
- yamlgraph/storage/checkpointer.py +72 -0
- yamlgraph/storage/checkpointer_factory.py +123 -0
- yamlgraph/storage/database.py +320 -0
- yamlgraph/storage/export.py +269 -0
- yamlgraph/tools/__init__.py +1 -0
- yamlgraph/tools/agent.py +320 -0
- yamlgraph/tools/graph_linter.py +388 -0
- yamlgraph/tools/langsmith_tools.py +125 -0
- yamlgraph/tools/nodes.py +126 -0
- yamlgraph/tools/python_tool.py +179 -0
- yamlgraph/tools/shell.py +205 -0
- yamlgraph/tools/websearch.py +242 -0
- yamlgraph/utils/__init__.py +48 -0
- yamlgraph/utils/conditions.py +157 -0
- yamlgraph/utils/expressions.py +245 -0
- yamlgraph/utils/json_extract.py +104 -0
- yamlgraph/utils/langsmith.py +416 -0
- yamlgraph/utils/llm_factory.py +118 -0
- yamlgraph/utils/llm_factory_async.py +105 -0
- yamlgraph/utils/logging.py +104 -0
- yamlgraph/utils/prompts.py +171 -0
- yamlgraph/utils/sanitize.py +98 -0
- yamlgraph/utils/template.py +102 -0
- yamlgraph/utils/validators.py +181 -0
- yamlgraph-0.3.9.dist-info/METADATA +1105 -0
- yamlgraph-0.3.9.dist-info/RECORD +185 -0
- yamlgraph-0.3.9.dist-info/WHEEL +5 -0
- yamlgraph-0.3.9.dist-info/entry_points.txt +2 -0
- yamlgraph-0.3.9.dist-info/licenses/LICENSE +33 -0
- yamlgraph-0.3.9.dist-info/top_level.txt +4 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""Tests for YAMLGraph meta-template tools."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from examples.codegen.tools.meta_tools import (
|
|
6
|
+
extract_graph_template,
|
|
7
|
+
extract_prompt_template,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
# ============================================================================
|
|
11
|
+
# extract_graph_template tests
|
|
12
|
+
# ============================================================================
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestExtractGraphTemplate:
|
|
16
|
+
"""Tests for extract_graph_template."""
|
|
17
|
+
|
|
18
|
+
def test_extracts_node_types(self, tmp_path: Path):
|
|
19
|
+
"""Extract node type patterns from graph."""
|
|
20
|
+
graph = tmp_path / "graph.yaml"
|
|
21
|
+
graph.write_text("""
|
|
22
|
+
name: test-graph
|
|
23
|
+
version: "1.0"
|
|
24
|
+
|
|
25
|
+
nodes:
|
|
26
|
+
step1:
|
|
27
|
+
type: llm
|
|
28
|
+
prompt: prompts/step1
|
|
29
|
+
state_key: result1
|
|
30
|
+
|
|
31
|
+
step2:
|
|
32
|
+
type: agent
|
|
33
|
+
prompt: prompts/step2
|
|
34
|
+
tools: [tool1, tool2]
|
|
35
|
+
max_iterations: 5
|
|
36
|
+
|
|
37
|
+
edges:
|
|
38
|
+
- START -> step1
|
|
39
|
+
- step1 -> step2
|
|
40
|
+
- step2 -> END
|
|
41
|
+
""")
|
|
42
|
+
result = extract_graph_template(str(graph))
|
|
43
|
+
|
|
44
|
+
assert "node_types" in result
|
|
45
|
+
assert "llm" in result["node_types"]
|
|
46
|
+
assert "agent" in result["node_types"]
|
|
47
|
+
|
|
48
|
+
def test_extracts_edge_patterns(self, tmp_path: Path):
|
|
49
|
+
"""Extract edge patterns (sequential, conditional)."""
|
|
50
|
+
graph = tmp_path / "graph.yaml"
|
|
51
|
+
graph.write_text("""
|
|
52
|
+
name: conditional-graph
|
|
53
|
+
|
|
54
|
+
nodes:
|
|
55
|
+
check:
|
|
56
|
+
type: llm
|
|
57
|
+
prompt: check
|
|
58
|
+
branch_a:
|
|
59
|
+
type: llm
|
|
60
|
+
prompt: a
|
|
61
|
+
branch_b:
|
|
62
|
+
type: llm
|
|
63
|
+
prompt: b
|
|
64
|
+
|
|
65
|
+
edges:
|
|
66
|
+
- START -> check
|
|
67
|
+
- check -> branch_a:
|
|
68
|
+
condition: state.result == "a"
|
|
69
|
+
- check -> branch_b:
|
|
70
|
+
condition: state.result == "b"
|
|
71
|
+
- branch_a -> END
|
|
72
|
+
- branch_b -> END
|
|
73
|
+
""")
|
|
74
|
+
result = extract_graph_template(str(graph))
|
|
75
|
+
|
|
76
|
+
assert "edge_patterns" in result
|
|
77
|
+
patterns = result["edge_patterns"]
|
|
78
|
+
assert "conditional" in patterns or any("condition" in str(p) for p in patterns)
|
|
79
|
+
|
|
80
|
+
def test_extracts_state_fields(self, tmp_path: Path):
|
|
81
|
+
"""Extract state field patterns."""
|
|
82
|
+
graph = tmp_path / "graph.yaml"
|
|
83
|
+
graph.write_text("""
|
|
84
|
+
name: stateful-graph
|
|
85
|
+
|
|
86
|
+
state:
|
|
87
|
+
input: str
|
|
88
|
+
results: list
|
|
89
|
+
errors: list[PipelineError]
|
|
90
|
+
|
|
91
|
+
nodes:
|
|
92
|
+
process:
|
|
93
|
+
type: llm
|
|
94
|
+
prompt: process
|
|
95
|
+
state_key: results
|
|
96
|
+
""")
|
|
97
|
+
result = extract_graph_template(str(graph))
|
|
98
|
+
|
|
99
|
+
assert "state_fields" in result
|
|
100
|
+
fields = result["state_fields"]
|
|
101
|
+
assert "input" in fields or any("input" in str(f) for f in fields)
|
|
102
|
+
|
|
103
|
+
def test_extracts_tool_patterns(self, tmp_path: Path):
|
|
104
|
+
"""Extract tool declaration patterns."""
|
|
105
|
+
graph = tmp_path / "graph.yaml"
|
|
106
|
+
graph.write_text("""
|
|
107
|
+
name: tool-graph
|
|
108
|
+
|
|
109
|
+
tools:
|
|
110
|
+
search:
|
|
111
|
+
type: python
|
|
112
|
+
module: mymodule
|
|
113
|
+
function: search_func
|
|
114
|
+
description: "Search for things"
|
|
115
|
+
|
|
116
|
+
fetch:
|
|
117
|
+
type: shell
|
|
118
|
+
command: "curl {url}"
|
|
119
|
+
|
|
120
|
+
nodes:
|
|
121
|
+
agent:
|
|
122
|
+
type: agent
|
|
123
|
+
tools: [search, fetch]
|
|
124
|
+
""")
|
|
125
|
+
result = extract_graph_template(str(graph))
|
|
126
|
+
|
|
127
|
+
assert "tool_patterns" in result
|
|
128
|
+
patterns = result["tool_patterns"]
|
|
129
|
+
assert "python" in patterns or any("python" in str(p) for p in patterns)
|
|
130
|
+
|
|
131
|
+
def test_handles_missing_file(self):
|
|
132
|
+
"""Returns error for missing file."""
|
|
133
|
+
result = extract_graph_template("/nonexistent/graph.yaml")
|
|
134
|
+
assert "error" in result
|
|
135
|
+
|
|
136
|
+
def test_handles_invalid_yaml(self, tmp_path: Path):
|
|
137
|
+
"""Returns error for invalid YAML."""
|
|
138
|
+
graph = tmp_path / "bad.yaml"
|
|
139
|
+
graph.write_text("{ invalid yaml: [")
|
|
140
|
+
|
|
141
|
+
result = extract_graph_template(str(graph))
|
|
142
|
+
assert "error" in result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ============================================================================
|
|
146
|
+
# extract_prompt_template tests
|
|
147
|
+
# ============================================================================
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestExtractPromptTemplate:
|
|
151
|
+
"""Tests for extract_prompt_template."""
|
|
152
|
+
|
|
153
|
+
def test_extracts_system_structure(self, tmp_path: Path):
|
|
154
|
+
"""Extract system prompt structure."""
|
|
155
|
+
prompt = tmp_path / "prompt.yaml"
|
|
156
|
+
prompt.write_text("""
|
|
157
|
+
metadata:
|
|
158
|
+
provider: anthropic
|
|
159
|
+
model: claude-sonnet-4-20250514
|
|
160
|
+
|
|
161
|
+
system: |
|
|
162
|
+
You are an expert assistant.
|
|
163
|
+
|
|
164
|
+
## Instructions
|
|
165
|
+
- Be helpful
|
|
166
|
+
- Be concise
|
|
167
|
+
|
|
168
|
+
## Output Format
|
|
169
|
+
Respond in JSON.
|
|
170
|
+
|
|
171
|
+
user: "Process this: {input}"
|
|
172
|
+
""")
|
|
173
|
+
result = extract_prompt_template(str(prompt))
|
|
174
|
+
|
|
175
|
+
assert "system_structure" in result
|
|
176
|
+
structure = result["system_structure"]
|
|
177
|
+
assert "sections" in structure or len(structure) > 0
|
|
178
|
+
|
|
179
|
+
def test_extracts_variable_patterns(self, tmp_path: Path):
|
|
180
|
+
"""Extract variable injection patterns."""
|
|
181
|
+
prompt = tmp_path / "prompt.yaml"
|
|
182
|
+
prompt.write_text("""
|
|
183
|
+
system: "You are a {role} assistant."
|
|
184
|
+
user: |
|
|
185
|
+
Input: {input}
|
|
186
|
+
Context: {context}
|
|
187
|
+
Previous: {history}
|
|
188
|
+
""")
|
|
189
|
+
result = extract_prompt_template(str(prompt))
|
|
190
|
+
|
|
191
|
+
assert "variables" in result
|
|
192
|
+
variables = result["variables"]
|
|
193
|
+
assert "input" in variables
|
|
194
|
+
assert "context" in variables
|
|
195
|
+
|
|
196
|
+
def test_extracts_schema_patterns(self, tmp_path: Path):
|
|
197
|
+
"""Extract output schema patterns."""
|
|
198
|
+
prompt = tmp_path / "prompt.yaml"
|
|
199
|
+
prompt.write_text("""
|
|
200
|
+
system: "Analyze the input."
|
|
201
|
+
user: "{input}"
|
|
202
|
+
|
|
203
|
+
schema:
|
|
204
|
+
type: object
|
|
205
|
+
properties:
|
|
206
|
+
result:
|
|
207
|
+
type: string
|
|
208
|
+
confidence:
|
|
209
|
+
type: number
|
|
210
|
+
required: [result]
|
|
211
|
+
""")
|
|
212
|
+
result = extract_prompt_template(str(prompt))
|
|
213
|
+
|
|
214
|
+
assert "schema_patterns" in result
|
|
215
|
+
patterns = result["schema_patterns"]
|
|
216
|
+
assert "result" in str(patterns) or len(patterns) > 0
|
|
217
|
+
|
|
218
|
+
def test_extracts_jinja_patterns(self, tmp_path: Path):
|
|
219
|
+
"""Extract Jinja2 template patterns."""
|
|
220
|
+
prompt = tmp_path / "prompt.yaml"
|
|
221
|
+
prompt.write_text("""
|
|
222
|
+
system: |
|
|
223
|
+
{% if context %}
|
|
224
|
+
Context: {{ context }}
|
|
225
|
+
{% endif %}
|
|
226
|
+
|
|
227
|
+
{% for item in items %}
|
|
228
|
+
- {{ item.name }}: {{ item.value }}
|
|
229
|
+
{% endfor %}
|
|
230
|
+
|
|
231
|
+
user: "Process {{ input }}"
|
|
232
|
+
""")
|
|
233
|
+
result = extract_prompt_template(str(prompt))
|
|
234
|
+
|
|
235
|
+
assert "jinja_patterns" in result
|
|
236
|
+
patterns = result["jinja_patterns"]
|
|
237
|
+
assert "if" in patterns or "for" in patterns
|
|
238
|
+
|
|
239
|
+
def test_handles_missing_file(self):
|
|
240
|
+
"""Returns error for missing file."""
|
|
241
|
+
result = extract_prompt_template("/nonexistent/prompt.yaml")
|
|
242
|
+
assert "error" in result
|
|
243
|
+
|
|
244
|
+
def test_handles_invalid_yaml(self, tmp_path: Path):
|
|
245
|
+
"""Returns error for invalid YAML."""
|
|
246
|
+
prompt = tmp_path / "bad.yaml"
|
|
247
|
+
prompt.write_text("{ invalid yaml: [")
|
|
248
|
+
|
|
249
|
+
result = extract_prompt_template(str(prompt))
|
|
250
|
+
assert "error" in result
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Tests for plan_discovery prompt."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from examples.codegen.models.schemas import DiscoveryPlan, DiscoveryTask
|
|
6
|
+
from yamlgraph.utils.prompts import load_prompt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestPlanDiscoveryPrompt:
|
|
10
|
+
"""Tests for plan_discovery.yaml prompt structure."""
|
|
11
|
+
|
|
12
|
+
def test_prompt_file_exists(self):
|
|
13
|
+
"""Prompt file exists and is loadable."""
|
|
14
|
+
prompt_path = Path("examples/codegen/prompts/plan_discovery.yaml")
|
|
15
|
+
assert prompt_path.exists(), "plan_discovery.yaml not found"
|
|
16
|
+
|
|
17
|
+
prompt = load_prompt("examples/codegen/plan_discovery")
|
|
18
|
+
assert "system" in prompt
|
|
19
|
+
assert "user" in prompt
|
|
20
|
+
assert "schema" in prompt
|
|
21
|
+
|
|
22
|
+
def test_prompt_has_required_sections(self):
|
|
23
|
+
"""Prompt contains key instruction sections."""
|
|
24
|
+
prompt = load_prompt("examples/codegen/plan_discovery")
|
|
25
|
+
system = prompt["system"]
|
|
26
|
+
|
|
27
|
+
# Check for key sections
|
|
28
|
+
assert "Available Tools" in system
|
|
29
|
+
assert "Minimum Checklist" in system
|
|
30
|
+
assert "Priority" in system
|
|
31
|
+
|
|
32
|
+
def test_prompt_lists_core_tools(self):
|
|
33
|
+
"""Prompt documents core discovery tools."""
|
|
34
|
+
prompt = load_prompt("examples/codegen/plan_discovery")
|
|
35
|
+
system = prompt["system"]
|
|
36
|
+
|
|
37
|
+
# Core tools should be listed
|
|
38
|
+
assert "list_modules" in system
|
|
39
|
+
assert "get_structure" in system
|
|
40
|
+
assert "find_tests" in system
|
|
41
|
+
assert "get_callers" in system
|
|
42
|
+
assert "find_similar_code" in system
|
|
43
|
+
|
|
44
|
+
def test_schema_matches_discovery_plan(self):
|
|
45
|
+
"""Output schema is compatible with DiscoveryPlan model."""
|
|
46
|
+
prompt = load_prompt("examples/codegen/plan_discovery")
|
|
47
|
+
schema = prompt["schema"]
|
|
48
|
+
|
|
49
|
+
# Schema should have name and define tasks field
|
|
50
|
+
assert "name" in schema
|
|
51
|
+
assert schema["name"] == "DiscoveryPlan"
|
|
52
|
+
assert "fields" in schema
|
|
53
|
+
assert "tasks" in schema["fields"]
|
|
54
|
+
|
|
55
|
+
def test_discovery_plan_accepts_valid_output(self):
|
|
56
|
+
"""DiscoveryPlan can parse expected LLM output format."""
|
|
57
|
+
# Simulate LLM output
|
|
58
|
+
llm_output = {
|
|
59
|
+
"tasks": [
|
|
60
|
+
{
|
|
61
|
+
"id": 1,
|
|
62
|
+
"task": "Find websearch module structure",
|
|
63
|
+
"tool": "get_structure",
|
|
64
|
+
"args": {"file_path": "yamlgraph/tools/websearch.py"},
|
|
65
|
+
"rationale": "Locate target function",
|
|
66
|
+
"priority": 1,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": 2,
|
|
70
|
+
"task": "Find callers of websearch",
|
|
71
|
+
"tool": "get_callers",
|
|
72
|
+
"args": {
|
|
73
|
+
"file_path": "yamlgraph/tools/websearch.py",
|
|
74
|
+
"function_name": "websearch",
|
|
75
|
+
"line": 10,
|
|
76
|
+
},
|
|
77
|
+
"rationale": "Understand dependencies",
|
|
78
|
+
"priority": 2,
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Should parse successfully
|
|
84
|
+
plan = DiscoveryPlan(tasks=[DiscoveryTask(**t) for t in llm_output["tasks"]])
|
|
85
|
+
assert len(plan.tasks) == 2
|
|
86
|
+
assert plan.tasks[0].tool == "get_structure"
|
|
87
|
+
assert plan.tasks[1].priority == 2
|
|
88
|
+
|
|
89
|
+
def test_minimum_checklist_coverage(self):
|
|
90
|
+
"""Prompt explicitly requires minimum discovery coverage."""
|
|
91
|
+
prompt = load_prompt("examples/codegen/plan_discovery")
|
|
92
|
+
system = prompt["system"]
|
|
93
|
+
|
|
94
|
+
# Minimum checklist items
|
|
95
|
+
assert "Location" in system
|
|
96
|
+
assert "Dependencies" in system
|
|
97
|
+
assert "Tests" in system
|
|
98
|
+
assert "Patterns" in system
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Tests for syntax validation tools."""
|
|
2
|
+
|
|
3
|
+
from examples.codegen.tools.syntax_tools import syntax_check
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestSyntaxCheck:
|
|
7
|
+
"""Tests for syntax_check function."""
|
|
8
|
+
|
|
9
|
+
def test_valid_python_returns_valid(self):
|
|
10
|
+
"""Valid Python code returns valid=True."""
|
|
11
|
+
code = "def hello():\n return 'world'"
|
|
12
|
+
result = syntax_check(code)
|
|
13
|
+
|
|
14
|
+
assert result["valid"] is True
|
|
15
|
+
assert "error" not in result
|
|
16
|
+
|
|
17
|
+
def test_valid_class_definition(self):
|
|
18
|
+
"""Valid class definition passes."""
|
|
19
|
+
code = """
|
|
20
|
+
class MyClass:
|
|
21
|
+
def __init__(self, name: str):
|
|
22
|
+
self.name = name
|
|
23
|
+
|
|
24
|
+
def greet(self) -> str:
|
|
25
|
+
return f"Hello, {self.name}"
|
|
26
|
+
"""
|
|
27
|
+
result = syntax_check(code)
|
|
28
|
+
assert result["valid"] is True
|
|
29
|
+
|
|
30
|
+
def test_invalid_syntax_returns_error(self):
|
|
31
|
+
"""Invalid syntax returns valid=False with error."""
|
|
32
|
+
code = "def broken(\n return"
|
|
33
|
+
result = syntax_check(code)
|
|
34
|
+
|
|
35
|
+
assert result["valid"] is False
|
|
36
|
+
assert "error" in result
|
|
37
|
+
assert isinstance(result["error"], str)
|
|
38
|
+
|
|
39
|
+
def test_missing_colon_error(self):
|
|
40
|
+
"""Missing colon is detected."""
|
|
41
|
+
code = "if True\n print('oops')"
|
|
42
|
+
result = syntax_check(code)
|
|
43
|
+
|
|
44
|
+
assert result["valid"] is False
|
|
45
|
+
assert "error" in result
|
|
46
|
+
|
|
47
|
+
def test_indentation_error(self):
|
|
48
|
+
"""Indentation errors are detected."""
|
|
49
|
+
code = "def foo():\nreturn 1"
|
|
50
|
+
result = syntax_check(code)
|
|
51
|
+
|
|
52
|
+
assert result["valid"] is False
|
|
53
|
+
assert "error" in result
|
|
54
|
+
|
|
55
|
+
def test_empty_string_is_valid(self):
|
|
56
|
+
"""Empty string is valid Python."""
|
|
57
|
+
result = syntax_check("")
|
|
58
|
+
assert result["valid"] is True
|
|
59
|
+
|
|
60
|
+
def test_comment_only_is_valid(self):
|
|
61
|
+
"""Comment-only code is valid."""
|
|
62
|
+
code = "# Just a comment"
|
|
63
|
+
result = syntax_check(code)
|
|
64
|
+
assert result["valid"] is True
|
|
65
|
+
|
|
66
|
+
def test_import_statement_valid(self):
|
|
67
|
+
"""Import statements are valid."""
|
|
68
|
+
code = "from typing import Optional\nimport os"
|
|
69
|
+
result = syntax_check(code)
|
|
70
|
+
assert result["valid"] is True
|
|
71
|
+
|
|
72
|
+
def test_error_includes_line_number(self):
|
|
73
|
+
"""Error message includes line information."""
|
|
74
|
+
code = "line1 = 1\nline2 =\nline3 = 3"
|
|
75
|
+
result = syntax_check(code)
|
|
76
|
+
|
|
77
|
+
assert result["valid"] is False
|
|
78
|
+
# Error should mention line 2
|
|
79
|
+
assert "line" in result["error"].lower() or "2" in result["error"]
|
|
80
|
+
|
|
81
|
+
def test_multiline_string_valid(self):
|
|
82
|
+
"""Multiline strings are valid."""
|
|
83
|
+
code = '"""This is a\nmultiline\ndocstring."""'
|
|
84
|
+
result = syntax_check(code)
|
|
85
|
+
assert result["valid"] is True
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Tests for synthesize prompt.
|
|
2
|
+
|
|
3
|
+
TDD Phase 4: LLM combines discovery findings into analysis.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
PROMPT_PATH = Path(__file__).parent.parent / "prompts" / "synthesize.yaml"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestSynthesizePromptStructure:
|
|
15
|
+
"""Test prompt file structure and content."""
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def prompt_content(self) -> dict:
|
|
19
|
+
"""Load the prompt YAML."""
|
|
20
|
+
assert PROMPT_PATH.exists(), f"Prompt not found: {PROMPT_PATH}"
|
|
21
|
+
with open(PROMPT_PATH) as f:
|
|
22
|
+
return yaml.safe_load(f)
|
|
23
|
+
|
|
24
|
+
def test_has_required_sections(self, prompt_content):
|
|
25
|
+
"""Should have system, user, and schema sections."""
|
|
26
|
+
assert "system" in prompt_content
|
|
27
|
+
assert "user" in prompt_content
|
|
28
|
+
assert "schema" in prompt_content
|
|
29
|
+
|
|
30
|
+
def test_has_metadata(self, prompt_content):
|
|
31
|
+
"""Should have metadata with description and model."""
|
|
32
|
+
assert "metadata" in prompt_content
|
|
33
|
+
metadata = prompt_content["metadata"]
|
|
34
|
+
assert "description" in metadata
|
|
35
|
+
assert "model" in metadata or "provider" in metadata
|
|
36
|
+
|
|
37
|
+
def test_system_covers_focus_areas(self, prompt_content):
|
|
38
|
+
"""System prompt should mention key focus areas."""
|
|
39
|
+
system = prompt_content["system"].lower()
|
|
40
|
+
# Must analyze these aspects
|
|
41
|
+
assert "file" in system or "target" in system
|
|
42
|
+
assert "depend" in system or "caller" in system
|
|
43
|
+
assert "test" in system
|
|
44
|
+
assert "pattern" in system
|
|
45
|
+
|
|
46
|
+
def test_user_includes_discovery_findings(self, prompt_content):
|
|
47
|
+
"""User template should include discovery_findings variable."""
|
|
48
|
+
user = prompt_content["user"]
|
|
49
|
+
assert "{discovery_findings}" in user or "discovery_findings" in user
|
|
50
|
+
|
|
51
|
+
def test_user_includes_story(self, prompt_content):
|
|
52
|
+
"""User template should include story context."""
|
|
53
|
+
user = prompt_content["user"]
|
|
54
|
+
assert "{story}" in user or "story" in user
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestSynthesizeSchema:
|
|
58
|
+
"""Test the output schema structure."""
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def schema(self) -> dict:
|
|
62
|
+
"""Load the schema from prompt."""
|
|
63
|
+
with open(PROMPT_PATH) as f:
|
|
64
|
+
content = yaml.safe_load(f)
|
|
65
|
+
return content.get("schema", {})
|
|
66
|
+
|
|
67
|
+
def test_schema_has_name(self, schema):
|
|
68
|
+
"""Schema should have a name."""
|
|
69
|
+
assert "name" in schema
|
|
70
|
+
|
|
71
|
+
def test_schema_has_summary(self, schema):
|
|
72
|
+
"""Schema should include summary field."""
|
|
73
|
+
fields = schema.get("fields", {})
|
|
74
|
+
assert "summary" in fields
|
|
75
|
+
|
|
76
|
+
def test_schema_has_target_files(self, schema):
|
|
77
|
+
"""Schema should include target_files array."""
|
|
78
|
+
fields = schema.get("fields", {})
|
|
79
|
+
assert "target_files" in fields
|
|
80
|
+
|
|
81
|
+
def test_schema_has_dependencies(self, schema):
|
|
82
|
+
"""Schema should include dependencies."""
|
|
83
|
+
fields = schema.get("fields", {})
|
|
84
|
+
assert "dependencies" in fields
|
|
85
|
+
|
|
86
|
+
def test_schema_has_test_coverage(self, schema):
|
|
87
|
+
"""Schema should include test coverage info."""
|
|
88
|
+
fields = schema.get("fields", {})
|
|
89
|
+
assert "test_coverage" in fields
|
|
90
|
+
|
|
91
|
+
def test_schema_has_patterns(self, schema):
|
|
92
|
+
"""Schema should include patterns to follow."""
|
|
93
|
+
fields = schema.get("fields", {})
|
|
94
|
+
assert "patterns_to_follow" in fields
|