Merge pull request 'feat(digests): Sonnet for digest metadata extraction (X12)' (#116) from worktree-digest-sonnet into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s

This commit was merged in pull request #116.
This commit is contained in:
2026-06-07 19:59:30 +00:00
2 changed files with 11 additions and 1 deletions

View File

@@ -54,6 +54,10 @@ REDIS_URL = os.environ.get("REDIS_URL", "redis://127.0.0.1:6380/0")
# pinned. # pinned.
HALACHA_EXTRACT_MODEL = os.environ.get("HALACHA_EXTRACT_MODEL", "claude-opus-4-8") HALACHA_EXTRACT_MODEL = os.environ.get("HALACHA_EXTRACT_MODEL", "claude-opus-4-8")
HALACHA_EXTRACT_EFFORT = os.environ.get("HALACHA_EXTRACT_EFFORT", "xhigh") HALACHA_EXTRACT_EFFORT = os.environ.get("HALACHA_EXTRACT_EFFORT", "xhigh")
# Digest (X12) metadata extraction is a simpler, high-volume task (concept tag,
# headline, underlying citation, tags from a one-page summary) — Sonnet is the
# speed/cost sweet-spot here, unlike halacha extraction which pins Opus. Tune via env.
DIGEST_EXTRACT_MODEL = os.environ.get("DIGEST_EXTRACT_MODEL", "claude-sonnet-4-6")
# Effort for BULK queue-drain extraction (process_pending over many precedents). # Effort for BULK queue-drain extraction (process_pending over many precedents).
# xhigh is the quality sweet-spot for a single precedent but very slow at scale # xhigh is the quality sweet-spot for a single precedent but very slow at scale
# (a 64-chunk case ≈ 20 min). Bulk drains use a lighter effort to cut wall-clock; # (a 64-chunk case ≈ 20 min). Bulk drains use a lighter effort to cut wall-clock;

View File

@@ -19,6 +19,7 @@ from __future__ import annotations
import logging import logging
from datetime import date as date_type from datetime import date as date_type
from legal_mcp import config
from legal_mcp.config import parse_llm_json from legal_mcp.config import parse_llm_json
from legal_mcp.services import claude_session from legal_mcp.services import claude_session
@@ -79,13 +80,17 @@ def _norm_date(result: dict, key: str) -> date_type | None:
return None return None
async def extract(raw_text: str) -> dict: async def extract(raw_text: str, model: str | None = None) -> dict:
"""Extract digest metadata from raw text. Returns a dict (never raises). """Extract digest metadata from raw text. Returns a dict (never raises).
Keys: yomon_number, digest_date (date|None), concept_tag, headline_holding, Keys: yomon_number, digest_date (date|None), concept_tag, headline_holding,
summary, underlying_citation, underlying_court, underlying_date (date|None), summary, underlying_citation, underlying_court, underlying_date (date|None),
underlying_judge, practice_area, appeal_subtype, subject_tags (list[str]). underlying_judge, practice_area, appeal_subtype, subject_tags (list[str]).
Missing/invalid fields are omitted so the caller's merge keeps user values. Missing/invalid fields are omitted so the caller's merge keeps user values.
Model: defaults to ``config.DIGEST_EXTRACT_MODEL`` (Sonnet — this is a
high-volume, simple extraction; no need for Opus). Override per-call via
``model``.
""" """
text = (raw_text or "").strip() text = (raw_text or "").strip()
if not text: if not text:
@@ -95,6 +100,7 @@ async def extract(raw_text: str) -> dict:
try: try:
result = await claude_session.query_json( result = await claude_session.query_json(
user_msg, system=DIGEST_EXTRACTION_PROMPT, user_msg, system=DIGEST_EXTRACTION_PROMPT,
model=(model or config.DIGEST_EXTRACT_MODEL or None),
) )
except Exception as e: # surfaced as warning, not swallowed silently (§6) except Exception as e: # surfaced as warning, not swallowed silently (§6)
logger.warning("digest_metadata_extractor: query failed: %s", e) logger.warning("digest_metadata_extractor: query failed: %s", e)