feat(style-acq T0): הזרקת פרופיל-הקול לכותב + מדיניות-העתקה + הפרדת דוגמאות↔פסיקה
הלוֹבר הראשי של מערכת רכישת-הסגנון. block_writer עבר היום מ"העתקה + ערבוב-מהות" ל"הכללת-סגנון + הפרדה": - _build_style_context: טוען את daphna-voice-fingerprint.md (פרופיל-הקול המופשט — המנגנון המרכזי) + מדיניות-העתקה מפורשת לפי סוג-תוכן (נוסחה→מותר, ניתוח→הכלל, מהות מתיק אחר→אסור). INV-LRN5. - _build_precedents_context: פוצל לשני זרמים נפרדים — daphna_style_exemplars (איך דפנה כותבת) מול case_law_citations (מהות לציטוט). - block-yod prompt: שני סעיפים מסומנים במקום "פסיקה רלוונטית (צטט מכאן)" שערבב סגנון ומהות; הדוגמאות-סגנוניות מתויגות "מבנה/קול בלבד". INV: G11 (סגנון דפנה), INV-LRN5 (טוהר-הקול). חלק מתוכנית style-acquisition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -248,8 +248,12 @@ BLOCK_PROMPTS = {
|
||||
## חומרי מקור:
|
||||
{source_context}
|
||||
|
||||
## פסיקה רלוונטית (צטט מכאן ומהידע הכללי שלך):
|
||||
{precedents_context}
|
||||
## דוגמאות-סגנון מהחלטות דפנה — מבנה וקול בלבד:
|
||||
⚠️ אלה דוגמאות ל**איך** דפנה כותבת (מבנה, קצב, תנועות-הנמקה, ביטויים) — **לא מקור-תוכן**. הכלל המבחין: נוסחה/בוילרפלייט קבוע (פתיח דוקטרינלי, תבנית-סיום) → מותר להעתיק; ניתוח/טענות ספציפיים → **הכלל את הדפוס והתאם לתיק שלפניך**, אל תעתיק; מהות משפטית (הלכה/עובדה) מתיק אחר → **אסור** להעתיק.
|
||||
{daphna_style_exemplars}
|
||||
|
||||
## פסיקה רלוונטית לציטוט (צטט מכאן ומהידע הכללי שלך):
|
||||
{case_law_citations}
|
||||
|
||||
## סגנון דפנה:
|
||||
{style_context}""",
|
||||
@@ -327,7 +331,9 @@ async def write_block(
|
||||
claims_context = await _build_claims_context(case_id)
|
||||
direction_context = _build_direction_context(decision)
|
||||
plans_context = await _build_plans_context(case_id)
|
||||
precedents_context, _precedent_case_law_ids = await _build_precedents_context(case_id, block_id)
|
||||
daphna_style_exemplars, case_law_citations, _precedent_case_law_ids = (
|
||||
await _build_precedents_context(case_id, block_id)
|
||||
)
|
||||
style_context = await _build_style_context()
|
||||
discussion_context = await _build_previous_blocks_context(case_id, decision)
|
||||
appraiser_facts_context = await _build_appraiser_facts_context(case_id)
|
||||
@@ -363,7 +369,8 @@ async def write_block(
|
||||
claims_context=claims_context,
|
||||
direction_context=direction_context,
|
||||
plans_context=plans_context,
|
||||
precedents_context=precedents_context,
|
||||
daphna_style_exemplars=daphna_style_exemplars,
|
||||
case_law_citations=case_law_citations,
|
||||
style_context=style_context,
|
||||
discussion_context=discussion_context,
|
||||
structure_guidance=structure_guidance,
|
||||
@@ -707,9 +714,16 @@ async def _build_post_hearing_context(case_id: UUID) -> str:
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
async def _build_precedents_context(case_id: UUID, block_id: str) -> tuple[str, list[str]]:
|
||||
"""Search for similar precedent paragraphs from other decisions and case law."""
|
||||
parts = []
|
||||
async def _build_precedents_context(
|
||||
case_id: UUID, block_id: str,
|
||||
) -> tuple[str, str, list[str]]:
|
||||
"""Two SEPARATE streams (INV-LRN5 — keep style apart from substance):
|
||||
1. style_exemplars — Dafna's own block-level paragraphs (HOW she writes; structure/voice).
|
||||
2. case_law_citations — precedent case-law (substantive material to quote).
|
||||
Returns (style_exemplars, case_law_citations, case_law_ids).
|
||||
"""
|
||||
style_parts: list[str] = []
|
||||
caselaw_parts: list[str] = []
|
||||
case_law_ids: list[str] = []
|
||||
try:
|
||||
case = await db.get_case(case_id)
|
||||
@@ -718,19 +732,18 @@ async def _build_precedents_context(case_id: UUID, block_id: str) -> tuple[str,
|
||||
query = f"דיון משפטי בנושא {subject}" if subject else "דיון משפטי ועדת ערר"
|
||||
query_emb = await embeddings.embed_query(query)
|
||||
|
||||
# Search 1: paragraph_embeddings (from other decisions by Dafna)
|
||||
# Stream 1: paragraph_embeddings — Dafna's own prose (STYLE exemplars, not content)
|
||||
para_results = await db.search_similar_paragraphs(
|
||||
query_embedding=query_emb, limit=10, block_type="block-yod",
|
||||
)
|
||||
# Filter out same case
|
||||
para_results = [r for r in para_results if r.get("case_number", "") != case_number]
|
||||
for r in para_results[:4]:
|
||||
parts.append(
|
||||
f"[החלטת {r.get('case_number', '?')} — {r.get('case_title', '')}, "
|
||||
f"בלוק {r.get('block_type', '')}]\n{r['content'][:500]}"
|
||||
style_parts.append(
|
||||
f"[דוגמת-סגנון — החלטת {r.get('case_number', '?')} "
|
||||
f"{r.get('case_title', '')}, בלוק {r.get('block_type', '')}]\n{r['content'][:500]}"
|
||||
)
|
||||
|
||||
# Search 2: case_law_embeddings (precedent case law)
|
||||
# Stream 2: case_law_embeddings — substantive precedent (citations)
|
||||
pool = await db.get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
caselaw_rows = await conn.fetch(
|
||||
@@ -746,7 +759,7 @@ async def _build_precedents_context(case_id: UUID, block_id: str) -> tuple[str,
|
||||
case_law_ids.append(str(r["id"]))
|
||||
text = r["key_quote"] or r["summary"] or ""
|
||||
if text:
|
||||
parts.append(
|
||||
caselaw_parts.append(
|
||||
f"[פסיקה: {r['case_number']} {r['case_name']} ({r.get('court', '')})] "
|
||||
f"score={r['score']:.3f}\n{text[:400]}"
|
||||
)
|
||||
@@ -754,16 +767,60 @@ async def _build_precedents_context(case_id: UUID, block_id: str) -> tuple[str,
|
||||
except Exception as e:
|
||||
logger.warning("Failed to fetch precedents: %s", e)
|
||||
|
||||
return ("\n\n".join(parts) if parts else "(אין תקדימים)"), case_law_ids
|
||||
return (
|
||||
"\n\n".join(style_parts) if style_parts else "(אין דוגמאות-סגנון)",
|
||||
"\n\n".join(caselaw_parts) if caselaw_parts else "(אין פסיקה רלוונטית)",
|
||||
case_law_ids,
|
||||
)
|
||||
|
||||
|
||||
# Cache for the abstract voice profile (read once per process).
|
||||
_VOICE_FINGERPRINT_CACHE: str | None = None
|
||||
|
||||
# Style-acquisition policy (INV-LRN5): how to USE the style material below.
|
||||
_COPY_POLICY = """## מדיניות-סגנון (איך להשתמש בחומר שלהלן) — חובה:
|
||||
**היעד: לכתוב בקול ובשיטה של דפנה — לא להעתיק.** הפרופיל שלהלן הוא ההכללה של *איך* דפנה כותבת; הַחֵל אותו על העובדות של התיק שלפניך.
|
||||
- **תוכן קבוע/נוסחאי** (פתיח דוקטרינלי, תבנית-סיום, ביטויי-מעבר) → מותר להשתמש כלשונו.
|
||||
- **ניתוח/טענות ספציפיים** → הכלל את הדפוס והתאם לתיק; אל תעתיק ניסוח מתיק אחר.
|
||||
- **מהות משפטית (הלכה/עובדה/תקדים) מתיק אחר** → אסור לגרור לכאן; המהות באה מחומרי-המקור והפסיקה של *התיק הזה* בלבד.
|
||||
"""
|
||||
|
||||
|
||||
def _load_voice_fingerprint() -> str:
|
||||
"""Load the abstract authorial-style profile (daphna-voice-fingerprint.md).
|
||||
|
||||
This is the PRIMARY style channel (Authorial Style Profiling): the generalized
|
||||
'how Dafna writes', injected so the writer adapts it rather than copying exemplars.
|
||||
Read-only consumption of a learning artifact (Writing↔Acquisition separation).
|
||||
"""
|
||||
global _VOICE_FINGERPRINT_CACHE
|
||||
if _VOICE_FINGERPRINT_CACHE is not None:
|
||||
return _VOICE_FINGERPRINT_CACHE
|
||||
try:
|
||||
path = config.DATA_DIR.parent / "docs" / "daphna-voice-fingerprint.md"
|
||||
_VOICE_FINGERPRINT_CACHE = path.read_text(encoding="utf-8")
|
||||
except Exception as e:
|
||||
logger.warning("voice-fingerprint not loaded: %s", e)
|
||||
_VOICE_FINGERPRINT_CACHE = ""
|
||||
return _VOICE_FINGERPRINT_CACHE
|
||||
|
||||
|
||||
async def _build_style_context() -> str:
|
||||
"""Build comprehensive style guide from DB patterns + SKILL.md rules.
|
||||
"""Build comprehensive style guide: abstract voice profile (primary) +
|
||||
SKILL.md rules + DB patterns.
|
||||
|
||||
Per Anthropic: explicit style instructions reduce generic output.
|
||||
Per Anthropic: explicit style instructions reduce generic output. The voice
|
||||
fingerprint is the primary abstract-profile channel (T0 / INV-LRN4-5).
|
||||
"""
|
||||
lines = []
|
||||
|
||||
# Copy-policy first, then the abstract voice profile (the PRIMARY channel).
|
||||
lines.append(_COPY_POLICY)
|
||||
fingerprint = _load_voice_fingerprint()
|
||||
if fingerprint:
|
||||
lines.append("## פרופיל-הקול של דפנה (טביעת-אצבע — המנגנון המרכזי):\n")
|
||||
lines.append(fingerprint)
|
||||
|
||||
# Core style rules (from SKILL.md analysis)
|
||||
lines.append("""## כללי סגנון דפנה תמיר — חובה:
|
||||
|
||||
@@ -882,7 +939,9 @@ async def get_block_context(case_id: UUID, block_id: str, instructions: str = ""
|
||||
claims_context = await _build_claims_context(case_id)
|
||||
direction_context = _build_direction_context(decision)
|
||||
plans_context = await _build_plans_context(case_id)
|
||||
precedents_context, _ = await _build_precedents_context(case_id, block_id)
|
||||
daphna_style_exemplars, case_law_citations, _ = (
|
||||
await _build_precedents_context(case_id, block_id)
|
||||
)
|
||||
style_context = await _build_style_context()
|
||||
discussion_context = await _build_previous_blocks_context(case_id, decision)
|
||||
appraiser_facts_context = await _build_appraiser_facts_context(case_id)
|
||||
@@ -914,7 +973,8 @@ async def get_block_context(case_id: UUID, block_id: str, instructions: str = ""
|
||||
claims_context=claims_context,
|
||||
direction_context=direction_context,
|
||||
plans_context=plans_context,
|
||||
precedents_context=precedents_context,
|
||||
daphna_style_exemplars=daphna_style_exemplars,
|
||||
case_law_citations=case_law_citations,
|
||||
style_context=style_context,
|
||||
discussion_context=discussion_context,
|
||||
structure_guidance=structure_guidance,
|
||||
@@ -942,7 +1002,8 @@ async def get_block_context(case_id: UUID, block_id: str, instructions: str = ""
|
||||
"source_documents": source_context,
|
||||
"claims": claims_context,
|
||||
"direction": direction_context,
|
||||
"precedents": precedents_context,
|
||||
"precedents": case_law_citations,
|
||||
"style_exemplars": daphna_style_exemplars,
|
||||
"style_guide": style_context,
|
||||
"previous_blocks": discussion_context,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user