@laitszkin/apollo-toolkit 2.9.0 → 2.10.0

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.
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env python3
2
+ """Render TeX formulas with the official KaTeX CLI and wrap the output for reuse."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import pathlib
10
+ import subprocess
11
+ import sys
12
+ import tempfile
13
+ from typing import Iterable
14
+
15
+
16
+ DEFAULT_CSS_HREF = "https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css"
17
+
18
+
19
+ class KatexRenderError(Exception):
20
+ """User-facing error for rendering failures."""
21
+
22
+
23
+ def parse_args(argv: list[str]) -> argparse.Namespace:
24
+ parser = argparse.ArgumentParser(
25
+ prog="render_katex.py",
26
+ description="Render TeX with KaTeX and emit insertion-ready output.",
27
+ )
28
+
29
+ input_group = parser.add_mutually_exclusive_group(required=True)
30
+ input_group.add_argument("--tex", help="Raw TeX expression without delimiters.")
31
+ input_group.add_argument("--input-file", help="Path to a UTF-8 text file containing raw TeX.")
32
+
33
+ parser.add_argument(
34
+ "--output-format",
35
+ choices=("html-fragment", "html-page", "markdown-inline", "markdown-block", "json"),
36
+ default="html-fragment",
37
+ help="How to wrap the rendered KaTeX output.",
38
+ )
39
+ parser.add_argument(
40
+ "--katex-format",
41
+ choices=("html", "mathml", "htmlAndMathml"),
42
+ default="htmlAndMathml",
43
+ help="KaTeX internal output format.",
44
+ )
45
+ parser.add_argument("--display-mode", action="store_true", help="Render in display mode.")
46
+ parser.add_argument("--output-file", help="Write the wrapped output to a file.")
47
+ parser.add_argument("--css-href", default=DEFAULT_CSS_HREF, help="Stylesheet href for html-page/json output.")
48
+ parser.add_argument("--title", default="KaTeX Render", help="Document title for html-page output.")
49
+ parser.add_argument("--lang", default="en", help="HTML lang attribute for html-page output.")
50
+
51
+ parser.add_argument("--macro", action="append", default=[], help="Macro definition in NAME:VALUE form.")
52
+ parser.add_argument("--macro-file", help="Path to a JSON file mapping macro names to expansion strings.")
53
+ parser.add_argument("--error-color", help="Hex color or CSS color name for parse errors.")
54
+ parser.add_argument("--strict", help="KaTeX strict mode setting.")
55
+ parser.add_argument("--trust", help="KaTeX trust mode setting.")
56
+ parser.add_argument("--max-size", type=float, help="Maximum user-specified size in em.")
57
+ parser.add_argument("--max-expand", type=int, help="Maximum macro expansion count.")
58
+ parser.add_argument("--min-rule-thickness", type=float, help="Minimum rule thickness in em.")
59
+ parser.add_argument("--leqno", action="store_true", help="Render display equations with left equation numbers.")
60
+ parser.add_argument("--fleqn", action="store_true", help="Render display equations flush left.")
61
+ parser.add_argument(
62
+ "--color-is-text-color",
63
+ action="store_true",
64
+ help="Interpret \\\\color like legacy text color behavior.",
65
+ )
66
+ parser.add_argument(
67
+ "--no-throw-on-error",
68
+ action="store_true",
69
+ help="Render invalid input with colored source text instead of failing.",
70
+ )
71
+
72
+ return parser.parse_args(argv)
73
+
74
+
75
+ def normalize_path(raw_path: str) -> pathlib.Path:
76
+ path = pathlib.Path(raw_path).expanduser()
77
+ if not path.is_absolute():
78
+ path = pathlib.Path.cwd() / path
79
+ return path.resolve()
80
+
81
+
82
+ def load_tex(args: argparse.Namespace) -> str:
83
+ if args.input_file:
84
+ path = normalize_path(args.input_file)
85
+ if not path.is_file():
86
+ raise KatexRenderError(f"Input file not found: {path}")
87
+ return path.read_text(encoding="utf-8").strip()
88
+ return (args.tex or "").strip()
89
+
90
+
91
+ def load_macro_pairs(values: Iterable[str]) -> list[tuple[str, str]]:
92
+ pairs: list[tuple[str, str]] = []
93
+ for raw_value in values:
94
+ if ":" not in raw_value:
95
+ raise KatexRenderError(f"Invalid --macro value '{raw_value}'. Use NAME:VALUE.")
96
+ name, expansion = raw_value.split(":", 1)
97
+ name = name.strip()
98
+ expansion = expansion.strip()
99
+ if not name or not expansion:
100
+ raise KatexRenderError(f"Invalid --macro value '{raw_value}'. Use NAME:VALUE.")
101
+ pairs.append((name, expansion))
102
+ return pairs
103
+
104
+
105
+ def run_katex_cli(tex: str, args: argparse.Namespace) -> str:
106
+ command = [
107
+ "npx",
108
+ "--yes",
109
+ "--package",
110
+ "katex",
111
+ "katex",
112
+ "--format",
113
+ args.katex_format,
114
+ ]
115
+
116
+ if args.display_mode:
117
+ command.append("--display-mode")
118
+ if args.leqno:
119
+ command.append("--leqno")
120
+ if args.fleqn:
121
+ command.append("--fleqn")
122
+ if args.color_is_text_color:
123
+ command.append("--color-is-text-color")
124
+ if args.no_throw_on_error:
125
+ command.append("--no-throw-on-error")
126
+
127
+ if args.error_color:
128
+ command.extend(["--error-color", args.error_color])
129
+ if args.strict:
130
+ command.extend(["--strict", args.strict])
131
+ if args.trust:
132
+ command.extend(["--trust", args.trust])
133
+ if args.max_size is not None:
134
+ command.extend(["--max-size", str(args.max_size)])
135
+ if args.max_expand is not None:
136
+ command.extend(["--max-expand", str(args.max_expand)])
137
+ if args.min_rule_thickness is not None:
138
+ command.extend(["--min-rule-thickness", str(args.min_rule_thickness)])
139
+
140
+ for name, expansion in load_macro_pairs(args.macro):
141
+ command.extend(["--macro", f"{name}:{expansion}"])
142
+
143
+ if args.macro_file:
144
+ macro_file = normalize_path(args.macro_file)
145
+ if not macro_file.is_file():
146
+ raise KatexRenderError(f"Macro file not found: {macro_file}")
147
+ command.extend(["--macro-file", str(macro_file)])
148
+
149
+ with tempfile.NamedTemporaryFile("w", suffix=".tex", encoding="utf-8", delete=False) as handle:
150
+ handle.write(tex)
151
+ handle.write("\n")
152
+ temp_path = pathlib.Path(handle.name)
153
+
154
+ try:
155
+ command.extend(["--input", str(temp_path)])
156
+ result = subprocess.run(
157
+ command,
158
+ check=False,
159
+ capture_output=True,
160
+ text=True,
161
+ encoding="utf-8",
162
+ )
163
+ finally:
164
+ temp_path.unlink(missing_ok=True)
165
+
166
+ if result.returncode != 0:
167
+ stderr = result.stderr.strip() or "KaTeX CLI failed."
168
+ raise KatexRenderError(stderr)
169
+
170
+ return result.stdout.strip()
171
+
172
+
173
+ def build_html_page(rendered_html: str, args: argparse.Namespace) -> str:
174
+ css_link = ""
175
+ if args.css_href.strip():
176
+ css_link = f' <link rel="stylesheet" href="{args.css_href.strip()}">\n'
177
+
178
+ return (
179
+ "<!DOCTYPE html>\n"
180
+ f'<html lang="{args.lang}">\n'
181
+ "<head>\n"
182
+ ' <meta charset="utf-8">\n'
183
+ f" <title>{args.title}</title>\n"
184
+ f"{css_link}"
185
+ "</head>\n"
186
+ "<body>\n"
187
+ f"{rendered_html}\n"
188
+ "</body>\n"
189
+ "</html>\n"
190
+ )
191
+
192
+
193
+ def wrap_output(rendered_html: str, tex: str, args: argparse.Namespace) -> str:
194
+ if args.output_format == "html-fragment":
195
+ return f"{rendered_html}\n"
196
+ if args.output_format == "html-page":
197
+ return build_html_page(rendered_html, args)
198
+ if args.output_format == "markdown-inline":
199
+ return f"{rendered_html}\n"
200
+ if args.output_format == "markdown-block":
201
+ return f"\n{rendered_html}\n"
202
+ if args.output_format == "json":
203
+ payload = {
204
+ "tex": tex,
205
+ "displayMode": args.display_mode,
206
+ "katexFormat": args.katex_format,
207
+ "cssHref": args.css_href,
208
+ "content": rendered_html,
209
+ }
210
+ return json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
211
+ raise KatexRenderError(f"Unsupported output format: {args.output_format}")
212
+
213
+
214
+ def write_output(content: str, output_file: str | None) -> None:
215
+ if not output_file:
216
+ sys.stdout.write(content)
217
+ return
218
+
219
+ path = normalize_path(output_file)
220
+ path.parent.mkdir(parents=True, exist_ok=True)
221
+ path.write_text(content, encoding="utf-8")
222
+ sys.stdout.write(str(path) + "\n")
223
+
224
+
225
+ def main(argv: list[str]) -> int:
226
+ try:
227
+ args = parse_args(argv)
228
+ tex = load_tex(args)
229
+ if not tex:
230
+ raise KatexRenderError("Input TeX is empty.")
231
+ rendered_html = run_katex_cli(tex, args)
232
+ wrapped = wrap_output(rendered_html, tex, args)
233
+ write_output(wrapped, args.output_file)
234
+ return 0
235
+ except KatexRenderError as exc:
236
+ print(f"[ERROR] {exc}", file=sys.stderr)
237
+ return 1
238
+ except FileNotFoundError as exc:
239
+ missing = exc.filename or "required executable"
240
+ if os.path.basename(missing) in {"npx", "node"}:
241
+ print("[ERROR] node and npx are required to render KaTeX.", file=sys.stderr)
242
+ return 1
243
+ raise
244
+
245
+
246
+ if __name__ == "__main__":
247
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
5
+
6
+ if ! command -v python3 >/dev/null 2>&1; then
7
+ echo "[ERROR] python3 is required." >&2
8
+ exit 1
9
+ fi
10
+
11
+ exec python3 "$script_dir/render_katex.py" "$@"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: learning-error-book
3
- description: A learning-focused error-book workflow. When the user asks to summarize mistakes, the agent summarizes mistakes made while solving questions and generates/updates an error book in Markdown, rendered to PDF (depends on the pdf skill).
3
+ description: A learning-focused error-book workflow. When the user asks to summarize mistakes, the agent summarizes mistakes made while solving questions, writes structured reference data, and renders polished PDFs directly without Markdown as an intermediate.
4
4
  ---
5
5
 
6
6
  # Learning Error Book Skill
@@ -15,23 +15,27 @@ description: A learning-focused error-book workflow. When the user asks to summa
15
15
  ## Standards
16
16
 
17
17
  - Evidence: Summarize mistakes only from traceable question sources, user attempts, and correct-answer evidence.
18
- - Execution: Build an evidence table first, update `error_book/error-book.md`, then render `error_book/error-book.pdf` with Chinese-safe fonts.
18
+ - Execution: Build an evidence table first, write structured reference data, then render polished PDFs directly with Chinese-safe fonts.
19
19
  - Quality: Explain mistake types, concept misunderstandings, and per-question solutions in a way that is specific, complete, and non-speculative.
20
- - Output: Deliver the standardized error-book structure in Markdown and PDF with consistent section coverage.
20
+ - Output: Deliver separate MC and long-question error books, each backed by its own reference file and rendered PDF.
21
21
 
22
- Goal: when the user asks to "summarize mistakes / summarize errors / compile an error book", **summarize mistakes with evidence** and **generate or update** an error book (Markdown -> PDF).
22
+ Goal: when the user asks to "summarize mistakes / summarize errors / compile an error book", summarize mistakes with evidence and generate or update structured error-book data plus polished PDFs directly from that data.
23
23
 
24
24
  ## Behavior Contract (GIVEN/THEN)
25
25
 
26
- GIVEN the user asks to **summarize mistakes/errors**
27
- THEN the agent summarizes the user's mistakes made while solving questions
28
- AND generates or updates an **error book** that includes:
26
+ GIVEN the user asks to summarize mistakes/errors
27
+ THEN the agent summarizes the user's mistakes made while solving questions
28
+ AND generates or updates two error-book tracks when relevant:
29
+ - one for multiple-choice questions
30
+ - one for long-answer questions
31
+ AND each track includes:
29
32
  - Coverage scope (which question files / sources are included)
30
33
  - Common mistake types overview
31
34
  - Conceptual mistake highlights (definition, user's common misjudgment, cautions)
32
35
  - Mistake-by-mistake analysis and solutions
33
- - For MC questions: explain why **each option** is wrong/right, and why the correct option is correct
34
- AND the delivered error book must be a **PDF rendered from Markdown**, using fonts that properly render **Chinese text and Markdown symbols**.
36
+ - For MC questions: explain why each option is wrong/right, and why the correct option is correct
37
+ - For long-answer questions: compare the expected solution steps against the user's steps, show exactly where the divergence starts, and identify the key concepts involved
38
+ AND the delivered error books must be polished PDFs rendered directly from structured data, without Markdown as an intermediate.
35
39
 
36
40
  ## Trigger Conditions
37
41
 
@@ -52,61 +56,72 @@ If the PDF is scanned/image-based and text extraction fails:
52
56
 
53
57
  ## Output Spec (Required Sections)
54
58
 
55
- The error book must contain:
56
- 1) **Coverage Scope**: which question files/sources are included (with paths; include page/question ids when available)
57
- 2) **Common Mistake Types Overview**: 3-8 categories (concept misunderstanding, misreading conditions, derivation/calculation error, option traps, etc.), with representative questions
58
- 3) **Conceptual Mistake Highlights** (per concept):
59
+ The error books must contain:
60
+ 1) Coverage Scope: which question files/sources are included (with paths; include page/question ids when available)
61
+ 2) Common Mistake Types Overview: 3-8 categories (concept misunderstanding, misreading conditions, derivation/calculation error, option traps, etc.), with representative questions
62
+ 3) Conceptual Mistake Highlights (per concept):
59
63
  - Definition (precise and actionable)
60
64
  - User's common misjudgment (mapped to concrete mistakes)
61
65
  - Cautions / checklists to avoid repeating the mistake
62
- 4) **Per-Question Mistake & Solution**:
66
+ 4) Per-Question Mistake & Solution:
63
67
  - Traceable locator: file + page/question id
64
68
  - User answer vs correct answer
65
69
  - Why it's wrong (link back to mistake type + concept)
66
70
  - Correct solution (step-by-step)
67
- - For **MC**: explain why **each option** is wrong/right, and why the correct option is correct
71
+ - For MC: explain why each option is wrong/right, and why the correct option is correct
72
+ - For Long Question: compare each expected step with the user's corresponding step, explain the gap at each step, state the first incorrect step clearly, and list the key concepts that question depends on
68
73
 
69
74
  Formats:
70
- - Editable source: `error_book/error-book.md` (Markdown)
71
- - Deliverable: `error_book/error-book.pdf` (PDF rendered from Markdown)
75
+ - MC reference: `error_book/references/mc-question-reference.json`
76
+ - Long-question reference: `error_book/references/long-question-reference.json`
77
+ - MC deliverable: `error_book/mc-question-error-book.pdf`
78
+ - Long-question deliverable: `error_book/long-question-error-book.pdf`
72
79
 
73
80
  ## Recommended File Layout (Keep It Consistent)
74
81
 
75
82
  ```text
76
83
  error_book/
77
- error-book.md
78
- error-book.pdf
84
+ mc-question-error-book.pdf
85
+ long-question-error-book.pdf
86
+ references/
87
+ mc-question-reference.json
88
+ long-question-reference.json
79
89
  sources/ # optional: shortcuts/copies/list of source PDFs
80
90
  ```
81
91
 
82
92
  ## Workflow (Required)
83
93
 
84
- 1) **Determine coverage**
94
+ 1) Determine coverage
85
95
  - If the user provided files/question ids: add them to Coverage Scope
86
96
  - If not: search the workspace for relevant PDFs and confirm with the user
87
97
 
88
- 2) **Extract question text + answers/explanations (extract when possible)**
98
+ 2) Extract question text + answers/explanations (extract when possible)
89
99
  - Use the `pdf` skill (pypdf/pdfplumber/OCR as available)
90
100
  - If extraction fails, request user-provided text/screenshots
91
101
 
92
- 3) **Build an evidence table before writing**
102
+ 3) Build an evidence table before writing
93
103
  - For each question: locator, user answer, correct answer, mistake type, concept(s), explanation
94
- - Then map it into the required four sections
104
+ - For long-answer questions, also collect expected steps, user steps, step-by-step gaps, first wrong step, and key concepts
105
+ - Then map it into the required sections for the relevant track
95
106
 
96
- 4) **Generate/update `error_book/error-book.md`**
97
- - If missing: start from `assets/error_book_template.md`
98
- - If exists: preserve existing content; append new mistakes; update Overview + Concepts sections
107
+ 4) Generate/update structured reference files
108
+ - For MC questions: start from `assets/mc_question_reference_template.json`
109
+ - For long-answer questions: start from `assets/long_question_reference_template.json`
110
+ - If a reference file already exists: preserve existing entries, append new evidence, and refresh overview/concept sections
99
111
 
100
- 5) **Render Markdown -> PDF (CJK font support)**
112
+ 5) Render structured data -> PDF (CJK font support)
101
113
  - Run:
102
- - `python3 learning-error-book/scripts/render_markdown_to_pdf.py error_book/error-book.md error_book/error-book.pdf`
114
+ - `python3 learning-error-book/scripts/render_error_book_json_to_pdf.py error_book/references/mc-question-reference.json error_book/mc-question-error-book.pdf`
115
+ - `python3 learning-error-book/scripts/render_error_book_json_to_pdf.py error_book/references/long-question-reference.json error_book/long-question-error-book.pdf`
103
116
  - If paper size/font needs change: adjust script flags (`--help`)
104
117
 
105
118
  ## Built-in Template
106
119
 
107
- - `assets/error_book_template.md`: template for first-time creation
120
+ - `assets/mc_question_reference_template.json`: MC error-book structured template
121
+ - `assets/long_question_reference_template.json`: long-answer error-book structured template
108
122
 
109
123
  ## Rendering Notes (Avoid Pitfalls)
110
124
 
111
- - Supported Markdown subset: headings, lists, **bold**/**italic**, inline `code`, fenced code blocks
112
- - For complex tables/math: prefer bullet lists + step-by-step derivations, or paste original content into the question section
125
+ - Avoid lossy Markdown conversion. Keep symbols, formulas, and option text in the structured reference payload.
126
+ - For long-answer questions, preserve the original step granularity instead of merging multiple reasoning steps into one.
127
+ - Keep key-concept labels stable across questions so the concept summary can aggregate them cleanly.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Learning Error Book"
3
- short_description: "Summarize mistakes into a Markdown-to-PDF error book"
4
- default_prompt: "Use $learning-error-book to gather evidence about the user's mistakes, use $pdf whenever PDF extraction or OCR is needed, generate or update error_book/error-book.md from the skill template, and render the final deliverable to error_book/error-book.pdf."
3
+ short_description: "Summarize mistakes into structured MC and long-question error books"
4
+ default_prompt: "Use $learning-error-book to gather evidence about the user's mistakes, use $pdf whenever PDF extraction or OCR is needed, generate or update error_book/references/mc-question-reference.json and error_book/references/long-question-reference.json as applicable, then render polished PDFs directly to error_book/mc-question-error-book.pdf and error_book/long-question-error-book.pdf without using Markdown as an intermediate."
@@ -0,0 +1,57 @@
1
+ {
2
+ "book_type": "long-question",
3
+ "title": "Long Question Error Book",
4
+ "last_updated": "YYYY-MM-DD",
5
+ "coverage_scope": [
6
+ {
7
+ "source_path": "",
8
+ "included_questions": [],
9
+ "notes": ""
10
+ }
11
+ ],
12
+ "mistake_overview": [
13
+ {
14
+ "type": "Incomplete derivation",
15
+ "summary": "",
16
+ "representative_questions": []
17
+ }
18
+ ],
19
+ "concept_highlights": [
20
+ {
21
+ "name": "",
22
+ "definition": "",
23
+ "common_misjudgment": "",
24
+ "checklist": []
25
+ }
26
+ ],
27
+ "questions": [
28
+ {
29
+ "question_id": "",
30
+ "source_path": "",
31
+ "page_or_locator": "",
32
+ "stem": "",
33
+ "user_answer": "",
34
+ "correct_answer": "",
35
+ "mistake_type": "",
36
+ "concepts": [],
37
+ "why_wrong": "",
38
+ "first_incorrect_step": "",
39
+ "key_concepts": [
40
+ {
41
+ "name": "",
42
+ "why_it_matters": ""
43
+ }
44
+ ],
45
+ "correct_solution_steps": [],
46
+ "step_comparison": [
47
+ {
48
+ "step_no": 1,
49
+ "expected_step": "",
50
+ "user_step": "",
51
+ "gap": "",
52
+ "fix": ""
53
+ }
54
+ ]
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "book_type": "mc-question",
3
+ "title": "MC Question Error Book",
4
+ "last_updated": "YYYY-MM-DD",
5
+ "coverage_scope": [
6
+ {
7
+ "source_path": "",
8
+ "included_questions": [],
9
+ "notes": ""
10
+ }
11
+ ],
12
+ "mistake_overview": [
13
+ {
14
+ "type": "Concept misunderstanding",
15
+ "summary": "",
16
+ "representative_questions": []
17
+ }
18
+ ],
19
+ "concept_highlights": [
20
+ {
21
+ "name": "",
22
+ "definition": "",
23
+ "common_misjudgment": "",
24
+ "checklist": []
25
+ }
26
+ ],
27
+ "questions": [
28
+ {
29
+ "question_id": "",
30
+ "source_path": "",
31
+ "page_or_locator": "",
32
+ "stem": "",
33
+ "user_answer": "",
34
+ "correct_answer": "",
35
+ "mistake_type": "",
36
+ "concepts": [],
37
+ "why_wrong": "",
38
+ "correct_solution_steps": [],
39
+ "options": [
40
+ {
41
+ "label": "A",
42
+ "text": "",
43
+ "verdict": "wrong",
44
+ "reason": ""
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }