Rank stored candidates instead of reusing them first
CI / ci (push) Successful in 1m48s
CI / ci (pull_request) Successful in 1m53s

Ground returned the first search result that was already stored before
fetching or ranking anything, so an unrelated stored recipe could win
over a relevant new one. Ground now ranks every candidate and walks the
ranking best first, reusing a stored recipe only at its ranked place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 15:18:29 -04:00
co-authored by Claude Opus 5.5
parent ceedc45309
commit 871a566800
2 changed files with 54 additions and 14 deletions
+46 -3
View File
@@ -328,10 +328,15 @@ func TestGroundReusesStoredRecipe(t *testing.T) {
again.Difficulty != first.Difficulty || *again.MinutesTotal != *first.MinutesTotal || again.SiteName != first.SiteName {
t.Errorf("reused grounding = %+v, want %+v", again, first)
}
if env.pages.fetchCount() != pages || env.images.requests.Load() != images || len(env.llm.Requests()) != calls {
t.Errorf("reuse fetched pages %d→%d, images %d→%d, model calls %d→%d; want none",
pages, env.pages.fetchCount(), images, env.images.requests.Load(), calls, len(env.llm.Requests()))
if env.images.requests.Load() != images || len(env.llm.Requests()) != calls {
t.Errorf("reuse fetched images %d→%d, model calls %d→%d; want none",
images, env.images.requests.Load(), calls, len(env.llm.Requests()))
}
// Ranking fetches both candidates' pages; only the stored one ranks.
if n := env.pages.fetchCount() - pages; n != 2 {
t.Errorf("reuse fetched %d pages, want the 2 candidates ranked", n)
}
pages = env.pages.fetchCount()
byURL, err := env.g.GroundURL(t.Context(), "https://b.example/curry")
if err != nil || byURL.RecipeID != first.RecipeID {
@@ -345,6 +350,44 @@ func TestGroundReusesStoredRecipe(t *testing.T) {
}
}
func TestGroundStoredRecipeDoesNotOutrankRelevantOne(t *testing.T) {
t.Parallel()
search := &fakeSearch{results: map[string][]Candidate{
"brownies": {{URL: "https://b.example/brownies"}},
"chicken curry": {{URL: "https://a.example/curry"}, {URL: "https://b.example/brownies"}},
}}
env := newGroundEnv(t,
map[string][]byte{
"https://a.example/curry": groundPage("Chicken Curry", "/img/curry.jpg"),
"https://b.example/brownies": groundPage("Fudgy Brownies", "/img/brownies.jpg"),
},
map[string]photoFile{
"/img/curry.jpg": jpegFile(t, 640, 480),
"/img/brownies.jpg": jpegFile(t, 640, 480),
},
search)
env.llm.QueueGenerate(normalizedReply(), normalizedReply())
brownies, err := env.g.Ground(t.Context(), "Brownies", "brownies")
if err != nil {
t.Fatalf("Ground brownies: %v", err)
}
// Search lists the relevant new recipe first and the unrelated stored
// one second: the relevant one is grounded.
curry, err := env.g.Ground(t.Context(), "Chicken Curry", "chicken curry")
if err != nil {
t.Fatalf("Ground chicken curry: %v", err)
}
if curry.SourceURL != "https://a.example/curry" || curry.RecipeID == brownies.RecipeID {
t.Errorf("grounded Chicken Curry in %s (recipe %s), want https://a.example/curry, not the stored brownies %s",
curry.SourceURL, curry.RecipeID, brownies.RecipeID)
}
if n := countRows(t, env.st, "recipes"); n != 2 {
t.Errorf("%d recipes rows, want 2", n)
}
}
func TestGroundURL(t *testing.T) {
t.Parallel()
env := newGroundEnv(t,