feat(principles): canonical_statement synthesis service + throttled backfill (Phase E groundwork, #152)
Grounded (INV-AH) multi-instance synthesis with drift guard + chair gate (pending_review, G10). Single path used by backfill, MCP tool, nightly drain. HELD from production run pending the principles-redesign (rename+cull, #152). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,24 +50,17 @@ from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from legal_mcp.services import claude_session, db
|
||||
from legal_mcp.services import db, panel_judges
|
||||
# Judges are the shared primitive (G2) — #152 lifted them to services/panel_judges.
|
||||
from legal_mcp.services.panel_judges import (
|
||||
DEEPSEEK_KEY,
|
||||
GEMINI_KEY,
|
||||
judge_claude,
|
||||
judge_deepseek,
|
||||
judge_gemini,
|
||||
)
|
||||
|
||||
# ── keys (local files, same pattern as the other local judges) ──
|
||||
|
||||
def _env_key(name: str, *files: str) -> str:
|
||||
for f in files:
|
||||
p = Path(f).expanduser()
|
||||
if p.exists():
|
||||
for line in p.read_text().splitlines():
|
||||
if line.startswith(name + "="):
|
||||
return line.split("=", 1)[1].strip()
|
||||
return os.environ.get(name, "")
|
||||
|
||||
|
||||
DEEPSEEK_KEY = _env_key("DEEPSEEK_API_KEY", "~/.hermes/profiles/deepseek/.env", "~/.env")
|
||||
# canonical Infisical name is GOOGLE_GEMINI_API_KEY (/external-apis/gemini); accept
|
||||
# the bare GEMINI_API_KEY too for back-compat.
|
||||
GEMINI_KEY = _env_key("GOOGLE_GEMINI_API_KEY", "~/.env") or _env_key("GEMINI_API_KEY", "~/.env")
|
||||
_bool = panel_judges.to_bool
|
||||
|
||||
# ── the two coarse questions (the reliable axis — NOT the fuzzy sub-type) ──
|
||||
|
||||
@@ -99,62 +92,6 @@ def _nli_user(h: dict) -> str:
|
||||
return f"כלל:\n{h.get('rule_statement') or ''}\n\nציטוט:\n{h.get('supporting_quote') or ''}"
|
||||
|
||||
|
||||
# ── three judges, one signature: (system, user) -> dict|None ──
|
||||
|
||||
async def judge_claude(system: str, user: str) -> dict | None:
|
||||
try:
|
||||
return await claude_session.query_json(user, system=system)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
async def judge_deepseek(client: httpx.AsyncClient, system: str, user: str) -> dict | None:
|
||||
if not DEEPSEEK_KEY:
|
||||
return None
|
||||
try:
|
||||
r = await client.post(
|
||||
"https://api.deepseek.com/v1/chat/completions",
|
||||
headers={"Authorization": f"Bearer {DEEPSEEK_KEY}", "Content-Type": "application/json"},
|
||||
json={"model": "deepseek-chat", "temperature": 0, "max_tokens": 120,
|
||||
"response_format": {"type": "json_object"},
|
||||
"messages": [{"role": "system", "content": system},
|
||||
{"role": "user", "content": user}]},
|
||||
timeout=90,
|
||||
)
|
||||
r.raise_for_status()
|
||||
return json.loads(r.json()["choices"][0]["message"]["content"])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
async def judge_gemini(client: httpx.AsyncClient, system: str, user: str) -> dict | None:
|
||||
if not GEMINI_KEY:
|
||||
return None
|
||||
try:
|
||||
r = await client.post(
|
||||
f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={GEMINI_KEY}",
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={"system_instruction": {"parts": [{"text": system}]},
|
||||
"contents": [{"parts": [{"text": user}]}],
|
||||
"generationConfig": {"temperature": 0, "maxOutputTokens": 4000,
|
||||
"responseMimeType": "application/json"}},
|
||||
timeout=90,
|
||||
)
|
||||
r.raise_for_status()
|
||||
return json.loads(r.json()["candidates"][0]["content"]["parts"][0]["text"])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _bool(d: dict | None, key: str) -> bool | None:
|
||||
if not isinstance(d, dict) or key not in d:
|
||||
return None
|
||||
v = d[key]
|
||||
if isinstance(v, bool):
|
||||
return v
|
||||
return str(v).strip().lower() in ("true", "1", "yes", "כן")
|
||||
|
||||
|
||||
async def panel_vote(client, system, user, key) -> dict:
|
||||
"""Run all three judges; return per-judge bools + the verdict."""
|
||||
c, ds, gm = await asyncio.gather(
|
||||
|
||||
Reference in New Issue
Block a user