From 9116f27a3a9d079d606a59f353d25eda553799fd Mon Sep 17 00:00:00 2001 From: Andrei Marinescu Date: Tue, 7 Jul 2026 15:59:06 +0300 Subject: [PATCH] fix: judge saw empty excerpts when no reranker is configured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rerankText is only populated when a reranker runs. The /answer judge used it as the candidate excerpt unconditionally, so with rerank disabled every candidate was scored against an empty string and dropped — /answer always returned 'none were judged relevant'. Fall back to title + retrieval excerpt when rerankText is empty. --- cmd/cosift/judge_excerpt_test.go | 24 ++++++++++++++++++++++++ cmd/cosift/serve_answer.go | 10 +++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 cmd/cosift/judge_excerpt_test.go diff --git a/cmd/cosift/judge_excerpt_test.go b/cmd/cosift/judge_excerpt_test.go new file mode 100644 index 0000000..7b16a50 --- /dev/null +++ b/cmd/cosift/judge_excerpt_test.go @@ -0,0 +1,24 @@ +package main + +import "testing" + +// Regression: with no reranker, the judge scored empty excerpts and dropped every candidate. +func TestJudgeExcerpt(t *testing.T) { + cases := []struct { + name string + rerankText string + title string + excerpt string + want string + }{ + {"reranker ran", "reranked body", "Title", "retrieval excerpt", "reranked body"}, + {"no reranker falls back", "", "Title", "retrieval excerpt", "Title\nretrieval excerpt"}, + {"no reranker, empty title", "", "", "retrieval excerpt", "\nretrieval excerpt"}, + } + for _, c := range cases { + if got := judgeExcerpt(c.rerankText, c.title, c.excerpt); got != c.want { + t.Errorf("%s: judgeExcerpt(%q, %q, %q) = %q, want %q", + c.name, c.rerankText, c.title, c.excerpt, got, c.want) + } + } +} diff --git a/cmd/cosift/serve_answer.go b/cmd/cosift/serve_answer.go index ef312ed..8ead2bb 100644 --- a/cmd/cosift/serve_answer.go +++ b/cmd/cosift/serve_answer.go @@ -200,6 +200,14 @@ func answerLooksLikeNoInfo(s string) bool { return false } +// rerankText is only populated when a reranker ran; an empty excerpt makes the judge drop the candidate. +func judgeExcerpt(rerankText, title, excerpt string) string { + if rerankText != "" { + return rerankText + } + return title + "\n" + excerpt +} + func (s *pebbleHTTP) handleAnswer(w http.ResponseWriter, r *http.Request) { // When clustered + gateway mode + // not already serving as a leaf for someone else, fan-out retrieval @@ -520,7 +528,7 @@ func (s *pebbleHTTP) handleAnswerInner(w http.ResponseWriter, r *http.Request, s before := len(cands) jCands := make([]judge.Candidate, len(cands)) for i, c := range cands { - jCands[i] = judge.Candidate{ID: strconv.Itoa(i), Excerpt: c.rerankText} + jCands[i] = judge.Candidate{ID: strconv.Itoa(i), Excerpt: judgeExcerpt(c.rerankText, c.src.Title, c.excerpt)} } verdicts := judge.Judge(r.Context(), s.chat, q, jCands, judge.Options{MinScore: 0.3}) keep := make([]cand, 0, len(cands))