From f131cee6de4878bd4bbba5fef3f727a317545550 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Tue, 17 Mar 2026 14:02:56 +0900 Subject: [PATCH] Revert "Reduce noisy Gemini batch failure warnings" This reverts commit 6faff4d269c5266f095af28a3caf68089213cf46. --- TODO.md | 21 ------------------ backend/services/gemini.go | 1 - backend/services/gemini_test.go | 15 ------------- backend/services/ranker.go | 39 +++------------------------------ 4 files changed, 3 insertions(+), 73 deletions(-) diff --git a/TODO.md b/TODO.md index 263c6ef..d06cd5f 100644 --- a/TODO.md +++ b/TODO.md @@ -233,7 +233,6 @@ - The result modal should now stay within viewport height, but this still needs real browser confirmation on multiple short-height displays because CSS-only constraints were the source of the latest user-visible regression. - Artgrid preview playback now has a server-side ffmpeg transcode path for `.m3u8` style preview URLs, but this trades storage savings for runtime CPU cost. - The reverted `5ca7aef` experiment showed that simply widening collector caps and Gemini candidate count can backfire when the added candidates are weak: final visible count fell sharply even though backend raw candidate count increased. -- Gemini Vision batch execution can still see mixed failure modes: true model/output-format failures and ignorable low-value/no-visual candidate skips. These should not be conflated in user-facing warnings. - The local self-test script is better than before, but it is still a smoke test, not full integration coverage. ## Current Risks Around Search Quality @@ -555,7 +554,6 @@ ## Highest-Value Next Steps - [ ] Reduce `/api/search` latency further without collapsing result count - [ ] Rebuild the reverted search-expansion work from the previous stable baseline, but only after measuring where candidate quality collapses between ranked pool and final merge -- [ ] Continue hardening Gemini response parsing so truncated JSON or mixed-quality candidate batches degrade more gracefully without surfacing alarming warnings - [ ] Build a repeatable repo-local bootstrap script or documented setup command set for non-root machines so fresh PC setup does not depend on shell history - [ ] Improve Envato / Artgrid preview acquisition reliability so Gemini Vision sees real frames more often - [ ] Browser-verify the new result modal at multiple viewport heights and confirm translated Source Summary readability on real long descriptions @@ -626,25 +624,6 @@ - If behavior in the browser does not match the latest backend/frontend code, the first assumption should be stale frontend assets until proven otherwise ## Recent Change Log -- Date: `2026-03-17` -- What changed: - - Reduced Gemini Vision batch size from `8` to `6` candidates to lower the chance of oversized/truncated JSON responses. - - Added an explicit Gemini output token cap for the vision JSON response path. - - Changed Gemini batch failure accounting so ignorable per-candidate skips such as low-value thumbnails or missing visuals no longer count as a user-facing partial batch failure when useful recommendations were still recovered. - - Added unit coverage for the hard-error filtering behavior. -- Why it changed: - - The user reported the warning `gemini vision partially failed on 2 of 2 batches`. - - The provided log `ai-media-hub-2026-03-17T04-35-33-028Z.log` showed two distinct failure classes during the second repeated search: - - a real batch-level JSON extraction failure caused by truncated Gemini output - - multiple ignorable sequential-retry skips caused by low-value thumbnails or missing visuals - - Those were being combined into an alarming partial-failure warning even though useful recommendations were still recovered. -- How it was verified: - - `go test ./...` - - `bash scripts/selftest.sh` -- What is still risky or incomplete: - - This reduces false-positive partial-failure warnings, but true Gemini output truncation can still happen occasionally if model behavior changes again. - - Further hardening may still be needed if Gemini starts returning different malformed JSON shapes. - - Date: `2026-03-17` - What changed: - Reverted commit `5ca7aef` (`Strengthen search breadth and modal fitting`) to restore the previous stable search/modal baseline. diff --git a/backend/services/gemini.go b/backend/services/gemini.go index 1d79df9..c61894b 100644 --- a/backend/services/gemini.go +++ b/backend/services/gemini.go @@ -306,7 +306,6 @@ User query: ` + query, }, "generationConfig": map[string]any{ "responseMimeType": "application/json", - "maxOutputTokens": 1400, }, } diff --git a/backend/services/gemini_test.go b/backend/services/gemini_test.go index 2f26575..0004f73 100644 --- a/backend/services/gemini_test.go +++ b/backend/services/gemini_test.go @@ -213,18 +213,3 @@ func TestMergeRecommendationsExcludesIrrelevantAndPendingFiller(t *testing.T) { t.Fatalf("unexpected merged result: %#v", merged) } } - -func TestFilterHardGeminiErrorsIgnoresLowValueVisualFailures(t *testing.T) { - errs := []string{ - "candidate thumbnail is low value", - "no candidate thumbnails or preview frames could be fetched for gemini vision", - "gemini vision JSON extraction failed: no complete JSON object found", - } - filtered := filterHardGeminiErrors(errs) - if len(filtered) != 1 { - t.Fatalf("expected only hard errors to remain, got %#v", filtered) - } - if !strings.Contains(filtered[0], "JSON extraction failed") { - t.Fatalf("unexpected filtered errors: %#v", filtered) - } -} diff --git a/backend/services/ranker.go b/backend/services/ranker.go index 12b2470..919d0bd 100644 --- a/backend/services/ranker.go +++ b/backend/services/ranker.go @@ -105,7 +105,7 @@ func EvaluateAllCandidatesWithGemini(service *GeminiService, query string, ranke } func EvaluateAllCandidatesWithGeminiWithDeadline(service *GeminiService, query string, ranked []SearchResult, deadline time.Time) ([]AIRecommendation, GeminiBatchStats, error) { - const chunkSize = 6 + const chunkSize = 8 const maxConcurrentBatches = 2 if service == nil { return nil, GeminiBatchStats{}, fmt.Errorf("gemini service is not configured") @@ -187,7 +187,6 @@ func EvaluateAllCandidatesWithGeminiWithDeadline(service *GeminiService, query s }) } recovered, recoveredErrs := recoverGeminiBatchSequentially(service, query, ranked, batch.index*chunkSize) - hardErrs := filterHardGeminiErrors(recoveredErrs) if len(recovered) > 0 { stats.SequentialRetried++ stats.Succeeded++ @@ -198,9 +197,9 @@ func EvaluateAllCandidatesWithGeminiWithDeadline(service *GeminiService, query s seen[item.Link] = true merged = append(merged, item) } - if len(hardErrs) > 0 { + if len(recoveredErrs) > 0 { stats.Failed++ - for _, recoveredErr := range hardErrs { + for _, recoveredErr := range recoveredErrs { if len(stats.Errors) < 5 { stats.Errors = append(stats.Errors, recoveredErr) } @@ -208,9 +207,6 @@ func EvaluateAllCandidatesWithGeminiWithDeadline(service *GeminiService, query s } continue } - if len(hardErrs) == 0 { - continue - } stats.Failed++ if len(stats.Errors) < 5 { stats.Errors = append(stats.Errors, batch.err.Error()) @@ -333,35 +329,6 @@ func MergeUniqueRecommendations(base, extra []AIRecommendation) []AIRecommendati return merged } -func filterHardGeminiErrors(errs []string) []string { - filtered := make([]string, 0, len(errs)) - for _, item := range errs { - if isIgnorableGeminiError(item) { - continue - } - filtered = append(filtered, item) - } - return filtered -} - -func isIgnorableGeminiError(message string) bool { - lower := strings.ToLower(strings.TrimSpace(message)) - if lower == "" { - return false - } - for _, token := range []string{ - "no candidate thumbnails or preview frames could be fetched for gemini vision", - "candidate thumbnail is low value", - "candidate has no thumbnail or preview video", - "image url is empty", - } { - if strings.Contains(lower, token) { - return true - } - } - return false -} - func MergeGeminiBatchStats(base, extra GeminiBatchStats) GeminiBatchStats { merged := base merged.CandidateCap += extra.CandidateCap