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/cli/common.py CHANGED
@@ -684,16 +684,19 @@ def find_image_for_problem(
684
684
  tex_file: Path,
685
685
  images_dir: Optional[str | Path] = None,
686
686
  extensions: tuple[str, ...] = (".png", ".jpg", ".jpeg", ".webp", ".gif"),
687
+ auto_discover: bool = True,
687
688
  ) -> Optional[Path]:
688
689
  """Find the corresponding image file for a tex file.
689
690
 
690
- Searches for an image with the same base name as the tex file
691
- in the specified images directory.
691
+ Searches for an image with the same base name as the tex file.
692
+ If images_dir is not provided and auto_discover is True, searches
693
+ common image locations automatically.
692
694
 
693
695
  Args:
694
696
  tex_file: Path to the .tex file
695
697
  images_dir: Directory to search for images (optional)
696
698
  extensions: Image file extensions to search for
699
+ auto_discover: Whether to auto-search common locations if images_dir not provided
697
700
 
698
701
  Returns:
699
702
  Path to the image file if found, None otherwise
@@ -702,39 +705,98 @@ def find_image_for_problem(
702
705
  tex_file = Path("src/src_tex/problem_1.tex")
703
706
  images_dir = "src/src_images"
704
707
  # Will look for: src/src_images/problem_1.png, problem_1.jpg, etc.
708
+
709
+ # Auto-discovery (when images_dir is None):
710
+ tex_file = Path("agentic/scans/Problem_12.tex")
711
+ # Will search: images/, ../images/, agentic/images/, etc.
705
712
  """
706
- if images_dir is None:
713
+ base_name = tex_file.stem
714
+
715
+ def _search_in_dir(search_dir: Path) -> Optional[Path]:
716
+ """Search for image in a specific directory."""
717
+ if not search_dir.exists():
718
+ return None
719
+
720
+ for ext in extensions:
721
+ image_path = search_dir / f"{base_name}{ext}"
722
+ if image_path.exists():
723
+ return image_path
724
+
725
+ # Also try case-insensitive match
726
+ image_path_upper = search_dir / f"{base_name}{ext.upper()}"
727
+ if image_path_upper.exists():
728
+ return image_path_upper
729
+
730
+ # Try glob pattern for case-insensitive search
731
+ for ext in extensions:
732
+ pattern = f"{base_name}.*"
733
+ matches = list(search_dir.glob(pattern))
734
+ for match in matches:
735
+ if match.suffix.lower() in extensions:
736
+ return match
737
+
707
738
  return None
708
739
 
709
- images_path = Path(images_dir)
710
- if not images_path.exists():
740
+ # If images_dir is explicitly provided, search only there
741
+ if images_dir is not None:
742
+ images_path = Path(images_dir)
743
+ return _search_in_dir(images_path)
744
+
745
+ # Auto-discovery mode
746
+ if not auto_discover:
711
747
  return None
712
748
 
713
- # Get the base name without extension
714
- base_name = tex_file.stem
749
+ tex_dir = tex_file.parent
750
+
751
+ # Common image directory locations to search (in priority order)
752
+ search_locations = [
753
+ # Same directory as tex file
754
+ tex_dir,
755
+ # images/ sibling directory
756
+ tex_dir.parent / "images",
757
+ # images/ subdirectory
758
+ tex_dir / "images",
759
+ # Parent's parent images/ (for nested structures like agentic/scans/)
760
+ tex_dir.parent.parent / "images",
761
+ # src_images pattern (common in some projects)
762
+ tex_dir.parent / "src_images",
763
+ ]
715
764
 
716
- # Search for image with matching name
717
- for ext in extensions:
718
- image_path = images_path / f"{base_name}{ext}"
719
- if image_path.exists():
720
- return image_path
721
-
722
- # Also try case-insensitive match
723
- image_path_upper = images_path / f"{base_name}{ext.upper()}"
724
- if image_path_upper.exists():
725
- return image_path_upper
726
-
727
- # Try glob pattern for case-insensitive search
728
- for ext in extensions:
729
- pattern = f"{base_name}.*"
730
- matches = list(images_path.glob(pattern))
731
- for match in matches:
732
- if match.suffix.lower() in extensions:
733
- return match
765
+ # Also check for pattern-based sibling directories
766
+ # e.g., src_tex -> src_images, scans -> images
767
+ dir_name = tex_dir.name
768
+ if dir_name.endswith("_tex"):
769
+ search_locations.append(tex_dir.parent / dir_name.replace("_tex", "_images"))
770
+ if dir_name == "scans":
771
+ search_locations.append(tex_dir.parent / "images")
772
+
773
+ for search_dir in search_locations:
774
+ result = _search_in_dir(search_dir)
775
+ if result:
776
+ return result
734
777
 
735
778
  return None
736
779
 
737
780
 
781
+ def has_diagram_placeholder(content: str) -> bool:
782
+ """Check if content contains a diagram placeholder that needs an image.
783
+
784
+ Detects patterns like:
785
+ - \\input{diagram}
786
+ - % \\input{diagram} (commented placeholder)
787
+ - \\begin{center}\\input{diagram}\\end{center}
788
+
789
+ Args:
790
+ content: LaTeX content to check
791
+
792
+ Returns:
793
+ True if a diagram placeholder is found (including commented ones)
794
+ """
795
+ import re
796
+ # Match both active and commented placeholders
797
+ return bool(re.search(r'%?\s*\\input\{diagram\}', content))
798
+
799
+
738
800
  def discover_images_dir(tex_dir: Path) -> Optional[Path]:
739
801
  """Auto-discover images directory based on tex directory structure.
740
802
 
vbagent/cli/convert.py CHANGED
@@ -86,7 +86,7 @@ def _format_latex(content: str) -> str:
86
86
  return '\n'.join(formatted_lines)
87
87
 
88
88
 
89
- VALID_FORMAT_CHOICES = ["mcq_sc", "mcq_mc", "subjective", "integer"]
89
+ VALID_FORMAT_CHOICES = ["mcq_sc", "mcq_mc", "subjective", "integer", "match", "passage"]
90
90
 
91
91
 
92
92
  def parse_tex_content(tex_path: str) -> str:
@@ -1,38 +1,67 @@
1
1
  """Data models for vbagent.
2
2
 
3
3
  Uses lazy imports to avoid loading pydantic until models are actually needed.
4
+
5
+ Available models:
6
+ - ClassificationResult: Result from image classification
7
+ - ScanResult: Result from LaTeX extraction
8
+ - IdeaResult: Extracted physics concepts
9
+ - PipelineResult: Full pipeline output
10
+ - ReviewResult, Suggestion, ReviewStats: QA review models
11
+ - VersionStore, StoredSuggestion: Version tracking
12
+ - BatchResult, BatchStats: Batch processing models
4
13
  """
5
14
 
6
15
  from typing import TYPE_CHECKING
7
16
 
8
17
  if TYPE_CHECKING:
9
- from .classification import ClassificationResult
18
+ from .classification import ClassificationResult, QuestionType, Difficulty, DiagramType
10
19
  from .scan import ScanResult
11
20
  from .idea import IdeaResult
12
21
  from .pipeline import PipelineResult
13
22
  from .review import ReviewIssueType, Suggestion, ReviewResult, ReviewStats
14
23
  from .version_store import SuggestionStatus, StoredSuggestion, VersionStore
24
+ from .batch import BatchDatabase, ImageRecord, ProcessingStatus
25
+ from .diff import generate_unified_diff, apply_unified_diff, apply_diff, parse_diff
15
26
 
16
27
  __all__ = [
28
+ # Classification
17
29
  "ClassificationResult",
30
+ "QuestionType",
31
+ "Difficulty",
32
+ "DiagramType",
33
+ # Scan
18
34
  "ScanResult",
35
+ # Idea
19
36
  "IdeaResult",
37
+ # Pipeline
20
38
  "PipelineResult",
39
+ # Review
21
40
  "ReviewIssueType",
22
41
  "Suggestion",
23
42
  "ReviewResult",
24
43
  "ReviewStats",
44
+ # Version store
25
45
  "SuggestionStatus",
26
46
  "StoredSuggestion",
27
47
  "VersionStore",
48
+ # Batch
49
+ "BatchDatabase",
50
+ "ImageRecord",
51
+ "ProcessingStatus",
52
+ # Diff utilities
53
+ "generate_unified_diff",
54
+ "apply_unified_diff",
55
+ "apply_diff",
56
+ "parse_diff",
28
57
  ]
29
58
 
30
59
 
31
60
  def __getattr__(name: str):
32
61
  """Lazy import of model classes to speed up CLI startup."""
33
- if name == "ClassificationResult":
34
- from .classification import ClassificationResult
35
- return ClassificationResult
62
+ if name in ("ClassificationResult", "QuestionType", "Difficulty", "DiagramType"):
63
+ from . import classification
64
+ return getattr(classification, name)
36
65
 
37
66
  if name == "ScanResult":
38
67
  from .scan import ScanResult
@@ -54,4 +83,12 @@ def __getattr__(name: str):
54
83
  from . import version_store
55
84
  return getattr(version_store, name)
56
85
 
86
+ if name in ("BatchDatabase", "ImageRecord", "ProcessingStatus"):
87
+ from . import batch
88
+ return getattr(batch, name)
89
+
90
+ if name in ("generate_unified_diff", "apply_unified_diff", "apply_diff", "parse_diff"):
91
+ from . import diff
92
+ return getattr(diff, name)
93
+
57
94
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -1 +1,120 @@
1
- """Prompt modules for vbagent agents."""
1
+ """Prompt modules for vbagent agents.
2
+
3
+ Provides prompt templates for all agent types. These can be used
4
+ to customize agent behavior or create custom agents.
5
+
6
+ Usage:
7
+ from vbagent.prompts import get_scanner_prompt, get_variant_prompt
8
+
9
+ # Get scanner prompt for MCQ questions
10
+ prompt = get_scanner_prompt("mcq_sc")
11
+
12
+ # Get variant prompts
13
+ system, user = get_variant_prompt("numerical")
14
+
15
+ # Access individual prompts
16
+ from vbagent.prompts import CLASSIFIER_PROMPT, IDEA_PROMPT
17
+
18
+ Available prompt modules:
19
+ - scanner: LaTeX extraction prompts by question type
20
+ - variants: Problem variant generation prompts
21
+ - classifier: Image classification prompt
22
+ - idea: Concept extraction prompt
23
+ - alternate: Alternate solution prompt
24
+ - reviewer: QA review prompt
25
+ - tikz: TikZ diagram generation prompt
26
+ - solution_checker, grammar_checker, clarity_checker, tikz_checker: QA checker prompts
27
+ """
28
+
29
+ from typing import TYPE_CHECKING
30
+
31
+ if TYPE_CHECKING:
32
+ from .scanner import get_scanner_prompt, SCANNER_PROMPTS
33
+ from .classifier import SYSTEM_PROMPT as CLASSIFIER_PROMPT
34
+ from .idea import SYSTEM_PROMPT as IDEA_PROMPT
35
+ from .alternate import SYSTEM_PROMPT as ALTERNATE_PROMPT
36
+ from .reviewer import SYSTEM_PROMPT as REVIEWER_PROMPT
37
+ from .tikz import SYSTEM_PROMPT as TIKZ_PROMPT
38
+ from .solution_checker import SYSTEM_PROMPT as SOLUTION_CHECKER_PROMPT
39
+ from .grammar_checker import SYSTEM_PROMPT as GRAMMAR_CHECKER_PROMPT
40
+ from .clarity_checker import SYSTEM_PROMPT as CLARITY_CHECKER_PROMPT
41
+ from .tikz_checker import SYSTEM_PROMPT as TIKZ_CHECKER_PROMPT
42
+
43
+ __all__ = [
44
+ # Scanner
45
+ "get_scanner_prompt",
46
+ "SCANNER_PROMPTS",
47
+ # Variant
48
+ "get_variant_prompt",
49
+ # Individual prompts
50
+ "CLASSIFIER_PROMPT",
51
+ "IDEA_PROMPT",
52
+ "ALTERNATE_PROMPT",
53
+ "REVIEWER_PROMPT",
54
+ "TIKZ_PROMPT",
55
+ "SOLUTION_CHECKER_PROMPT",
56
+ "GRAMMAR_CHECKER_PROMPT",
57
+ "CLARITY_CHECKER_PROMPT",
58
+ "TIKZ_CHECKER_PROMPT",
59
+ ]
60
+
61
+
62
+ def get_variant_prompt(variant_type: str) -> tuple[str, str]:
63
+ """Get the system and user prompts for a variant type.
64
+
65
+ Args:
66
+ variant_type: Type of variant (numerical, context, conceptual, calculus)
67
+
68
+ Returns:
69
+ Tuple of (system_prompt, user_template)
70
+
71
+ Raises:
72
+ ValueError: If variant_type is not valid
73
+ """
74
+ from vbagent.agents.variant import get_variant_prompt as _get_variant_prompt
75
+ return _get_variant_prompt(variant_type)
76
+
77
+
78
+ def __getattr__(name: str):
79
+ """Lazy import of prompt modules."""
80
+ if name in ("get_scanner_prompt", "SCANNER_PROMPTS"):
81
+ from . import scanner
82
+ return getattr(scanner, name)
83
+
84
+ if name == "CLASSIFIER_PROMPT":
85
+ from .classifier import SYSTEM_PROMPT
86
+ return SYSTEM_PROMPT
87
+
88
+ if name == "IDEA_PROMPT":
89
+ from .idea import SYSTEM_PROMPT
90
+ return SYSTEM_PROMPT
91
+
92
+ if name == "ALTERNATE_PROMPT":
93
+ from .alternate import SYSTEM_PROMPT
94
+ return SYSTEM_PROMPT
95
+
96
+ if name == "REVIEWER_PROMPT":
97
+ from .reviewer import SYSTEM_PROMPT
98
+ return SYSTEM_PROMPT
99
+
100
+ if name == "TIKZ_PROMPT":
101
+ from .tikz import SYSTEM_PROMPT
102
+ return SYSTEM_PROMPT
103
+
104
+ if name == "SOLUTION_CHECKER_PROMPT":
105
+ from .solution_checker import SYSTEM_PROMPT
106
+ return SYSTEM_PROMPT
107
+
108
+ if name == "GRAMMAR_CHECKER_PROMPT":
109
+ from .grammar_checker import SYSTEM_PROMPT
110
+ return SYSTEM_PROMPT
111
+
112
+ if name == "CLARITY_CHECKER_PROMPT":
113
+ from .clarity_checker import SYSTEM_PROMPT
114
+ return SYSTEM_PROMPT
115
+
116
+ if name == "TIKZ_CHECKER_PROMPT":
117
+ from .tikz_checker import SYSTEM_PROMPT
118
+ return SYSTEM_PROMPT
119
+
120
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")