vbagent 0.1.0__tar.gz
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-0.1.0/PKG-INFO +21 -0
- vbagent-0.1.0/README.md +0 -0
- vbagent-0.1.0/pyproject.toml +29 -0
- vbagent-0.1.0/vbagent/__init__.py +3 -0
- vbagent-0.1.0/vbagent/agents/__init__.py +127 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/alternate.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/base.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/clarity_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/classifier.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/converter.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/grammar_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/idea.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/multi_variant.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/reviewer.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/scanner.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/selector.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/solution_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/tikz.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/tikz_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/__pycache__/variant.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/agents/alternate.py +178 -0
- vbagent-0.1.0/vbagent/agents/base.py +214 -0
- vbagent-0.1.0/vbagent/agents/clarity_checker.py +113 -0
- vbagent-0.1.0/vbagent/agents/classifier.py +39 -0
- vbagent-0.1.0/vbagent/agents/converter.py +115 -0
- vbagent-0.1.0/vbagent/agents/grammar_checker.py +113 -0
- vbagent-0.1.0/vbagent/agents/idea.py +135 -0
- vbagent-0.1.0/vbagent/agents/multi_variant.py +68 -0
- vbagent-0.1.0/vbagent/agents/reviewer.py +364 -0
- vbagent-0.1.0/vbagent/agents/scanner.py +144 -0
- vbagent-0.1.0/vbagent/agents/selector.py +143 -0
- vbagent-0.1.0/vbagent/agents/solution_checker.py +116 -0
- vbagent-0.1.0/vbagent/agents/tikz.py +240 -0
- vbagent-0.1.0/vbagent/agents/tikz_checker.py +153 -0
- vbagent-0.1.0/vbagent/agents/variant.py +185 -0
- vbagent-0.1.0/vbagent/cli/__init__.py +1 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/alternate.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/batch.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/check.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/classify.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/common.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/config.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/convert.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/idea.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/main.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/process.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/ref.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/scan.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/tikz.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/__pycache__/variant.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/cli/alternate.py +176 -0
- vbagent-0.1.0/vbagent/cli/batch.py +633 -0
- vbagent-0.1.0/vbagent/cli/check.py +2962 -0
- vbagent-0.1.0/vbagent/cli/classify.py +112 -0
- vbagent-0.1.0/vbagent/cli/common.py +779 -0
- vbagent-0.1.0/vbagent/cli/config.py +191 -0
- vbagent-0.1.0/vbagent/cli/convert.py +248 -0
- vbagent-0.1.0/vbagent/cli/idea.py +165 -0
- vbagent-0.1.0/vbagent/cli/main.py +91 -0
- vbagent-0.1.0/vbagent/cli/process.py +1067 -0
- vbagent-0.1.0/vbagent/cli/ref.py +617 -0
- vbagent-0.1.0/vbagent/cli/scan.py +133 -0
- vbagent-0.1.0/vbagent/cli/tikz.py +125 -0
- vbagent-0.1.0/vbagent/cli/variant.py +271 -0
- vbagent-0.1.0/vbagent/config.py +291 -0
- vbagent-0.1.0/vbagent/models/__init__.py +57 -0
- vbagent-0.1.0/vbagent/models/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/batch.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/classification.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/diff.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/idea.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/pipeline.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/review.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/scan.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/__pycache__/version_store.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/models/batch.py +380 -0
- vbagent-0.1.0/vbagent/models/classification.py +37 -0
- vbagent-0.1.0/vbagent/models/diff.py +416 -0
- vbagent-0.1.0/vbagent/models/idea.py +15 -0
- vbagent-0.1.0/vbagent/models/pipeline.py +20 -0
- vbagent-0.1.0/vbagent/models/review.py +69 -0
- vbagent-0.1.0/vbagent/models/scan.py +13 -0
- vbagent-0.1.0/vbagent/models/version_store.py +955 -0
- vbagent-0.1.0/vbagent/prompts/__init__.py +1 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/alternate.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/clarity_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/classifier.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/converter.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/grammar_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/idea.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/reviewer.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/solution_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/tikz.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/__pycache__/tikz_checker.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/alternate.py +110 -0
- vbagent-0.1.0/vbagent/prompts/clarity_checker.py +58 -0
- vbagent-0.1.0/vbagent/prompts/classifier.py +31 -0
- vbagent-0.1.0/vbagent/prompts/converter.py +125 -0
- vbagent-0.1.0/vbagent/prompts/grammar_checker.py +58 -0
- vbagent-0.1.0/vbagent/prompts/idea.py +142 -0
- vbagent-0.1.0/vbagent/prompts/reviewer.py +140 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__init__.py +71 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/assertion_reason.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/common.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/match.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/mcq_mc.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/mcq_sc.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/passage.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/__pycache__/subjective.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/scanner/assertion_reason.py +64 -0
- vbagent-0.1.0/vbagent/prompts/scanner/common.py +216 -0
- vbagent-0.1.0/vbagent/prompts/scanner/match.py +94 -0
- vbagent-0.1.0/vbagent/prompts/scanner/mcq_mc.py +68 -0
- vbagent-0.1.0/vbagent/prompts/scanner/mcq_sc.py +64 -0
- vbagent-0.1.0/vbagent/prompts/scanner/passage.py +76 -0
- vbagent-0.1.0/vbagent/prompts/scanner/subjective.py +56 -0
- vbagent-0.1.0/vbagent/prompts/solution_checker.py +114 -0
- vbagent-0.1.0/vbagent/prompts/tikz.py +486 -0
- vbagent-0.1.0/vbagent/prompts/tikz_checker.py +230 -0
- vbagent-0.1.0/vbagent/prompts/variants/__init__.py +23 -0
- vbagent-0.1.0/vbagent/prompts/variants/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/variants/__pycache__/conceptual.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/variants/__pycache__/conceptual_calculus.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/variants/__pycache__/context.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/variants/__pycache__/multi_context.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/variants/__pycache__/numerical.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/prompts/variants/conceptual.py +55 -0
- vbagent-0.1.0/vbagent/prompts/variants/conceptual_calculus.py +60 -0
- vbagent-0.1.0/vbagent/prompts/variants/context.py +50 -0
- vbagent-0.1.0/vbagent/prompts/variants/multi_context.py +62 -0
- vbagent-0.1.0/vbagent/prompts/variants/numerical.py +51 -0
- vbagent-0.1.0/vbagent/references/__init__.py +5 -0
- vbagent-0.1.0/vbagent/references/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/references/__pycache__/context.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/references/__pycache__/store.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/references/__pycache__/tikz_store.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/references/context.py +404 -0
- vbagent-0.1.0/vbagent/references/store.py +331 -0
- vbagent-0.1.0/vbagent/references/tikz_store.py +509 -0
- vbagent-0.1.0/vbagent/templates/__init__.py +5 -0
- vbagent-0.1.0/vbagent/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/templates/__pycache__/agentic_context.cpython-312.pyc +0 -0
- vbagent-0.1.0/vbagent/templates/agentic_context.py +193 -0
vbagent-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: vbagent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool for physics question image scanning, categorization, and variant generation
|
|
5
|
+
Author: vaibhavblayer
|
|
6
|
+
Author-email: vaibhavblayer@gmail.com
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: click (>=8.1.0)
|
|
13
|
+
Requires-Dist: hypothesis (>=6.0.0) ; extra == "dev"
|
|
14
|
+
Requires-Dist: openai-agents (>=0.6.2)
|
|
15
|
+
Requires-Dist: pillow (>=10.0.0)
|
|
16
|
+
Requires-Dist: pydantic (>=2.0.0)
|
|
17
|
+
Requires-Dist: pytest (>=8.0.0) ; extra == "dev"
|
|
18
|
+
Requires-Dist: rich (>=13.0.0)
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
|
vbagent-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vbagent"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI tool for physics question image scanning, categorization, and variant generation"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "vaibhavblayer", email = "vaibhavblayer@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"openai-agents>=0.6.2",
|
|
12
|
+
"pydantic>=2.0.0",
|
|
13
|
+
"click>=8.1.0",
|
|
14
|
+
"pillow>=10.0.0",
|
|
15
|
+
"rich>=13.0.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = [
|
|
20
|
+
"pytest>=8.0.0",
|
|
21
|
+
"hypothesis>=6.0.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
vbagent = "vbagent.cli.main:main"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
29
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Agent modules for vbagent using openai-agents SDK.
|
|
2
|
+
|
|
3
|
+
Uses lazy imports to avoid loading heavy dependencies (openai, agents, mcp, pydantic)
|
|
4
|
+
until they are actually needed. This significantly improves CLI startup time.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
# Only import for type checking - avoids heavy runtime imports
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .base import (
|
|
12
|
+
encode_image,
|
|
13
|
+
create_image_message,
|
|
14
|
+
create_agent,
|
|
15
|
+
run_agent,
|
|
16
|
+
run_agent_sync,
|
|
17
|
+
)
|
|
18
|
+
from .classifier import classifier_agent, classify
|
|
19
|
+
from .idea import idea_agent_json, idea_agent_latex, extract_ideas, generate_idea_latex
|
|
20
|
+
from .alternate import alternate_agent, generate_alternate, extract_answer
|
|
21
|
+
from .selector import (
|
|
22
|
+
ProblemContext,
|
|
23
|
+
discover_problems,
|
|
24
|
+
select_random,
|
|
25
|
+
load_problem_context,
|
|
26
|
+
)
|
|
27
|
+
from .solution_checker import (
|
|
28
|
+
solution_checker_agent,
|
|
29
|
+
check_solution,
|
|
30
|
+
has_solution_passed,
|
|
31
|
+
)
|
|
32
|
+
from .grammar_checker import (
|
|
33
|
+
grammar_checker_agent,
|
|
34
|
+
check_grammar,
|
|
35
|
+
has_grammar_passed,
|
|
36
|
+
)
|
|
37
|
+
from .clarity_checker import (
|
|
38
|
+
clarity_checker_agent,
|
|
39
|
+
check_clarity,
|
|
40
|
+
has_clarity_passed,
|
|
41
|
+
)
|
|
42
|
+
from .tikz_checker import (
|
|
43
|
+
tikz_checker_agent,
|
|
44
|
+
check_tikz,
|
|
45
|
+
has_tikz_passed,
|
|
46
|
+
has_tikz_environment,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"encode_image",
|
|
52
|
+
"create_image_message",
|
|
53
|
+
"create_agent",
|
|
54
|
+
"run_agent",
|
|
55
|
+
"run_agent_sync",
|
|
56
|
+
"classifier_agent",
|
|
57
|
+
"classify",
|
|
58
|
+
"idea_agent_json",
|
|
59
|
+
"idea_agent_latex",
|
|
60
|
+
"extract_ideas",
|
|
61
|
+
"generate_idea_latex",
|
|
62
|
+
"alternate_agent",
|
|
63
|
+
"generate_alternate",
|
|
64
|
+
"extract_answer",
|
|
65
|
+
"ProblemContext",
|
|
66
|
+
"discover_problems",
|
|
67
|
+
"select_random",
|
|
68
|
+
"load_problem_context",
|
|
69
|
+
# Solution checker
|
|
70
|
+
"solution_checker_agent",
|
|
71
|
+
"check_solution",
|
|
72
|
+
"has_solution_passed",
|
|
73
|
+
# Grammar checker
|
|
74
|
+
"grammar_checker_agent",
|
|
75
|
+
"check_grammar",
|
|
76
|
+
"has_grammar_passed",
|
|
77
|
+
# Clarity checker
|
|
78
|
+
"clarity_checker_agent",
|
|
79
|
+
"check_clarity",
|
|
80
|
+
"has_clarity_passed",
|
|
81
|
+
# TikZ checker
|
|
82
|
+
"tikz_checker_agent",
|
|
83
|
+
"check_tikz",
|
|
84
|
+
"has_tikz_passed",
|
|
85
|
+
"has_tikz_environment",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def __getattr__(name: str):
|
|
90
|
+
"""Lazy import of agent modules to speed up CLI startup."""
|
|
91
|
+
if name in ("encode_image", "create_image_message", "create_agent", "run_agent", "run_agent_sync"):
|
|
92
|
+
from . import base
|
|
93
|
+
return getattr(base, name)
|
|
94
|
+
|
|
95
|
+
if name in ("classifier_agent", "classify"):
|
|
96
|
+
from . import classifier
|
|
97
|
+
return getattr(classifier, name)
|
|
98
|
+
|
|
99
|
+
if name in ("idea_agent_json", "idea_agent_latex", "extract_ideas", "generate_idea_latex"):
|
|
100
|
+
from . import idea
|
|
101
|
+
return getattr(idea, name)
|
|
102
|
+
|
|
103
|
+
if name in ("alternate_agent", "generate_alternate", "extract_answer"):
|
|
104
|
+
from . import alternate
|
|
105
|
+
return getattr(alternate, name)
|
|
106
|
+
|
|
107
|
+
if name in ("ProblemContext", "discover_problems", "select_random", "load_problem_context"):
|
|
108
|
+
from . import selector
|
|
109
|
+
return getattr(selector, name)
|
|
110
|
+
|
|
111
|
+
if name in ("solution_checker_agent", "check_solution", "has_solution_passed"):
|
|
112
|
+
from . import solution_checker
|
|
113
|
+
return getattr(solution_checker, name)
|
|
114
|
+
|
|
115
|
+
if name in ("grammar_checker_agent", "check_grammar", "has_grammar_passed"):
|
|
116
|
+
from . import grammar_checker
|
|
117
|
+
return getattr(grammar_checker, name)
|
|
118
|
+
|
|
119
|
+
if name in ("clarity_checker_agent", "check_clarity", "has_clarity_passed"):
|
|
120
|
+
from . import clarity_checker
|
|
121
|
+
return getattr(clarity_checker, name)
|
|
122
|
+
|
|
123
|
+
if name in ("tikz_checker_agent", "check_tikz", "has_tikz_passed", "has_tikz_environment"):
|
|
124
|
+
from . import tikz_checker
|
|
125
|
+
return getattr(tikz_checker, name)
|
|
126
|
+
|
|
127
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Alternate solution agent for physics problems.
|
|
2
|
+
|
|
3
|
+
Uses openai-agents SDK to generate alternative solution methods
|
|
4
|
+
while maintaining the same final answer.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from vbagent.agents.base import create_agent, run_agent_sync
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def clean_latex_output(latex: str) -> str:
|
|
14
|
+
"""Clean up LaTeX output by removing markdown code block markers.
|
|
15
|
+
|
|
16
|
+
Removes patterns like:
|
|
17
|
+
- ```latex ... ```
|
|
18
|
+
- ``` ... ```
|
|
19
|
+
- Leading/trailing whitespace
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
latex: Raw LaTeX output from LLM
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Cleaned LaTeX without markdown artifacts
|
|
26
|
+
"""
|
|
27
|
+
if not latex:
|
|
28
|
+
return latex
|
|
29
|
+
|
|
30
|
+
# Remove markdown code block markers with language specifier
|
|
31
|
+
# Matches: ```latex, ```tex, ```LaTeX, etc.
|
|
32
|
+
latex = re.sub(r'^```(?:latex|tex|LaTeX)?\s*\n?', '', latex, flags=re.IGNORECASE)
|
|
33
|
+
|
|
34
|
+
# Remove closing code block marker
|
|
35
|
+
latex = re.sub(r'\n?```\s*$', '', latex)
|
|
36
|
+
|
|
37
|
+
# Also handle case where ``` appears at the start without newline
|
|
38
|
+
latex = re.sub(r'^```\s*', '', latex)
|
|
39
|
+
|
|
40
|
+
return latex.strip()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
from vbagent.models.idea import IdeaResult
|
|
44
|
+
from vbagent.prompts.alternate import (
|
|
45
|
+
SYSTEM_PROMPT,
|
|
46
|
+
USER_TEMPLATE,
|
|
47
|
+
USER_TEMPLATE_WITH_EXISTING,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Create the alternate solution agent
|
|
52
|
+
alternate_agent = create_agent(
|
|
53
|
+
name="AlternateSolution",
|
|
54
|
+
instructions=SYSTEM_PROMPT,
|
|
55
|
+
agent_type="alternate",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def extract_answer(solution: str) -> str:
|
|
60
|
+
"""Extract the final numerical answer from a solution.
|
|
61
|
+
|
|
62
|
+
Looks for common answer patterns in LaTeX solutions.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
solution: The solution text in LaTeX format
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The extracted answer string, or empty string if not found
|
|
69
|
+
"""
|
|
70
|
+
# Common patterns for final answers in physics solutions
|
|
71
|
+
patterns = [
|
|
72
|
+
# Boxed answers: \boxed{...} - handle nested braces
|
|
73
|
+
r'\\boxed\{((?:[^{}]|\{[^{}]*\})*)\}',
|
|
74
|
+
# Answer environment or explicit answer markers
|
|
75
|
+
r'(?:answer|ans|result)\s*[:=]\s*([^\n\\]+)',
|
|
76
|
+
# Final equals in solution: = X (with optional units in \text{})
|
|
77
|
+
r'=\s*([-+]?\d+(?:\.\d+)?(?:\s*\\times\s*10\^?\{?[-+]?\d+\}?)?)\s*(?:\\text\{[^}]*\}|\\[a-zA-Z]+|[a-zA-Z/]+)?(?:\s*$|\s*\\\\|\s*\n)',
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for pattern in patterns:
|
|
81
|
+
matches = re.findall(pattern, solution, re.IGNORECASE | re.MULTILINE)
|
|
82
|
+
if matches:
|
|
83
|
+
# Return the last match (usually the final answer)
|
|
84
|
+
return matches[-1].strip()
|
|
85
|
+
|
|
86
|
+
return ""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def extract_existing_alternates(content: str) -> list[str]:
|
|
90
|
+
"""Extract existing alternate solutions from LaTeX content.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
content: Full LaTeX content of the problem file
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
List of existing alternate solution contents
|
|
97
|
+
"""
|
|
98
|
+
pattern = r'\\begin\{alternatesolution\}(.*?)\\end\{alternatesolution\}'
|
|
99
|
+
matches = re.findall(pattern, content, re.DOTALL)
|
|
100
|
+
return [m.strip() for m in matches]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def has_alternate_solution(content: str) -> bool:
|
|
104
|
+
"""Check if content already has an alternate solution.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
content: Full LaTeX content of the problem file
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
True if alternatesolution environment exists
|
|
111
|
+
"""
|
|
112
|
+
return r'\begin{alternatesolution}' in content
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def count_alternate_solutions(content: str) -> int:
|
|
116
|
+
"""Count the number of alternate solutions in content.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
content: Full LaTeX content of the problem file
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Number of alternatesolution environments
|
|
123
|
+
"""
|
|
124
|
+
return content.count(r'\begin{alternatesolution}')
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def generate_alternate(
|
|
128
|
+
problem: str,
|
|
129
|
+
solution: str,
|
|
130
|
+
ideas: Optional[IdeaResult] = None,
|
|
131
|
+
existing_alternates: Optional[list[str]] = None,
|
|
132
|
+
full_content: Optional[str] = None,
|
|
133
|
+
) -> str:
|
|
134
|
+
"""Generate an alternate solution approach for a physics problem.
|
|
135
|
+
|
|
136
|
+
Creates a different valid solution method that arrives at the same
|
|
137
|
+
final answer as the original solution.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
problem: The problem statement in LaTeX format (legacy, used if full_content not provided)
|
|
141
|
+
solution: The original solution in LaTeX format (legacy, used if full_content not provided)
|
|
142
|
+
ideas: Optional IdeaResult with extracted concepts and techniques
|
|
143
|
+
existing_alternates: List of existing alternate solutions to avoid repeating
|
|
144
|
+
full_content: Full LaTeX file content (preferred - pass entire file)
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Alternative solution in LaTeX format within alternatesolution environment
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
ValueError: If content is empty
|
|
151
|
+
"""
|
|
152
|
+
# Use full_content if provided, otherwise construct from problem/solution
|
|
153
|
+
if full_content:
|
|
154
|
+
content_to_use = full_content.strip()
|
|
155
|
+
else:
|
|
156
|
+
if not problem.strip():
|
|
157
|
+
raise ValueError("Problem cannot be empty")
|
|
158
|
+
if not solution.strip():
|
|
159
|
+
raise ValueError("Solution cannot be empty")
|
|
160
|
+
content_to_use = f"{problem}\n\n\\begin{{solution}}\n{solution}\n\\end{{solution}}"
|
|
161
|
+
|
|
162
|
+
if not content_to_use:
|
|
163
|
+
raise ValueError("Content cannot be empty")
|
|
164
|
+
|
|
165
|
+
# Check if there are existing alternates in the content
|
|
166
|
+
has_existing = r'\begin{alternatesolution}' in content_to_use
|
|
167
|
+
|
|
168
|
+
# Choose template based on whether existing alternates exist
|
|
169
|
+
# Use string replace instead of .format() to avoid issues with LaTeX curly braces
|
|
170
|
+
if has_existing or existing_alternates:
|
|
171
|
+
message = USER_TEMPLATE_WITH_EXISTING.replace('{full_content}', content_to_use)
|
|
172
|
+
else:
|
|
173
|
+
message = USER_TEMPLATE.replace('{full_content}', content_to_use)
|
|
174
|
+
|
|
175
|
+
raw_result = run_agent_sync(alternate_agent, message)
|
|
176
|
+
|
|
177
|
+
# Clean up markdown artifacts from LLM output
|
|
178
|
+
return clean_latex_output(raw_result)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Base agent utilities using OpenAI Agents SDK."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
6
|
+
|
|
7
|
+
# Lazy import for heavy agents SDK - only import at runtime when needed
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from agents import Agent, ModelSettings
|
|
10
|
+
|
|
11
|
+
from vbagent.config import get_model, get_model_settings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_agent_class():
|
|
15
|
+
"""Lazy import of Agent class."""
|
|
16
|
+
from agents import Agent
|
|
17
|
+
return Agent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_runner_class():
|
|
21
|
+
"""Lazy import of Runner class."""
|
|
22
|
+
from agents import Runner
|
|
23
|
+
return Runner
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_model_settings_class():
|
|
27
|
+
"""Lazy import of ModelSettings class."""
|
|
28
|
+
from agents import ModelSettings
|
|
29
|
+
return ModelSettings
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def encode_image(image_path: str) -> tuple[str, str]:
|
|
33
|
+
"""Encode an image file to base64.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
image_path: Path to the image file
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Tuple of (base64_data, media_type)
|
|
40
|
+
"""
|
|
41
|
+
path = Path(image_path)
|
|
42
|
+
if not path.exists():
|
|
43
|
+
raise FileNotFoundError(f"Image not found: {image_path}")
|
|
44
|
+
|
|
45
|
+
with open(path, "rb") as f:
|
|
46
|
+
image_bytes = f.read()
|
|
47
|
+
|
|
48
|
+
image_data = base64.b64encode(image_bytes).decode("utf-8")
|
|
49
|
+
|
|
50
|
+
suffix = path.suffix.lower()
|
|
51
|
+
media_type_map = {
|
|
52
|
+
".jpg": "image/jpeg",
|
|
53
|
+
".jpeg": "image/jpeg",
|
|
54
|
+
".png": "image/png",
|
|
55
|
+
".gif": "image/gif",
|
|
56
|
+
".webp": "image/webp",
|
|
57
|
+
}
|
|
58
|
+
media_type = media_type_map.get(suffix, "image/jpeg")
|
|
59
|
+
|
|
60
|
+
return image_data, media_type
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def create_image_message(image_path: str, text: str) -> list[dict[str, Any]]:
|
|
64
|
+
"""Create a message with image and text for the agent.
|
|
65
|
+
|
|
66
|
+
Uses the OpenAI Responses API format for image input.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
image_path: Path to the image file
|
|
70
|
+
text: Text message to accompany the image
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List containing a single message dict in Responses API format
|
|
74
|
+
"""
|
|
75
|
+
image_data, media_type = encode_image(image_path)
|
|
76
|
+
# Responses API format: message with content list containing input_image and input_text
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
"type": "message",
|
|
80
|
+
"role": "user",
|
|
81
|
+
"content": [
|
|
82
|
+
{
|
|
83
|
+
"type": "input_image",
|
|
84
|
+
"image_url": f"data:{media_type};base64,{image_data}",
|
|
85
|
+
"detail": "auto"
|
|
86
|
+
},
|
|
87
|
+
{"type": "input_text", "text": text}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def create_agent(
|
|
94
|
+
name: str,
|
|
95
|
+
instructions: str,
|
|
96
|
+
model: Optional[str] = None,
|
|
97
|
+
model_settings: Optional["ModelSettings"] = None,
|
|
98
|
+
output_type: Optional[type] = None,
|
|
99
|
+
tools: Optional[list] = None,
|
|
100
|
+
agent_type: Optional[str] = None,
|
|
101
|
+
) -> "Agent":
|
|
102
|
+
"""Create an agent with default configuration.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
name: Agent name
|
|
106
|
+
instructions: System prompt / instructions
|
|
107
|
+
model: Model to use (if None, uses config for agent_type)
|
|
108
|
+
model_settings: Optional ModelSettings for temperature, max_tokens, etc.
|
|
109
|
+
output_type: Optional Pydantic model for structured output
|
|
110
|
+
tools: Optional list of tools (@function_tool decorated functions)
|
|
111
|
+
agent_type: Agent type for config lookup (classifier, scanner, tikz, etc.)
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Configured Agent instance
|
|
115
|
+
"""
|
|
116
|
+
Agent = _get_agent_class()
|
|
117
|
+
|
|
118
|
+
# Get model and settings from config if not explicitly provided
|
|
119
|
+
if model is None:
|
|
120
|
+
model = get_model(agent_type or "default")
|
|
121
|
+
if model_settings is None:
|
|
122
|
+
model_settings = get_model_settings(agent_type or "default")
|
|
123
|
+
|
|
124
|
+
return Agent(
|
|
125
|
+
name=name,
|
|
126
|
+
instructions=instructions,
|
|
127
|
+
model=model,
|
|
128
|
+
model_settings=model_settings,
|
|
129
|
+
output_type=output_type,
|
|
130
|
+
tools=tools or [],
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _print_agent_info(agent: "Agent") -> None:
|
|
135
|
+
"""Print agent information when running.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
agent: The Agent instance being run
|
|
139
|
+
"""
|
|
140
|
+
model = agent.model or "default"
|
|
141
|
+
reasoning = "unknown"
|
|
142
|
+
|
|
143
|
+
# Extract reasoning effort from model_settings if available
|
|
144
|
+
if agent.model_settings:
|
|
145
|
+
settings = agent.model_settings
|
|
146
|
+
if hasattr(settings, 'reasoning') and settings.reasoning:
|
|
147
|
+
reasoning_obj = settings.reasoning
|
|
148
|
+
# Handle both dict and Reasoning object
|
|
149
|
+
if isinstance(reasoning_obj, dict):
|
|
150
|
+
reasoning = reasoning_obj.get('effort', 'unknown')
|
|
151
|
+
elif hasattr(reasoning_obj, 'effort'):
|
|
152
|
+
reasoning = reasoning_obj.effort or 'unknown'
|
|
153
|
+
|
|
154
|
+
# Use dim styling for subtle output
|
|
155
|
+
print(f"\033[2m⚡ {agent.name} | model: {model} | reasoning: {reasoning}\033[0m")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def run_agent(agent: "Agent", input_text: str | list) -> Any:
|
|
159
|
+
"""Run an agent asynchronously and return the final output.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
agent: The Agent instance to run
|
|
163
|
+
input_text: The input text or message (can be string or list for images)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
The agent's final output (string or structured type)
|
|
167
|
+
"""
|
|
168
|
+
Runner = _get_runner_class()
|
|
169
|
+
_print_agent_info(agent)
|
|
170
|
+
result = await Runner.run(agent, input=input_text)
|
|
171
|
+
return result.final_output
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def run_agent_sync(agent: "Agent", input_text: str | list) -> Any:
|
|
175
|
+
"""Run an agent synchronously and return the final output.
|
|
176
|
+
|
|
177
|
+
Uses a thread to allow immediate Ctrl+C interruption.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
agent: The Agent instance to run
|
|
181
|
+
input_text: The input text or message (can be string or list for images)
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
The agent's final output (string or structured type)
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
KeyboardInterrupt: If user presses Ctrl+C
|
|
188
|
+
"""
|
|
189
|
+
import concurrent.futures
|
|
190
|
+
import threading
|
|
191
|
+
|
|
192
|
+
Runner = _get_runner_class()
|
|
193
|
+
_print_agent_info(agent)
|
|
194
|
+
|
|
195
|
+
# Use a thread pool to run the agent, allowing Ctrl+C to interrupt
|
|
196
|
+
result_holder = {"result": None, "error": None}
|
|
197
|
+
|
|
198
|
+
def run_in_thread():
|
|
199
|
+
try:
|
|
200
|
+
result_holder["result"] = Runner.run_sync(agent, input=input_text)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
result_holder["error"] = e
|
|
203
|
+
|
|
204
|
+
thread = threading.Thread(target=run_in_thread, daemon=True)
|
|
205
|
+
thread.start()
|
|
206
|
+
|
|
207
|
+
# Wait for thread with small intervals to allow Ctrl+C handling
|
|
208
|
+
while thread.is_alive():
|
|
209
|
+
thread.join(timeout=0.1) # Check every 100ms for interrupt
|
|
210
|
+
|
|
211
|
+
if result_holder["error"]:
|
|
212
|
+
raise result_holder["error"]
|
|
213
|
+
|
|
214
|
+
return result_holder["result"].final_output
|