Merge pull request 'fix(block-writer): רתימת חלון-1M של Opus 4.8 לבלוק-י (#216)' (#389) from worktree-blockyod-1m into main
This commit was merged in pull request #389.
This commit is contained in:
@@ -85,6 +85,11 @@ knowledge נגזר; G2: מקור-האמת הוא הפרוטוקול + `legal_arg
|
||||
היו"ר כבר מילא תאריך — לא דורסים קלט-יו"ר).
|
||||
- **ייצור:** קריאת-ה-LLM ההשוואתית עוברת `claude_session` (מקומי בלבד), מעוגנת
|
||||
`model="claude-opus-4-8"` + `effort="high"` (ראה `reference_claude_generation_path`).
|
||||
**חלון-הקשר (#216):** הבנייה הסטנדרטית רצה 200K-טוקן (~400K תווים עברית). פרומפט גדול
|
||||
(בעיקר בלוק-י, שנושא את התיק המלא כ-source-context) מוסלם אוטומטית לבניית-ה-1M של *אותו*
|
||||
מודל — `claude-opus-4-8[1m]` (1M-טוקן, מחיר רגיל; `claude -p` חושף אותה ישירות כ-model-id,
|
||||
בלי beta-header). **עידון של אותו pin, לא מסלול-מודל מקביל (G2).** הסף ב-`block_writer`:
|
||||
פרומפט > ~350K תווים → `[1m]`; אחרת הבנייה הסטנדרטית. תקרת-קשיחה: 1.5M תווים.
|
||||
|
||||
### 1.4 סיכום-מנהלים של טענות הצדדים (מסמך-הכנה לדיון, WS3/#202)
|
||||
|
||||
|
||||
@@ -58,6 +58,18 @@ logger = logging.getLogger(__name__)
|
||||
# Output token note (Anthropic): Opus 4.8 supports large outputs; streaming is
|
||||
# handled by the CLI. `max_tokens` is advisory context for callers, not sent.
|
||||
GENERATION_MODEL = "claude-opus-4-8" # single pinned model for every AI block (#204)
|
||||
# 1M-context build of the SAME pinned model (#216). The default build runs a
|
||||
# 200K-token context (~400K Hebrew chars at ~2 chars/token); large prompts —
|
||||
# notably block-yod, which carries the full case as source-context — overflow it.
|
||||
# Opus 4.8 offers a 1M-token window at standard pricing; `claude -p` exposes it
|
||||
# via the `[1m]` model id directly (no beta header — verified CLI 2.1.196). We
|
||||
# escalate to it only when the prompt is large, so small blocks stay on the
|
||||
# cheaper/faster 200K build. NOT a parallel model (G2) — same model, wider window.
|
||||
GENERATION_MODEL_1M = "claude-opus-4-8[1m]"
|
||||
# Escalate to the 1M build above this prompt size. Set below the 200K-token wall
|
||||
# (~400K chars) with headroom for output tokens (max_tokens up to 16K) + tokenizer
|
||||
# variance, so we switch before the standard build can overflow.
|
||||
_CTX_1M_THRESHOLD_CHARS = 350_000
|
||||
|
||||
BLOCK_CONFIG = {
|
||||
"block-alef": {"index": 1, "title": "כותרת מוסדית", "gen_type": "template-fill", "model": "script"},
|
||||
@@ -461,17 +473,21 @@ async def write_block(
|
||||
if not dir_doc.get("approved"):
|
||||
raise ValueError("לא ניתן לכתוב בלוק דיון ללא כיוון מאושר. הפעל brainstorm → approve_direction קודם.")
|
||||
|
||||
# Guard against context overflow before calling claude -p.
|
||||
# Sonnet: 200K context → ~800K chars max; Opus: 200K context → same.
|
||||
# In practice the CLI has crashed on prompts above ~400K chars, so use
|
||||
# that as a conservative ceiling (well below the token limit).
|
||||
_MAX_PROMPT_CHARS = 400_000
|
||||
# Pick the model build by prompt size (#216). Above the 200K-token wall we
|
||||
# escalate to the 1M-context build (`[1m]`) instead of failing the block —
|
||||
# block-yod legitimately carries the whole case as source-context. The 400K
|
||||
# ceiling was an artifact of the old 200K-only build, NOT a model limit.
|
||||
gen_model = GENERATION_MODEL_1M if len(prompt) > _CTX_1M_THRESHOLD_CHARS else GENERATION_MODEL
|
||||
|
||||
# Final guard: even the 1M build is finite (~2M Hebrew chars of input). Cap at
|
||||
# 1.5M chars (~750K tokens) to leave room for output + a safety margin under 1M.
|
||||
_MAX_PROMPT_CHARS = 1_500_000
|
||||
if len(prompt) > _MAX_PROMPT_CHARS:
|
||||
raise RuntimeError(
|
||||
f"Prompt too large for {block_id}: {len(prompt):,} chars "
|
||||
f"(limit {_MAX_PROMPT_CHARS:,}). "
|
||||
f"(limit {_MAX_PROMPT_CHARS:,}, even on the 1M-context build). "
|
||||
f"source_context: {len(source_context):,} chars. "
|
||||
f"Reduce documents or call extract_appraiser_facts first."
|
||||
f"Reduce source-context (summaries / appraiser_facts / focused RAG)."
|
||||
)
|
||||
|
||||
# Call Claude via Claude Code session (no API). #204: pin the model + per-block
|
||||
@@ -483,14 +499,14 @@ async def write_block(
|
||||
content = await claude_session.query(
|
||||
prompt,
|
||||
timeout=timeout,
|
||||
model=GENERATION_MODEL,
|
||||
model=gen_model,
|
||||
effort=effort,
|
||||
tools="", # prose gen — no tool_use → no error_max_turns
|
||||
)
|
||||
|
||||
sources = await _collect_block_sources(case_id, block_id)
|
||||
sources["case_law_ids"] = _precedent_case_law_ids
|
||||
result = _build_result(block_id, content, block_cfg)
|
||||
result = _build_result(block_id, content, block_cfg, model_used=gen_model)
|
||||
# Record the EFFECTIVE effort (override wins) so the harness can attribute
|
||||
# the measured distance to the effort that actually produced the text.
|
||||
if result.get("effort") is not None:
|
||||
@@ -499,7 +515,8 @@ async def write_block(
|
||||
return result
|
||||
|
||||
|
||||
def _build_result(block_id: str, content: str, block_cfg: dict) -> dict:
|
||||
def _build_result(block_id: str, content: str, block_cfg: dict,
|
||||
model_used: str = GENERATION_MODEL) -> dict:
|
||||
word_count = len(content.split())
|
||||
is_ai = block_cfg["model"] == "ai"
|
||||
return {
|
||||
@@ -509,8 +526,9 @@ def _build_result(block_id: str, content: str, block_cfg: dict) -> dict:
|
||||
"content": content,
|
||||
"word_count": word_count,
|
||||
"generation_type": block_cfg["gen_type"],
|
||||
# AI blocks record the pinned model; template blocks record "script".
|
||||
"model_used": GENERATION_MODEL if is_ai else block_cfg["model"],
|
||||
# AI blocks record the model build actually used (200K or [1m], #216);
|
||||
# template blocks record "script".
|
||||
"model_used": model_used if is_ai else block_cfg["model"],
|
||||
# The real generation knob (#204). None for template/script blocks.
|
||||
"effort": block_cfg.get("effort", DEFAULT_EFFORT) if is_ai else None,
|
||||
# DEPRECATED: temperature is not a real knob on Opus 4.7/4.8 (sending it
|
||||
|
||||
@@ -146,8 +146,15 @@ async def query(
|
||||
"""
|
||||
full_prompt = f"{system}\n\n{prompt}" if system else prompt
|
||||
|
||||
if len(full_prompt) > 150_000:
|
||||
logger.warning("Large prompt: %d chars — may hit context limits", len(full_prompt))
|
||||
# ~350K chars ≈ the 200K-token wall of the default Opus build (~2 Hebrew
|
||||
# chars/token). Above it callers should pass the 1M-context model id
|
||||
# (`claude-opus-4-8[1m]`) — block_writer escalates automatically (#216).
|
||||
if len(full_prompt) > 350_000 and "[1m]" not in (model or ""):
|
||||
logger.warning(
|
||||
"Large prompt: %d chars on a non-[1m] model (%s) — may overflow the "
|
||||
"200K-token context. Pass claude-opus-4-8[1m] for the 1M window.",
|
||||
len(full_prompt), model or "CLI-default",
|
||||
)
|
||||
|
||||
cmd = [
|
||||
"claude", "-p",
|
||||
|
||||
Reference in New Issue
Block a user