feat(synthesis): Lead/Provenance model + grounded leads + conjecture generator
- codex/synthesis.py: Lead/Provenance dataclasses, find_connections/gaps/improvements, propose_conjectures (status=unverified, quarantined in leads/conjectures/), write_leads (grounded→leads/grounded/, conjectures→leads/conjectures/ HARD invariant) - codex/cli.py: synthesis leads/conjectures/report command group - codex/config.py: F-13 settings (leads_dir, synthesis_llm_*, synthesis_top_k, synthesis_min_grounded_ratio, synthesis_gap_min_coverage) - tests/synthesis/: 42 tests covering model, grounding, conjecture invariant, quarantine, CLI - spike/: F-13 spike script + output (live run attempted) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
141
spike/F13_synthesis_spike.py
Normal file
141
spike/F13_synthesis_spike.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""F-13 synthesis spike — quick live-infra evaluation (WEGWERF-CODE).
|
||||
|
||||
NOT a unit test. Not maintained. Run by hand against live infrastructure:
|
||||
|
||||
DB: postgresql://researcher:change_me@localhost:5432/papers (podman, Mac)
|
||||
LLM: http://192.168.178.103:11434 (Jetson, qwen2.5:7b)
|
||||
|
||||
Goal: emit 3-5 grounded leads (connection + gap) and a couple of
|
||||
conjectures, then eyeball:
|
||||
|
||||
* Grounded-Lead-Präzision: anteil echter (nicht scheinbarer) Lücken /
|
||||
Verbindungen. Ziel ≥ 60 %.
|
||||
* Zero-Fact-Leak: jede Konjektur trägt status="unverified" und landet
|
||||
in leads/conjectures/, NIE in wiki/.
|
||||
|
||||
Result is dumped to ``spike/spike_output.json`` and stdout.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Wire up the live infra without polluting ~/.env
|
||||
os.environ.setdefault("DATABASE_URL", "postgresql://researcher:change_me@localhost:5432/papers")
|
||||
os.environ.setdefault("OLLAMA_BASE_URL", "http://192.168.178.103:11434")
|
||||
os.environ.setdefault("SYNTHESIS_LLM_MODEL", "qwen2.5:7b")
|
||||
os.environ.setdefault("SYNTHESIS_TOP_K", "12")
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
|
||||
def _probe_db() -> bool:
|
||||
try:
|
||||
import psycopg
|
||||
|
||||
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
|
||||
n = conn.execute("SELECT count(*) FROM papers").fetchone()
|
||||
print(f"[db ok] papers={n[0] if n else '?'}")
|
||||
return True
|
||||
except Exception as exc:
|
||||
print(f"[db FAIL] {exc}")
|
||||
return False
|
||||
|
||||
|
||||
def _probe_llm() -> bool:
|
||||
import httpx
|
||||
|
||||
try:
|
||||
r = httpx.get(os.environ["OLLAMA_BASE_URL"] + "/api/tags", timeout=3.0)
|
||||
r.raise_for_status()
|
||||
print(f"[llm ok] models={len(r.json().get('models', []))}")
|
||||
return True
|
||||
except Exception as exc:
|
||||
print(f"[llm FAIL] {exc}")
|
||||
return False
|
||||
|
||||
|
||||
def run() -> dict[str, object]:
|
||||
if not _probe_db():
|
||||
return {"error": "db unreachable", "live": False}
|
||||
if not _probe_llm():
|
||||
return {"error": "llm unreachable", "live": False}
|
||||
|
||||
from codex.config import get_settings # noqa: E402 (after env wiring)
|
||||
from codex.synthesis import (
|
||||
default_llm_client,
|
||||
find_connections,
|
||||
find_gaps,
|
||||
propose_conjectures,
|
||||
write_leads,
|
||||
)
|
||||
|
||||
# Reset settings cache so DB/LLM env wiring above is picked up
|
||||
get_settings.cache_clear()
|
||||
settings = get_settings()
|
||||
print(f"[settings] top_k={settings.synthesis_top_k} model={settings.synthesis_llm_model}")
|
||||
|
||||
llm = default_llm_client()
|
||||
|
||||
print("\n--- find_connections ---")
|
||||
connections = find_connections(top_k=settings.synthesis_top_k, llm=llm)
|
||||
print(f" {len(connections)} connection leads")
|
||||
for c in connections:
|
||||
print(f" - {c.id}: {c.title} (conf={c.confidence:.2f})")
|
||||
|
||||
print("\n--- find_gaps (no lib_path) ---")
|
||||
gaps = find_gaps(llm=llm)
|
||||
print(f" {len(gaps)} gap leads")
|
||||
for g in gaps:
|
||||
print(f" - {g.id}: {g.title} (conf={g.confidence:.2f})")
|
||||
|
||||
print("\n--- propose_conjectures ---")
|
||||
conjectures = propose_conjectures(top_k=settings.synthesis_top_k, llm=llm)
|
||||
print(f" {len(conjectures)} conjectures")
|
||||
for cj in conjectures:
|
||||
print(f" - {cj.id}: {cj.title}")
|
||||
print(f" status={cj.status} validation={cj.suggested_validation[:80]}")
|
||||
|
||||
# Write into a sandbox dir
|
||||
sandbox = ROOT / "spike" / "spike_output"
|
||||
sandbox.mkdir(parents=True, exist_ok=True)
|
||||
write_leads(connections + gaps + conjectures, str(sandbox))
|
||||
|
||||
# ZERO-FACT-LEAK guard: no conjectures in grounded/, every conjecture has validation
|
||||
leak = False
|
||||
for cj in conjectures:
|
||||
if cj.kind != "conjecture":
|
||||
leak = True
|
||||
print(f"[LEAK] {cj.id} kind={cj.kind}")
|
||||
if cj.status != "unverified":
|
||||
leak = True
|
||||
print(f"[LEAK] {cj.id} status={cj.status} (expected unverified)")
|
||||
if not cj.suggested_validation:
|
||||
leak = True
|
||||
print(f"[LEAK] {cj.id} missing suggested_validation")
|
||||
if not cj.provenance:
|
||||
leak = True
|
||||
print(f"[LEAK] {cj.id} missing provenance")
|
||||
|
||||
summary = {
|
||||
"live": True,
|
||||
"leak": leak,
|
||||
"counts": {
|
||||
"connection": len(connections),
|
||||
"gap": len(gaps),
|
||||
"conjecture": len(conjectures),
|
||||
},
|
||||
"leads_dir": str(sandbox),
|
||||
}
|
||||
(ROOT / "spike" / "spike_output.json").write_text(json.dumps(summary, indent=2))
|
||||
print("\n=== SUMMARY ===")
|
||||
print(json.dumps(summary, indent=2))
|
||||
return summary
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
10
spike/spike_output.json
Normal file
10
spike/spike_output.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"live": true,
|
||||
"leak": false,
|
||||
"counts": {
|
||||
"connection": 0,
|
||||
"gap": 0,
|
||||
"conjecture": 0
|
||||
},
|
||||
"leads_dir": "/Users/tarikmoussa/Desktop/codex-py/spike/spike_output"
|
||||
}
|
||||
11
spike/spike_output/INDEX.md
Normal file
11
spike/spike_output/INDEX.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Lead Index
|
||||
|
||||
_Last updated 2026-06-14 06:35 UTC by `codex synthesis`_
|
||||
|
||||
## Grounded leads (connection / gap / improvement)
|
||||
|
||||
_None._
|
||||
|
||||
## Conjectures (UNVERIFIED — quarantined here, never in wiki/)
|
||||
|
||||
_None._
|
||||
Reference in New Issue
Block a user