Skip to content

week_5: Module C (The Librarian) — C.3 confidence calibration (temperature scaling) - #974

Open
PRAteek-singHWY wants to merge 10 commits into
OWASP:mainfrom
PRAteek-singHWY:gsocmodule_C_week_5
Open

week_5: Module C (The Librarian) — C.3 confidence calibration (temperature scaling)#974
PRAteek-singHWY wants to merge 10 commits into
OWASP:mainfrom
PRAteek-singHWY:gsocmodule_C_week_5

Conversation

@PRAteek-singHWY

@PRAteek-singHWY PRAteek-singHWY commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Hi @northdpole — Week 5 of Module C. This one turns the cross-encoder's raw scores into an honest probability (temperature scaling), so Week 6 can threshold "auto-link vs. send-to-human" on a number that actually means what it says.

Based on latest main — rebased/merged onto main after Week 1–4 (#922#925#937#957) and after the pgvector migration (#979), so the diff is a clean Week-5-only surface.

Overview

Week 3 built the search step (C.1) — a bi-encoder that cosine-ranks the whole CRE hub down to a top-20 shortlist. Week 4 built the rerank step (C.2) — a cross-encoder that reads each (section, candidate-CRE) pair together and re-sorts the shortlist by a raw relevance logit. Those logits are great for ordering but are not confidence: a raw +1.5 does not mean "82% sure this is the right CRE," and cross-encoders are systematically over-confident.

The problem: Week 6's decision — auto-link vs. send to a human — is a threshold on confidence ("link if ≥ 90%"). That is only safe if 90% really means 90%. A raw score cannot be thresholded honestly.

This PR's role: build the calibration step (C.3) — temperature scaling. Learn a single scalar temperature T on the golden set and map the reranked shortlist to a real probability p = softmax(logits / T); the top-1 candidate's share of that probability mass is the confidence — "how likely is the top candidate the correct CRE?" Prove it honest with Expected Calibration Error < 0.10. Temperature only flattens/sharpens the distribution — it never changes the ranking — so calibration can't hurt C.1/C.2 recall; it only makes the confidence trustworthy.

One design note worth surfacing (the golden set caught it): calibrating the single absolute top-1 logit with sigmoid(z / T) does not work — a cross-encoder's absolute logit has no fixed zero point (its 50/50 boundary isn't at z=0), and one temperature can only squash toward 0.5, never shift the boundary, so ECE stays stuck at ~0.27. Calibrating the softmax over the whole shortlist is the fix (and the textbook Guo et al. temperature scaling): the candidates' relative logits are what a cross-encoder's scores actually mean, and their softmax is a genuine distribution over "which candidate is right." It is near-calibrated even before fitting (ECE 0.053 at T=1), and T polishes it to 0.046. (CALIBRATOR_NAME = "temperature-scaling/0.2.0" records this over the rejected single-logit 0.1.0.)

Scope: 1 new package (1 module + 1 test) + harness wiring for the ECE gate. UpdateDetection (the other W5 schema deliverable) already exists in schemas.py — verified, nothing to add. No frontend, no behaviour change to OpenCRE proper.

What changed

Area Files Description
C.3 calibration calibration/temperature.py (new), calibration/__init__.py (new) TemperatureScaler(T) maps a reranked shortlist to a calibrated distribution (.probabilities = softmax(logits/T)) and a top-1 .confidence (the mass Week 6 thresholds on); fit_temperature learns the single scalar T by minimising NLL (scipy.optimize.minimize_scalar, bounded), guarding single-outcome data (DegenerateLabelsError); expected_calibration_error is the 10-bin ECE gate; negative_log_likelihood is the fit objective (exposed for testing/reporting). Model-free (numpy + scipy.special.softmax) so it stays import-light and hermetically testable — mirrors the C.1 embed_fn / C.2 score_fn seams. CALIBRATOR_NAME audit tag (mirrors RETRIEVER_NAME/RERANKER_NAME).
Eval harness evaluate_librarian.py report_calibration builds a (shortlist, label) set from the live C.1→C.2 pipeline over the positive + hard_negative slices (each row's reranked logits; label 1 iff its top-1 is an expected CRE — hard_negatives supply the 0 outcome), fits T, and reports ECE at T=1 vs. the fitted T with the < 0.10 gate. _build_live_pipeline extracted so recall and calibration share one hub + model load. Offline path unchanged (no live number is faked).
Docs __init__.py Scope note now covers C.3.
Tests temperature_test.py (new) 21 hermetic tests: softmax top-1 confidence, temperature flatten/sharpen, single-candidate edge, NLL hand-check, fit recovers a known T and reduces NLL vs. T=1, ECE on perfectly-/mis-calibrated data + a hand-checked two-bin value, peaked-vs-flat shortlist confidence, and every guard (single-outcome, non-binary, length mismatch, empty, n_bins < 1, non-positive/non-finite T).

How the pieces connect

flowchart TB
    audit["RetrievalAudit from C.2 (W4)<br/>reranked[] shortlist with score_rerank logits"]
    subgraph FIT["fit once on the golden set"]
        g["(shortlist, is-top1-correct) pairs<br/>positive + hard_negative slices"]
        nll["fit_temperature: argmin NLL(T)"]
        Tstar["T-star (one scalar)"]
        ece["expected_calibration_error<br/>gate: ECE below 0.10"]
        g --> nll --> Tstar --> ece
    end
    subgraph APPLY["apply per shortlist"]
        s["reranked logits"]
        p["confidence = top-1 mass of<br/>softmax(logits / T-star)"]
        s --> p
    end
    audit --> g
    audit --> s
    Tstar -.-> p
    p --> dec["W6 decision engine<br/>confidence at or above tau: LinkProposal, else ReviewItem"]
Loading

Results

# offline (CI default) — hermetic, no key/DB/model
113 librarian tests passing (92 from W1–W4 + 21 new; 1 skipped)
explicit slice (C.0.5 resolver): 5/5 — gate 100%: PASS
calibration (C.3): wired; ECE gate needs --use_live_embeddings

# live (local, against a populated standards_cache.sqlite migrated to embedding_vec:
# 428 CRE embeddings, gemini/gemini-embedding-001 dim 3072, ms-marco-MiniLM-L-6-v2, hub-firewall ON)
retrieval recall@20 (C.1): any-hit 285/292 (98%), all-hit 274/292 (94%)
rerank top-1     (C.2): 220/292 (75%)
calibration (C.3, 304 rows): fitted T=1.105; ECE 0.053 (raw, T=1) -> 0.046 (calibrated); gate ECE<0.10: PASS

Reading it: the reranked top-1 is correct 75% of the time, and after calibration the model's stated confidence tracks that — mean confidence 0.767 vs. actual accuracy 0.757, ECE 0.046 < 0.10. So a "90%" from C.3 can be trusted as ~90%, which is exactly what the W6 auto-link threshold needs. (Calibration doesn't touch the 75% top-1 itself — that's the reranker's job, with W6/W7 the levers to lift it toward 0.80.)

What is intentionally not here

  • Decision / threshold routing (C.4, W6) — turning the calibrated confidence into a LinkProposal (≥ τ) vs. a ReviewItem, and writing it into ProposedLink.confidence. This PR produces the honest number; W6 thresholds it.
  • Persisting the fitted T for the live decision path (loaded by the W6 engine).
  • Graph writes / worker wiring (W8) — the pipeline stays dry-run; nothing here costs the embedding API outside a manual --use_live_embeddings run.

How to verify locally

# all librarian tests (calibration is hermetic — no key, DB, or model)
python3 -m unittest discover -s application/tests/librarian -p '*_test.py' -t .
# or just the new ones:
python3 -m unittest application.tests.librarian.temperature_test

# offline harness: C.0 pass rate + explicit gate + calibration wired
python3 scripts/evaluate_librarian.py --dataset application/tests/librarian/fixtures/golden_dataset.json

# live ECE gate (needs a populated cache DB + an embedding-capable LLM)
# NOTE: after the pgvector migration (#979) a legacy SQLite cache must first be
# rewritten to the embedding_vec store, or the ORM will refuse it:
python3 scripts/rewrite_sqlite_embeddings_to_vec.py --db standards_cache.sqlite
python3 scripts/evaluate_librarian.py \
    --dataset application/tests/librarian/fixtures/golden_dataset.json \
    --use_live_embeddings --cache_file standards_cache.sqlite

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • New Features

    • Added temperature scaling to produce calibrated confidence scores for ranked search results.
    • Added calibration quality reporting with expected calibration error (ECE) and a 0.10 acceptance threshold.
    • Updated evaluation workflows to report calibration alongside retrieval performance.
    • Added validation to reject duplicate evaluation dataset IDs.
  • Tests

    • Added deterministic coverage for calibration behavior, validation, fitting, and end-to-end confidence checks.
  • Documentation

    • Documented confidence calibration scope and its relationship to future decision routing.

Walkthrough

Adds temperature-scaling calibration utilities, deterministic tests, module documentation, dataset ID validation, and a live evaluation gate based on calibrated ECE.

Changes

Temperature Calibration Feature

Layer / File(s) Summary
Calibration module documentation
application/utils/librarian/__init__.py, application/utils/librarian/calibration/__init__.py
Documents W5 confidence calibration, temperature scaling, NLL fitting, ECE gating, and the remaining unimplemented decision routing.
Calibration utilities
application/utils/librarian/calibration/temperature.py
Adds calibration metadata, error types, TemperatureScaler, NLL calculation, temperature fitting, and ECE calculation.
Calibration utility tests
application/tests/librarian/temperature_test.py
Adds deterministic tests for scaling, NLL, fitting, ECE, validation errors, end-to-end confidence, and metadata.
Shared evaluation audits and calibration gate
scripts/evaluate_librarian.py, application/tests/librarian/dataset_test.py
Rejects duplicate dataset IDs, shares live audits between recall and calibration, reports raw and calibrated ECE, enforces calibrated ECE below 0.10, and returns the calibration status.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • OWASP/OpenCRE#922: Extends the Librarian evaluation harness used by this change.
  • OWASP/OpenCRE#957: Provides the reranking pipeline integrated with calibration.
  • OWASP/OpenCRE#990: Shares the temperature-calibration module, tests, and evaluation harness.

Suggested reviewers: robvanderveer, paoga87, pa04rth

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Week 5 confidence calibration work and its temperature-scaling implementation.
Description check ✅ Passed The description directly explains the calibration implementation, evaluation harness changes, tests, results, and intended Week 6 integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PRAteek-singHWY
PRAteek-singHWY marked this pull request as ready for review July 9, 2026 17:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/evaluate_librarian.py (1)

182-184: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Live pipeline is built (and positives reranked) twice per run.

With --use_live_embeddings, report_retrieval_recall and report_calibration each call _build_live_pipeline(...) independently, so the DB connection, embedding model, and cross-encoder are loaded twice, and every positive row is retrieved+reranked in both reports. Since the cross-encoder load and per-pair inference are the expensive steps here, building the pipeline once in main and passing (retriever, reranker) into both reports would roughly halve the live cost. This also matches the intent stated in _build_live_pipeline's docstring that the heavy hub + model load happens once.

♻️ Sketch: build once in main, pass into reports
     if args.use_live_embeddings:
+        retriever, reranker = _build_live_pipeline(
+            args.cache_file,
+            args.top_k_retrieval,
+            args.threshold,
+            args.top_k_rerank,
+            cfg.crossencoder_model,
+        )
-        report_retrieval_recall(
-            rows,
-            args.cache_file,
-            args.top_k_retrieval,
-            args.threshold,
-            args.top_k_rerank,
-            cfg.crossencoder_model,
-        )
-        calib_status = report_calibration(
-            rows,
-            args.cache_file,
-            args.top_k_retrieval,
-            args.threshold,
-            args.top_k_rerank,
-            cfg.crossencoder_model,
-        )
+        report_retrieval_recall(rows, retriever, reranker, args.top_k_retrieval, args.top_k_rerank)
+        calib_status = report_calibration(rows, retriever, reranker)

(Adjust the two report signatures to accept the prebuilt retriever, reranker.)

Also applies to: 238-251

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/evaluate_librarian.py` around lines 182 - 184, The live pipeline is
being constructed twice because both report_retrieval_recall and
report_calibration call _build_live_pipeline independently. Build the pipeline
once in main by calling _build_live_pipeline(cache_file, top_k, threshold,
top_n_rerank, crossencoder_model) a single time, then pass the resulting
retriever and reranker into both report functions. Update the signatures of
report_retrieval_recall and report_calibration to accept the prebuilt pipeline
objects and remove their internal _build_live_pipeline calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/evaluate_librarian.py`:
- Around line 182-184: The live pipeline is being constructed twice because both
report_retrieval_recall and report_calibration call _build_live_pipeline
independently. Build the pipeline once in main by calling
_build_live_pipeline(cache_file, top_k, threshold, top_n_rerank,
crossencoder_model) a single time, then pass the resulting retriever and
reranker into both report functions. Update the signatures of
report_retrieval_recall and report_calibration to accept the prebuilt pipeline
objects and remove their internal _build_live_pipeline calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: e64443ca-ff6c-46c9-bff4-b4820fd64a97

📥 Commits

Reviewing files that changed from the base of the PR and between 7d0634e and fc68304.

📒 Files selected for processing (5)
  • application/tests/librarian/temperature_test.py
  • application/utils/librarian/__init__.py
  • application/utils/librarian/calibration/__init__.py
  • application/utils/librarian/calibration/temperature.py
  • scripts/evaluate_librarian.py

PRAteek-singHWY and others added 3 commits July 9, 2026 23:06
… once

report_retrieval_recall and report_calibration each built the live
pipeline (DB + embedding model + cross-encoder) independently, loading it
twice and reranking every positive row twice per --use_live_embeddings run.
Build it once in main and pass (retriever, reranker) into both reports,
matching _build_live_pipeline's stated intent. Behavior-preserving:
recall@20 285/292, rerank top-1 220/292, ECE 0.046 PASS unchanged.

@northdpole northdpole left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer review — Module C Week 5 (#974)

Temperature scaling (softmax-over-shortlist + NLL fit + ECE) looks correct and the hermetic tests are strong. One gate integrity issue before merge.

Please fix the inline comment (calibration skip must not exit 0 when --use_live_embeddings is on). After that, this is merge-ready as the base of the Module C stack (#974#990#991).

Comment thread scripts/evaluate_librarian.py Outdated
"calibration (C.3): need both outcomes in the selection (positive + "
"hard_negative slices) to fit temperature; skipped"
)
return 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High — calibration skip reports success

When the live C.3 calibration set is degenerate (all top-1 labels 0 or all 1, empty after dropping empty shortlists, or --slice with only one class), report_calibration prints "skipped" and returns 0. main then exits successfully even though the ECE < 0.10 gate never ran.

With --use_live_embeddings, a skipped gate should fail the run (non-zero exit), e.g. return 1, so CI/harness cannot greenwash a missing calibration. Only return 0 when calibration actually ran and ECE passed.

if len(set(labels)) < 2:
    print(
        "calibration (C.3): need both outcomes in the selection (positive + "
        "hard_negative slices) to fit temperature; FAILED (gate did not run)"
    )
    return 1

Please fix before merge.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 62abf2d2. A degenerate calibration set now returns 1 instead of 0, so --use_live_embeddings cannot exit 0 without the ECE gate having run. The message also prints the row and class counts, so a failure says why the set was degenerate rather than just that it was.

Verified the hermetic path is unaffected: evaluate_librarian.py --dataset application/tests/librarian/fixtures/golden_dataset.json still exits 0, since calibration is skipped upstream of this check when there are no live CRE vectors.

PRAteek-singHWY and others added 3 commits August 3, 2026 10:11
…on gate must fail

report_calibration returned 0 ("skipped") when the live calibration set was
degenerate (single-class labels, empty after dropping empty shortlists, or a
--slice with one class). Under --use_live_embeddings that let a run exit 0 without
the ECE < 0.10 gate ever running, so CI could greenwash a live run in which
calibration was never checked. A skipped gate now returns 1 (fail), with a message
stating the row/class counts, so exit 0 means the gate actually ran and passed.
…hortlists, dedupe softmax

Three nitpicks from the bot review, none of which changed a metric:

- evaluate_librarian: the live retrieve+rerank was recomputed per report. The
  models were already built once in main, but report_retrieval_recall and
  report_calibration each re-ran the pipeline over the positive slice, so every
  positive row paid for two cross-encoder passes. live_audits() now retrieves and
  reranks each row once, keyed by golden row id, and both reports read the same
  shortlists. Rows without an audit no longer count toward a report's
  denominator, so the printed fractions cannot divide by unscored rows.

- temperature: _softmax_top duplicated the empty-shortlist guard and the softmax
  already in TemperatureScaler.probabilities. Both now route through one
  _softmax_at helper, and confidence() derives from probabilities() so the two
  can never disagree.

- temperature is now clean under the --strict mypy the coding guidelines ask for:
  annotated _paired's return and the bounds tuple, re-asserted the array type
  over untyped scipy, and hoisted the label conversion out of the fit objective
  (it was re-validated on every optimiser iteration anyway).

Behaviour is unchanged: 113 librarian tests pass and the hermetic harness run
still exits 0.
PRAteek-singHWY added a commit to PRAteek-singHWY/OpenCRE that referenced this pull request Aug 4, 2026
…hortlists, dedupe softmax

Three nitpicks from the bot review, none of which changed a metric:

- evaluate_librarian: the live retrieve+rerank was recomputed per report. The
  models were already built once in main, but report_retrieval_recall and
  report_calibration each re-ran the pipeline over the positive slice, so every
  positive row paid for two cross-encoder passes. live_audits() now retrieves and
  reranks each row once, keyed by golden row id, and both reports read the same
  shortlists. Rows without an audit no longer count toward a report's
  denominator, so the printed fractions cannot divide by unscored rows.

- temperature: _softmax_top duplicated the empty-shortlist guard and the softmax
  already in TemperatureScaler.probabilities. Both now route through one
  _softmax_at helper, and confidence() derives from probabilities() so the two
  can never disagree.

- temperature is now clean under the --strict mypy the coding guidelines ask for:
  annotated _paired's return and the bounds tuple, re-asserted the array type
  over untyped scipy, and hoisted the label conversion out of the fit objective
  (it was re-validated on every optimiser iteration anyway).

Behaviour is unchanged: 113 librarian tests pass and the hermetic harness run
still exits 0.

(cherry picked from commit 2bbc76e)
PRAteek-singHWY added a commit to PRAteek-singHWY/OpenCRE that referenced this pull request Aug 4, 2026
…on gate must fail

report_calibration returned 0 ("skipped") when the live calibration set was
degenerate (single-class labels, empty after dropping empty shortlists, or a
--slice with one class). Under --use_live_embeddings that let a run exit 0 without
the ECE < 0.10 gate ever running, so CI could greenwash a live run in which
calibration was never checked. A skipped gate now returns 1 (fail), with a message
stating the row/class counts, so exit 0 means the gate actually ran and passed.

(cherry picked from commit 62abf2d)
PRAteek-singHWY added a commit to PRAteek-singHWY/OpenCRE that referenced this pull request Aug 4, 2026
…hortlists, dedupe softmax

Three nitpicks from the bot review, none of which changed a metric:

- evaluate_librarian: the live retrieve+rerank was recomputed per report. The
  models were already built once in main, but report_retrieval_recall and
  report_calibration each re-ran the pipeline over the positive slice, so every
  positive row paid for two cross-encoder passes. live_audits() now retrieves and
  reranks each row once, keyed by golden row id, and both reports read the same
  shortlists. Rows without an audit no longer count toward a report's
  denominator, so the printed fractions cannot divide by unscored rows.

- temperature: _softmax_top duplicated the empty-shortlist guard and the softmax
  already in TemperatureScaler.probabilities. Both now route through one
  _softmax_at helper, and confidence() derives from probabilities() so the two
  can never disagree.

- temperature is now clean under the --strict mypy the coding guidelines ask for:
  annotated _paired's return and the bounds tuple, re-asserted the array type
  over untyped scipy, and hoisted the label conversion out of the fit objective
  (it was re-validated on every optimiser iteration anyway).

Behaviour is unchanged: 113 librarian tests pass and the hermetic harness run
still exits 0.

(cherry picked from commit 2bbc76e)
(cherry picked from commit ae6bb69)
PRAteek-singHWY added a commit to PRAteek-singHWY/OpenCRE that referenced this pull request Aug 4, 2026
…on gate must fail

report_calibration returned 0 ("skipped") when the live calibration set was
degenerate (single-class labels, empty after dropping empty shortlists, or a
--slice with one class). Under --use_live_embeddings that let a run exit 0 without
the ECE < 0.10 gate ever running, so CI could greenwash a live run in which
calibration was never checked. A skipped gate now returns 1 (fail), with a message
stating the row/class counts, so exit 0 means the gate actually ran and passed.

(cherry picked from commit 62abf2d)
(cherry picked from commit cb5577a)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/evaluate_librarian.py`:
- Around line 176-179: Update load_dataset to detect and reject duplicate
GoldenDatasetRow.id values before live_audits, recall, or calibration compute
audits; preserve the existing non-empty ID validation and raise a clear
validation error identifying the duplicate ID.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: af532b61-e0e4-4706-b0f2-db03cd46c26f

📥 Commits

Reviewing files that changed from the base of the PR and between 62abf2d and 2bbc76e.

📒 Files selected for processing (2)
  • application/utils/librarian/calibration/temperature.py
  • scripts/evaluate_librarian.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • application/utils/librarian/calibration/temperature.py

Comment thread scripts/evaluate_librarian.py
@PRAteek-singHWY

Copy link
Copy Markdown
Contributor Author

Pushed 2bbc76e5 on top of the gate fix. Bot-review follow-ups on this PR:

  1. "Live pipeline is built twice per run" was already stale: main builds the retriever and reranker once and passes them into both reports. What was still real is that the shortlists were recomputed, so report_retrieval_recall and report_calibration each re-ran retrieve and rerank over the positive slice. live_audits() now retrieves and reranks each row once, keyed by golden row id, and both reports read the same audits. Rows without an audit no longer count toward a report's denominator, so the printed fractions cannot divide by unscored rows.

  2. temperature.py: _softmax_top duplicated the empty-shortlist guard and the softmax already in TemperatureScaler.probabilities. Both now route through one _softmax_at helper, and confidence() derives from probabilities(), so the two cannot disagree.

  3. temperature.py is now clean under the --strict mypy the coding guidelines ask for: annotated _paired's return type and the bounds tuple, re-asserted the array type over untyped scipy, and hoisted the label conversion out of the fit objective (it was re-validated on every optimiser iteration).

113 librarian tests pass and the hermetic harness run still exits 0. No metric moved.

PRAteek-singHWY and others added 2 commits August 4, 2026 23:15
Follow-up to the shared-audit refactor. live_audits keys the reranked shortlists
by row.id and the reports read them back with audits[row.id], so two rows sharing
an id would collapse in that dict: the surviving audit gets reused for the earlier
row and its scores are reported against the wrong text. Before the refactor each
report recomputed per row, so a duplicate id was harmless; keying by it turned a
harmless quirk into a silently wrong number.

GoldenDatasetRow only requires an id to be non-empty, so uniqueness is enforced at
load time and the harness refuses the file instead of printing a wrong metric. The
committed dataset has 319 distinct ids, so nothing changes today; this closes the
trap the refactor opened.

Adds a test that a forced collision raises and names the offending id.
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.

2 participants