fix(writer): כלל-הסגנון בסוף הפרומפט — הוא היה שם, במקום שבו הוא לא תופס
הרשימה הקנונית של lessons.ANTI_PATTERNS כבר הוזרקה לכותב, אבל בתו ~46,781 מתוך 46,950 של style_context — שהוא עצמו מקטע אחד מתוך ~12 בפרומפט. הטיוטות המשיכו לפלוט בדיוק את מה שהיא אוסרת. A/B מדוד מול הסופיים החתומים (9 תיקים, 60 ייצורים, 2026-07-28) הראה שאותו כלל, בסוף הפרומפט, חותך anti_pattern_total ב-72–93%: block-vav opus-4-8 1.75→0.12 · opus-5 2.25→0.62 block-zayin opus-4-8 4.57→0.43 · opus-5 4.43→0.43 וב-distance: −12%/−29% (4-8), −9%/−27% (5). זה שיפור גדול פי-3 מכל הבדל שנמדד בין המודלים עצמם. - `lessons.anti_pattern_directive()` — רינדור שני של אותה רשימה קנונית (מקור אחד, שתי תצוגות — לא שני כללים). - מתווסף **אחרון** בשני מסלולי-הכתיבה: `write_block` (בתהליך) ו- `get_block_context` (סוכן legal-writer). אילו הוחל רק באחד, שני הכותבים היו נפרדים בסגנון (G2). - **תיקון בליעה-שקטה (§6):** הרשימה הקנונית רונדרה בתוך לולאת-ה-overrides, כך שכשל-DB בקטגוריה מוקדמת (golden_ratios) הפיל את הלולאה והשמיט את אינווריאנטי-הסגנון כליל — עם אזהרה גנרית בלבד. עכשיו היא מרונדרת ללא תנאי, לפני כל קריאת-DB; הערות-היו"ר מתווספות מעליה. invariants: G11 (תוכן משפטי — סגנון דפנה) · G2 (מקור-אמת יחיד לכלל, ושני מסלולי-הכתיבה מיושרים) · §6 (אין בליעה שקטה). בדיקות: 473 passed (3 חדשות — רינדור מלא, שני המסלולים, שרידות לכשל-DB). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,10 @@ from pathlib import Path
|
||||
from legal_mcp import config
|
||||
from legal_mcp.services import db, embeddings, claude_session, audit, storage
|
||||
from legal_mcp.services.lessons import (
|
||||
ANTI_PATTERNS as _ANTI_PATTERNS,
|
||||
OUTCOME_LABELS_HE,
|
||||
PRACTICE_AREA_OVERRIDES,
|
||||
anti_pattern_directive,
|
||||
canonical_outcome,
|
||||
get_content_checklist,
|
||||
get_methodology_summary,
|
||||
@@ -468,6 +470,12 @@ async def write_block(
|
||||
if instructions:
|
||||
prompt += f"\n\n## הנחיות נוספות:\n{instructions}"
|
||||
|
||||
# LAST in the prompt, deliberately (see lessons.anti_pattern_directive): the
|
||||
# same canonical rule already appears inside style_context, but ~47K chars
|
||||
# deep, where it measurably fails to bind. Restating it here is the only
|
||||
# change the A/B isolated as effective — so nothing may be appended after it.
|
||||
prompt += "\n\n" + anti_pattern_directive()
|
||||
|
||||
# Block י requires approved direction
|
||||
if block_id == "block-yod":
|
||||
dir_doc = (decision or {}).get("direction_doc") or {}
|
||||
@@ -1107,6 +1115,16 @@ async def _build_style_context(practice_area: str = "") -> str:
|
||||
# ── למידה מצטברת (T15) — עריכות היו"ר ב-/methodology + לקחי /training ──
|
||||
# גובר על ברירות-המחדל לעיל. כך כל מה שלמדנו עד היום מגיע לכותב.
|
||||
learned: list[str] = []
|
||||
# The canonical anti-patterns are rendered UNCONDITIONALLY, before any DB
|
||||
# call. They used to be produced inside the overrides loop below — so a
|
||||
# failure on an EARLIER category (e.g. golden_ratios) aborted the loop and
|
||||
# dropped the style invariants from the prompt silently, with only a generic
|
||||
# "overrides not loaded" warning to show for it (§6). A chair-override
|
||||
# outage must not be able to un-teach Dafna's structural style.
|
||||
learned.append("\n**אנטי-דפוסים (להימנע) — כתוב נרטיב משפטי רציף; הימנע מ:**")
|
||||
for ap in _ANTI_PATTERNS:
|
||||
learned.append(f"- {ap['note']}")
|
||||
|
||||
try:
|
||||
for cat, label in (
|
||||
("golden_ratios", "יחסי-זהב (אחוזי-סעיפים)"),
|
||||
@@ -1125,10 +1143,8 @@ async def _build_style_context(practice_area: str = "") -> str:
|
||||
# corrects them, and drafts keep emitting them (the gap that left
|
||||
# 8137 with 28 hits). Chair additions layer on top; they never
|
||||
# remove the canonical ones.
|
||||
from legal_mcp.services.lessons import ANTI_PATTERNS as _ANTI
|
||||
learned.append(f"\n**{label} — כתוב נרטיב משפטי רציף; הימנע מ:**")
|
||||
for ap in _ANTI:
|
||||
learned.append(f"- {ap['note']}")
|
||||
# The canonical list is already rendered above, outside this try —
|
||||
# here we only layer the chair's ADDITIONS on top of it.
|
||||
for k, v in (ov or {}).items():
|
||||
learned.append(f"- (יו\"ר) {k}: {json.dumps(v, ensure_ascii=False)}")
|
||||
continue
|
||||
@@ -1264,6 +1280,12 @@ async def get_block_context(case_id: UUID, block_id: str, instructions: str = ""
|
||||
if instructions:
|
||||
formatted_prompt += f"\n\n## הנחיות נוספות:\n{instructions}"
|
||||
|
||||
# Same closing directive, same position, same canonical source as write_block.
|
||||
# This is the EXTERNAL-writer path (legal-writer agent) — if the rule were
|
||||
# applied only in write_block, agent-written blocks would keep emitting the
|
||||
# anti-patterns and the two writers would drift apart (G2).
|
||||
formatted_prompt += "\n\n" + anti_pattern_directive()
|
||||
|
||||
# Block י requires approved direction
|
||||
if block_id == "block-yod":
|
||||
dir_doc = (decision or {}).get("direction_doc") or {}
|
||||
|
||||
Reference in New Issue
Block a user