diagram kit: triangles and quadrilaterals from problem data, shape patterns, translate points, arithmetic/quote/duplicate-key repairs, clock guard (tutor-v10-shape-anchor)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 06:11:43 +03:30
parent a8cd681d65
commit 50df7bd371
4 changed files with 335 additions and 13 deletions

View File

@@ -38,7 +38,7 @@ _RAW_MARKUP = re.compile(r'\\[A-Za-z(\[]|\$|\*\*|^\s*#{1,6}\s|^\s*\|\s*:?-{3,}',
# بشود گفت «کدام نسخه‌ی پرامپت جوابِ بهتری می‌داد» — بدونِ این، همه‌ی فیدبک‌ها
# در یک کاسه می‌ریزند و مقایسه‌ی نسخه‌ها ممکن نیست. کشِ پاسخ هم با همین نسخه
# کلید می‌خورد، پس جوابِ نسخه‌ی قبلی بعد از دیپلوی دوباره سرو نمی‌شود.
PROMPT_VERSION = 'tutor-v8-asked-figure'
PROMPT_VERSION = 'tutor-v10-shape-anchor'
_PRIMARY = {'اول', 'دوم', 'سوم', 'چهارم', 'پنجم', 'ششم'}
_MIDDLE = {'هفتم', 'هشتم', 'نهم'}
@@ -259,12 +259,16 @@ def build_messages(question: str, context_chunks: list[dict] | None,
messages.append({'role': role, 'content': text[:_HISTORY_CHARS]})
# سوالِ قبلیِ کاربر: «اگه به‌جای ۶ بذاریم ۴ چی؟» کلمه‌ی شکل‌دار ندارد، ولی ادامه‌ی
# همان مسئله‌ی شکل‌دار است و شکلش باید با عددِ تازه دوباره کشیده شود.
previous = next((t.get('text') or '' for t in reversed(history or []) if t.get('role') == 'user'), '')
previous = _previous_question(history)
messages.append({'role': 'user', 'content': _user_prompt(
question, contexts, focus, primary=grade_tier(grade_title) == 'primary', previous=previous)})
return messages
def _previous_question(history) -> str:
return next((t.get('text') or '' for t in reversed(history or []) if t.get('role') == 'user'), '')
def generate_answer(question: str, context_chunks: list[dict] | None,
grade_title: str = '', history: list[dict] | None = None,
model: str | None = None, focus: str = '') -> tuple[str, list[str], dict]:
@@ -290,6 +294,8 @@ def generate_answer(question: str, context_chunks: list[dict] | None,
raw = (completion.choices[0].message.content or '').strip()
# بلوکِ شکل پیش از to_plain جدا می‌شود؛ to_plain کسرها و «*» را در JSON عوض می‌کرد.
raw, diagram = diagram_mod.extract(raw)
# سوالِ ادامه‌ای («حالا ۱۰ دقیقه بعد؟») با سوالِ قبلی سنجیده می‌شود
diagram = diagram_mod.fits(f'{question}\n{focus}\n{_previous_question(history)}', diagram)
answer = to_plain(raw, fraction_word=grade_tier(grade_title) == 'primary')
if not answer:
answer = 'متأسفم، الان نتونستم پاسخِ مناسبی بسازم. لطفاً دوباره بپرس.'
@@ -351,7 +357,7 @@ def stream_answer(question: str, context_chunks: list[dict] | None,
yield clean
tail = sanitizer.feed(splitter.flush()) + sanitizer.flush()
if result is not None:
result['diagram'] = splitter.diagram
result['diagram'] = diagram_mod.fits(f'{question}\n{focus}\n{_previous_question(history)}', splitter.diagram)
if tail.strip():
yield tail