vbagent 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vbagent/__init__.py +220 -2
- vbagent/agents/__init__.py +84 -1
- vbagent/agents/converter.py +2 -2
- vbagent/agents/reviewer.py +2 -2
- vbagent/agents/tikz_checker.py +351 -37
- vbagent/agents/variant.py +74 -0
- vbagent/cli/check.py +750 -34
- vbagent/cli/common.py +87 -25
- vbagent/cli/convert.py +1 -1
- vbagent/models/__init__.py +41 -4
- vbagent/prompts/__init__.py +120 -1
- vbagent/prompts/converter.py +317 -74
- vbagent/prompts/tikz_checker.py +91 -3
- vbagent/references/__init__.py +60 -3
- vbagent-0.2.0.dist-info/METADATA +1049 -0
- {vbagent-0.1.1.dist-info → vbagent-0.2.0.dist-info}/RECORD +18 -18
- vbagent-0.1.1.dist-info/METADATA +0 -383
- {vbagent-0.1.1.dist-info → vbagent-0.2.0.dist-info}/WHEEL +0 -0
- {vbagent-0.1.1.dist-info → vbagent-0.2.0.dist-info}/entry_points.txt +0 -0
vbagent/agents/tikz_checker.py
CHANGED
|
@@ -2,20 +2,239 @@
|
|
|
2
2
|
|
|
3
3
|
Checks TikZ/PGF code for syntax errors, best practices,
|
|
4
4
|
and physics diagram conventions.
|
|
5
|
+
|
|
6
|
+
Supports two modes:
|
|
7
|
+
1. Legacy mode: Returns full corrected content (check_tikz)
|
|
8
|
+
2. Patch mode: Uses apply_patch tool for structured diffs (check_tikz_with_patch)
|
|
5
9
|
"""
|
|
6
10
|
|
|
7
11
|
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Optional
|
|
8
14
|
|
|
9
15
|
from vbagent.agents.base import create_agent, run_agent_sync
|
|
10
|
-
from vbagent.prompts.tikz_checker import
|
|
16
|
+
from vbagent.prompts.tikz_checker import (
|
|
17
|
+
SYSTEM_PROMPT,
|
|
18
|
+
USER_TEMPLATE,
|
|
19
|
+
PATCH_SYSTEM_PROMPT,
|
|
20
|
+
PATCH_USER_TEMPLATE,
|
|
21
|
+
)
|
|
11
22
|
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
@dataclass
|
|
25
|
+
class PatchResult:
|
|
26
|
+
"""Result from patch-based TikZ check."""
|
|
27
|
+
passed: bool
|
|
28
|
+
summary: str
|
|
29
|
+
corrected_content: str # Empty if passed
|
|
30
|
+
patches_applied: int
|
|
31
|
+
patch_errors: list[str]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_tikz_reference_context(
|
|
35
|
+
classification=None,
|
|
36
|
+
diagram_type: Optional[str] = None,
|
|
37
|
+
) -> str:
|
|
38
|
+
"""Get TikZ reference context for the checker.
|
|
39
|
+
|
|
40
|
+
Uses the same TikZReferenceStore as the generator for consistency.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
classification: Optional ClassificationResult for metadata matching
|
|
44
|
+
diagram_type: Optional filter by diagram type (e.g., 'circuit')
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Formatted context string with matching TikZ examples
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
from vbagent.references.tikz_store import TikZReferenceStore
|
|
51
|
+
|
|
52
|
+
store = TikZReferenceStore.get_instance()
|
|
53
|
+
|
|
54
|
+
if not store.enabled or not store.references:
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
# If classification provided, use metadata matching
|
|
58
|
+
if classification:
|
|
59
|
+
context = store.get_context_for_classification(classification)
|
|
60
|
+
elif diagram_type:
|
|
61
|
+
# Filter by diagram type
|
|
62
|
+
refs = store.list_references(diagram_type=diagram_type)
|
|
63
|
+
refs = refs[:store.max_examples]
|
|
64
|
+
if not refs:
|
|
65
|
+
return ""
|
|
66
|
+
|
|
67
|
+
parts = []
|
|
68
|
+
for ref in refs:
|
|
69
|
+
header = f"% === Reference: {ref.name} ==="
|
|
70
|
+
if ref.metadata.diagram_type:
|
|
71
|
+
header += f"\n% Type: {ref.metadata.diagram_type}"
|
|
72
|
+
if ref.metadata.topic:
|
|
73
|
+
header += f", Topic: {ref.metadata.topic}"
|
|
74
|
+
parts.append(f"{header}\n{ref.tikz_code}")
|
|
75
|
+
context = "\n\n".join(parts)
|
|
76
|
+
else:
|
|
77
|
+
# Get general examples (top by any criteria)
|
|
78
|
+
refs = store.references[:store.max_examples]
|
|
79
|
+
if not refs:
|
|
80
|
+
return ""
|
|
81
|
+
|
|
82
|
+
parts = []
|
|
83
|
+
for ref in refs:
|
|
84
|
+
header = f"% === Reference: {ref.name} ==="
|
|
85
|
+
if ref.metadata.diagram_type:
|
|
86
|
+
header += f"\n% Type: {ref.metadata.diagram_type}"
|
|
87
|
+
parts.append(f"{header}\n{ref.tikz_code}")
|
|
88
|
+
context = "\n\n".join(parts)
|
|
89
|
+
|
|
90
|
+
if not context:
|
|
91
|
+
return ""
|
|
92
|
+
|
|
93
|
+
return f"""
|
|
94
|
+
## TikZ Reference Examples
|
|
95
|
+
|
|
96
|
+
Use these as style references for corrections:
|
|
97
|
+
|
|
98
|
+
{context}
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
"""
|
|
102
|
+
except Exception:
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def create_tikz_checker_agent(
|
|
107
|
+
use_context: bool = True,
|
|
108
|
+
classification=None,
|
|
109
|
+
diagram_type: Optional[str] = None,
|
|
110
|
+
):
|
|
111
|
+
"""Create a TikZ checker agent with optional reference context.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
use_context: Whether to include reference context
|
|
115
|
+
classification: Optional ClassificationResult for metadata matching
|
|
116
|
+
diagram_type: Optional filter by diagram type (e.g., 'circuit')
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Configured Agent instance
|
|
120
|
+
"""
|
|
121
|
+
prompt = SYSTEM_PROMPT
|
|
122
|
+
|
|
123
|
+
if use_context:
|
|
124
|
+
context = _get_tikz_reference_context(classification, diagram_type)
|
|
125
|
+
if context:
|
|
126
|
+
prompt = prompt + "\n" + context
|
|
127
|
+
|
|
128
|
+
return create_agent(
|
|
129
|
+
name="TikZChecker",
|
|
130
|
+
instructions=prompt,
|
|
131
|
+
agent_type="tikz_checker",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TikZPatchEditor:
|
|
136
|
+
"""Editor for collecting TikZ patches without immediately applying them.
|
|
137
|
+
|
|
138
|
+
This editor collects patch operations so they can be reviewed
|
|
139
|
+
before being applied to the file system.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, file_path: str, original_content: str):
|
|
143
|
+
"""Initialize the editor.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
file_path: Path to the file being edited
|
|
147
|
+
original_content: Original content of the file
|
|
148
|
+
"""
|
|
149
|
+
self.file_path = file_path
|
|
150
|
+
self.original_content = original_content
|
|
151
|
+
self.current_content = original_content
|
|
152
|
+
self.patches: list[dict] = []
|
|
153
|
+
self.errors: list[str] = []
|
|
154
|
+
|
|
155
|
+
def create_file(self, operation) -> dict:
|
|
156
|
+
"""Handle create_file operation (not expected for TikZ checker)."""
|
|
157
|
+
self.errors.append(f"Unexpected create_file for {operation.path}")
|
|
158
|
+
return {"status": "failed", "output": "create_file not supported"}
|
|
159
|
+
|
|
160
|
+
def update_file(self, operation) -> dict:
|
|
161
|
+
"""Handle update_file operation by applying the diff."""
|
|
162
|
+
from agents import apply_diff
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
# Apply the V4A diff
|
|
166
|
+
new_content = apply_diff(self.current_content, operation.diff)
|
|
167
|
+
self.current_content = new_content
|
|
168
|
+
self.patches.append({
|
|
169
|
+
"type": "update_file",
|
|
170
|
+
"path": operation.path,
|
|
171
|
+
"diff": operation.diff,
|
|
172
|
+
})
|
|
173
|
+
return {"status": "completed", "output": f"Updated {operation.path}"}
|
|
174
|
+
except Exception as e:
|
|
175
|
+
error_msg = f"Failed to apply patch: {e}"
|
|
176
|
+
self.errors.append(error_msg)
|
|
177
|
+
return {"status": "failed", "output": error_msg}
|
|
178
|
+
|
|
179
|
+
def delete_file(self, operation) -> dict:
|
|
180
|
+
"""Handle delete_file operation (not expected for TikZ checker)."""
|
|
181
|
+
self.errors.append(f"Unexpected delete_file for {operation.path}")
|
|
182
|
+
return {"status": "failed", "output": "delete_file not supported"}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def create_tikz_patch_agent(
|
|
186
|
+
use_context: bool = True,
|
|
187
|
+
classification=None,
|
|
188
|
+
editor: Optional[TikZPatchEditor] = None,
|
|
189
|
+
diagram_type: Optional[str] = None,
|
|
190
|
+
):
|
|
191
|
+
"""Create a TikZ checker agent with apply_patch tool.
|
|
192
|
+
|
|
193
|
+
This agent uses the apply_patch tool to emit structured diffs
|
|
194
|
+
instead of returning full corrected content.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
use_context: Whether to include reference context
|
|
198
|
+
classification: Optional ClassificationResult for metadata matching
|
|
199
|
+
editor: Optional TikZPatchEditor instance (created if not provided)
|
|
200
|
+
diagram_type: Optional filter by diagram type (e.g., 'circuit')
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Configured Agent instance with apply_patch tool
|
|
204
|
+
"""
|
|
205
|
+
from agents import Agent, ApplyPatchTool
|
|
206
|
+
from vbagent.config import get_model, get_model_settings
|
|
207
|
+
|
|
208
|
+
prompt = PATCH_SYSTEM_PROMPT
|
|
209
|
+
|
|
210
|
+
if use_context:
|
|
211
|
+
context = _get_tikz_reference_context(classification, diagram_type)
|
|
212
|
+
if context:
|
|
213
|
+
prompt = prompt + "\n" + context
|
|
214
|
+
|
|
215
|
+
# Create a dummy editor if none provided (will be replaced at runtime)
|
|
216
|
+
if editor is None:
|
|
217
|
+
editor = TikZPatchEditor("dummy.tex", "")
|
|
218
|
+
|
|
219
|
+
return Agent(
|
|
220
|
+
name="TikZPatchChecker",
|
|
221
|
+
instructions=prompt,
|
|
222
|
+
model=get_model("tikz_checker"),
|
|
223
|
+
model_settings=get_model_settings("tikz_checker"),
|
|
224
|
+
tools=[ApplyPatchTool(editor=editor)],
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# Legacy agent (created lazily for backward compatibility)
|
|
229
|
+
_tikz_checker_agent = None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _get_tikz_checker_agent():
|
|
233
|
+
"""Get or create the legacy TikZ checker agent."""
|
|
234
|
+
global _tikz_checker_agent
|
|
235
|
+
if _tikz_checker_agent is None:
|
|
236
|
+
_tikz_checker_agent = create_tikz_checker_agent(use_context=False)
|
|
237
|
+
return _tikz_checker_agent
|
|
19
238
|
|
|
20
239
|
|
|
21
240
|
def clean_latex_output(latex: str) -> str:
|
|
@@ -38,30 +257,25 @@ def clean_latex_output(latex: str) -> str:
|
|
|
38
257
|
return latex.strip()
|
|
39
258
|
|
|
40
259
|
|
|
260
|
+
|
|
41
261
|
def check_tikz(
|
|
42
262
|
full_content: str,
|
|
43
263
|
image_path: str | None = None,
|
|
264
|
+
use_context: bool = True,
|
|
265
|
+
classification=None,
|
|
44
266
|
) -> tuple[bool, str, str]:
|
|
45
|
-
"""Check TikZ code for errors and best practices.
|
|
267
|
+
"""Check TikZ code for errors and best practices (legacy mode).
|
|
46
268
|
|
|
47
|
-
|
|
48
|
-
- TikZ syntax errors
|
|
49
|
-
- Missing libraries or packages
|
|
50
|
-
- Best practice violations
|
|
51
|
-
- Physics diagram convention issues
|
|
52
|
-
|
|
53
|
-
If an image is provided, the checker can compare the TikZ output
|
|
54
|
-
against the reference image for accuracy.
|
|
269
|
+
Returns full corrected content. For structured diffs, use check_tikz_with_patch().
|
|
55
270
|
|
|
56
271
|
Args:
|
|
57
272
|
full_content: Full LaTeX file content containing TikZ code
|
|
58
273
|
image_path: Optional path to reference image for comparison
|
|
274
|
+
use_context: Whether to include reference context
|
|
275
|
+
classification: Optional ClassificationResult for metadata matching
|
|
59
276
|
|
|
60
277
|
Returns:
|
|
61
278
|
Tuple of (passed, summary, corrected_content)
|
|
62
|
-
- passed: True if no errors found
|
|
63
|
-
- summary: Description of what was fixed (or "PASSED")
|
|
64
|
-
- corrected_content: The corrected file content (empty if passed)
|
|
65
279
|
|
|
66
280
|
Raises:
|
|
67
281
|
ValueError: If content is empty
|
|
@@ -71,6 +285,9 @@ def check_tikz(
|
|
|
71
285
|
if not full_content.strip():
|
|
72
286
|
raise ValueError("Content cannot be empty")
|
|
73
287
|
|
|
288
|
+
# Create agent with context
|
|
289
|
+
agent = create_tikz_checker_agent(use_context, classification)
|
|
290
|
+
|
|
74
291
|
# Use string replace instead of .format() to avoid issues with LaTeX curly braces
|
|
75
292
|
message_text = USER_TEMPLATE.replace('{full_content}', full_content)
|
|
76
293
|
|
|
@@ -81,12 +298,110 @@ def check_tikz(
|
|
|
81
298
|
else:
|
|
82
299
|
message = message_text
|
|
83
300
|
|
|
84
|
-
raw_result = run_agent_sync(
|
|
301
|
+
raw_result = run_agent_sync(agent, message)
|
|
85
302
|
result = clean_latex_output(raw_result)
|
|
86
303
|
|
|
87
304
|
return parse_check_result(result, "TIKZ_CHECK")
|
|
88
305
|
|
|
89
306
|
|
|
307
|
+
def check_tikz_with_patch(
|
|
308
|
+
file_path: str,
|
|
309
|
+
full_content: str,
|
|
310
|
+
image_path: str | None = None,
|
|
311
|
+
use_context: bool = True,
|
|
312
|
+
classification=None,
|
|
313
|
+
ref_diagram_type: Optional[str] = None,
|
|
314
|
+
) -> PatchResult:
|
|
315
|
+
"""Check TikZ code using apply_patch tool for structured diffs.
|
|
316
|
+
|
|
317
|
+
Uses OpenAI's apply_patch tool to emit V4A diffs that can be
|
|
318
|
+
reviewed and applied incrementally.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
file_path: Path to the file being checked (for patch operations)
|
|
322
|
+
full_content: Full LaTeX file content containing TikZ code
|
|
323
|
+
image_path: Optional path to reference image for comparison
|
|
324
|
+
use_context: Whether to include reference context
|
|
325
|
+
classification: Optional ClassificationResult for metadata matching
|
|
326
|
+
ref_diagram_type: Filter reference examples by diagram type (e.g., 'circuit')
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
PatchResult with pass/fail status, summary, and corrected content
|
|
330
|
+
|
|
331
|
+
Raises:
|
|
332
|
+
ValueError: If content is empty
|
|
333
|
+
"""
|
|
334
|
+
from agents import Runner
|
|
335
|
+
from vbagent.agents.base import create_image_message, _print_agent_info
|
|
336
|
+
|
|
337
|
+
if not full_content.strip():
|
|
338
|
+
raise ValueError("Content cannot be empty")
|
|
339
|
+
|
|
340
|
+
# Create editor to collect patches
|
|
341
|
+
editor = TikZPatchEditor(file_path, full_content)
|
|
342
|
+
|
|
343
|
+
# Create patch agent with the editor
|
|
344
|
+
agent = create_tikz_patch_agent(use_context, classification, editor, ref_diagram_type)
|
|
345
|
+
|
|
346
|
+
# Build the input message
|
|
347
|
+
message_text = PATCH_USER_TEMPLATE.replace('{file_path}', file_path)
|
|
348
|
+
message_text = message_text.replace('{full_content}', full_content)
|
|
349
|
+
|
|
350
|
+
if image_path:
|
|
351
|
+
message_text += "\n\n[Reference image provided - compare TikZ output against this image for accuracy]"
|
|
352
|
+
message = create_image_message(image_path, message_text)
|
|
353
|
+
else:
|
|
354
|
+
message = message_text
|
|
355
|
+
|
|
356
|
+
_print_agent_info(agent)
|
|
357
|
+
|
|
358
|
+
# Run the agent
|
|
359
|
+
result = Runner.run_sync(agent, input=message)
|
|
360
|
+
|
|
361
|
+
# Check if agent returned text indicating pass
|
|
362
|
+
final_output = result.final_output or ""
|
|
363
|
+
if "PASSED" in final_output.upper() or "no errors" in final_output.lower():
|
|
364
|
+
return PatchResult(
|
|
365
|
+
passed=True,
|
|
366
|
+
summary="No TikZ errors found",
|
|
367
|
+
corrected_content="",
|
|
368
|
+
patches_applied=0,
|
|
369
|
+
patch_errors=[],
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Get results from editor
|
|
373
|
+
patches_applied = len(editor.patches)
|
|
374
|
+
patch_errors = editor.errors
|
|
375
|
+
|
|
376
|
+
# Determine pass/fail
|
|
377
|
+
if patches_applied == 0 and not patch_errors:
|
|
378
|
+
return PatchResult(
|
|
379
|
+
passed=True,
|
|
380
|
+
summary="No TikZ errors found",
|
|
381
|
+
corrected_content="",
|
|
382
|
+
patches_applied=0,
|
|
383
|
+
patch_errors=[],
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Build summary
|
|
387
|
+
if patches_applied > 0:
|
|
388
|
+
summary = f"Applied {patches_applied} patch(es)"
|
|
389
|
+
else:
|
|
390
|
+
summary = "TikZ issues found but patches failed"
|
|
391
|
+
|
|
392
|
+
if patch_errors:
|
|
393
|
+
summary += f" ({len(patch_errors)} error(s))"
|
|
394
|
+
|
|
395
|
+
return PatchResult(
|
|
396
|
+
passed=False,
|
|
397
|
+
summary=summary,
|
|
398
|
+
corrected_content=editor.current_content if patches_applied > 0 else "",
|
|
399
|
+
patches_applied=patches_applied,
|
|
400
|
+
patch_errors=patch_errors,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
90
405
|
def parse_check_result(result: str, check_type: str) -> tuple[bool, str, str]:
|
|
91
406
|
"""Parse the check result to extract pass/fail status and content.
|
|
92
407
|
|
|
@@ -100,7 +415,6 @@ def parse_check_result(result: str, check_type: str) -> tuple[bool, str, str]:
|
|
|
100
415
|
# Check if passed
|
|
101
416
|
passed_pattern = rf'%\s*{check_type}:\s*PASSED'
|
|
102
417
|
if re.search(passed_pattern, result, re.IGNORECASE):
|
|
103
|
-
# Extract the summary after PASSED
|
|
104
418
|
match = re.search(rf'%\s*{check_type}:\s*PASSED\s*[-–—]?\s*(.*?)(?:\n|$)', result, re.IGNORECASE)
|
|
105
419
|
summary = match.group(1).strip() if match else "No TikZ errors found"
|
|
106
420
|
return True, summary, ""
|
|
@@ -118,26 +432,12 @@ def parse_check_result(result: str, check_type: str) -> tuple[bool, str, str]:
|
|
|
118
432
|
|
|
119
433
|
|
|
120
434
|
def has_tikz_passed(result: str) -> bool:
|
|
121
|
-
"""Check if TikZ check passed.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
result: Raw result from checker
|
|
125
|
-
|
|
126
|
-
Returns:
|
|
127
|
-
True if TikZ check passed
|
|
128
|
-
"""
|
|
435
|
+
"""Check if TikZ check passed."""
|
|
129
436
|
return '% TIKZ_CHECK: PASSED' in result or 'TIKZ_CHECK: PASSED' in result.upper()
|
|
130
437
|
|
|
131
438
|
|
|
132
439
|
def has_tikz_environment(content: str) -> bool:
|
|
133
|
-
"""Check if content contains TikZ code.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
content: LaTeX content to check
|
|
137
|
-
|
|
138
|
-
Returns:
|
|
139
|
-
True if TikZ environment or commands found
|
|
140
|
-
"""
|
|
440
|
+
"""Check if content contains TikZ code."""
|
|
141
441
|
tikz_patterns = [
|
|
142
442
|
r'\\begin\{tikzpicture\}',
|
|
143
443
|
r'\\tikz\s*[{\[]',
|
|
@@ -151,3 +451,17 @@ def has_tikz_environment(content: str) -> bool:
|
|
|
151
451
|
if re.search(pattern, content):
|
|
152
452
|
return True
|
|
153
453
|
return False
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
# Backward compatibility: expose tikz_checker_agent as module-level
|
|
457
|
+
class _TikzCheckerAgentProxy:
|
|
458
|
+
"""Proxy for lazy loading tikz_checker_agent."""
|
|
459
|
+
_agent = None
|
|
460
|
+
|
|
461
|
+
def __getattr__(self, name):
|
|
462
|
+
if self._agent is None:
|
|
463
|
+
self._agent = _get_tikz_checker_agent()
|
|
464
|
+
return getattr(self._agent, name)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
tikz_checker_agent = _TikzCheckerAgentProxy()
|
vbagent/agents/variant.py
CHANGED
|
@@ -183,3 +183,77 @@ def generate_variant(
|
|
|
183
183
|
|
|
184
184
|
# Clean up markdown artifacts from LLM output
|
|
185
185
|
return clean_latex_output(raw_result)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Convenience functions for specific variant types
|
|
189
|
+
|
|
190
|
+
def generate_numerical_variant(
|
|
191
|
+
source_latex: str,
|
|
192
|
+
ideas: Optional[IdeaResult] = None,
|
|
193
|
+
use_context: bool = True,
|
|
194
|
+
) -> str:
|
|
195
|
+
"""Generate a numerical variant (changes only numerical values).
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
source_latex: The source problem in LaTeX format
|
|
199
|
+
ideas: Optional IdeaResult with extracted concepts
|
|
200
|
+
use_context: Whether to include reference context in prompt
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The generated variant in LaTeX format
|
|
204
|
+
"""
|
|
205
|
+
return generate_variant(source_latex, "numerical", ideas, use_context)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def generate_context_variant(
|
|
209
|
+
source_latex: str,
|
|
210
|
+
ideas: Optional[IdeaResult] = None,
|
|
211
|
+
use_context: bool = True,
|
|
212
|
+
) -> str:
|
|
213
|
+
"""Generate a context variant (changes only the scenario/context).
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
source_latex: The source problem in LaTeX format
|
|
217
|
+
ideas: Optional IdeaResult with extracted concepts
|
|
218
|
+
use_context: Whether to include reference context in prompt
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
The generated variant in LaTeX format
|
|
222
|
+
"""
|
|
223
|
+
return generate_variant(source_latex, "context", ideas, use_context)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def generate_conceptual_variant(
|
|
227
|
+
source_latex: str,
|
|
228
|
+
ideas: Optional[IdeaResult] = None,
|
|
229
|
+
use_context: bool = True,
|
|
230
|
+
) -> str:
|
|
231
|
+
"""Generate a conceptual variant (changes the core physics concept).
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
source_latex: The source problem in LaTeX format
|
|
235
|
+
ideas: Optional IdeaResult with extracted concepts
|
|
236
|
+
use_context: Whether to include reference context in prompt
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
The generated variant in LaTeX format
|
|
240
|
+
"""
|
|
241
|
+
return generate_variant(source_latex, "conceptual", ideas, use_context)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def generate_calculus_variant(
|
|
245
|
+
source_latex: str,
|
|
246
|
+
ideas: Optional[IdeaResult] = None,
|
|
247
|
+
use_context: bool = True,
|
|
248
|
+
) -> str:
|
|
249
|
+
"""Generate a calculus variant (adds calculus-based modifications).
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
source_latex: The source problem in LaTeX format
|
|
253
|
+
ideas: Optional IdeaResult with extracted concepts
|
|
254
|
+
use_context: Whether to include reference context in prompt
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
The generated variant in LaTeX format
|
|
258
|
+
"""
|
|
259
|
+
return generate_variant(source_latex, "calculus", ideas, use_context)
|