Skip to content

feat: grounded generation and eval harness with 30-doc golden corpus - #4

Merged
adel-saoud merged 1 commit into
mainfrom
feat/generation-eval
Jun 17, 2026
Merged

feat: grounded generation and eval harness with 30-doc golden corpus#4
adel-saoud merged 1 commit into
mainfrom
feat/generation-eval

Conversation

@adel-saoud

Copy link
Copy Markdown
Owner

Summary

  • Generation: async OllamaClient (tenacity retry on 5xx, raises on 4xx), grounded prompt that teaches the LLM to treat [PERSON_n] placeholders as opaque tokens, [N] citation verifier, Generator facade that short-circuits to a refusal when context is empty
  • Eval harness: golden corpus loader, recall + precision runner with separate recall_hits / precision_hits counters, LLM-as-Judge faithfulness scorer
  • 30-document synthetic golden corpus (10 HR / 10 contracts / 10 support) generated via scripts/seed_demo_corpus.py, 91 planted PII entities total
  • scripts/eval.py CLI prints a rich table, writes an HTML report, and supports --with-gliner / --recall-gate / --precision-gate

Measured baseline (scripts/eval.py --with-gliner)

  • PII Recall: 100.0%
  • PII Precision: 0.93
  • Both gates pass (>=85% recall, >=0.85 precision)

Test plan

  • uv run ruff check . clean
  • uv run pyright 0 errors / 0 warnings
  • uv run pytest tests/test_generation_*.py tests/test_eval_*.py all green
  • CI green on the PR

Generation:
- `ollama.py` is an async adapter for the local Ollama `POST /api/chat`
  endpoint. Tenacity retry on 5xx and transport errors; raises on 4xx.
  Token counts and latency are surfaced through frozen pydantic models.
- `prompt.py` builds the grounded system + user pair. The system prompt
  explicitly teaches the model to treat `[PERSON_n]` placeholders as
  opaque tokens. Empty parent contexts short-circuit to a refusal.
- `citation.py` extracts `[N]` citations from the answer, deduplicates,
  and flags any citation outside the retrieved parent set as ungrounded.
- `generator.py` ties it all together. The empty-context refusal is
  enforced in code so Ollama is never called with `(no context available)`.

Eval harness:
- `models.py` carries the golden corpus shape: `GoldenDocument`,
  `GoldenEntity`, `GoldenQA`, and `EvalCounts` with separate `recall_hits`
  / `precision_hits` because a planted entity can be matched by several
  detector outputs. Counting both directions keeps the metrics honest.
- `loader.py` reads the corpus from disk and validates every JSON.
- `runner.py` runs the configured detector over the corpus and rolls up
  per-category and overall counts.
- `judge.py` calls Ollama to do binary LLM-as-Judge faithfulness
  scoring. Informational only; no gate.

Scripts:
- `scripts/seed_demo_corpus.py` regenerates the 30 synthetic documents
  (10 HR / 10 contracts / 10 support) with 91 planted PII spans. All
  values are synthetic (IANA test cards, ISO IBAN examples, example.com
  emails).
- `scripts/eval.py` CLI prints a rich table, writes an HTML report,
  takes `--with-gliner` for the two-stage path, and gates the recall and
  precision floors so a regression fails the run.

Measured baseline against the 30-doc corpus with GLiNER on:
  PII Recall    100.0%
  PII Precision 0.93
  (gates >= 85% recall, >= 0.85 precision both pass)
@adel-saoud adel-saoud self-assigned this Jun 17, 2026
@adel-saoud
adel-saoud merged commit 4811b54 into main Jun 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant