This commit is contained in:
@@ -255,6 +255,17 @@
|
|||||||
- backend debug broadcasts
|
- backend debug broadcasts
|
||||||
|
|
||||||
## Recent Change Log
|
## Recent Change Log
|
||||||
|
- Date: `2026-03-16`
|
||||||
|
- What changed:
|
||||||
|
- Added a search-time budget for Gemini evaluation so the API can return partial reviewed results before reverse-proxy timeout instead of surfacing `504 Gateway Time-out`.
|
||||||
|
- Supplemental exploration now only runs when there is enough remaining request budget.
|
||||||
|
- Why it changed:
|
||||||
|
- Fully sequential enrichment and Gemini evaluation improved coverage but made the endpoint vulnerable to upstream proxy timeouts.
|
||||||
|
- How it was verified:
|
||||||
|
- code-path inspection of deadline-aware Gemini evaluation and handler warning flow
|
||||||
|
- What is still risky or incomplete:
|
||||||
|
- Very slow upstream pages or repeated Gemini retries can still reduce final result count because the handler now prioritizes responding before proxy timeout.
|
||||||
|
|
||||||
- Date: `2026-03-16`
|
- Date: `2026-03-16`
|
||||||
- What changed:
|
- What changed:
|
||||||
- Search enrichment now runs across the full result set sequentially instead of only enriching a capped top subset in parallel.
|
- Search enrichment now runs across the full result set sequentially instead of only enriching a capped top subset in parallel.
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ func (a *App) runDownload(recordID int64, url, start, end, quality, outputPath s
|
|||||||
|
|
||||||
func (a *App) searchMedia(c *gin.Context) {
|
func (a *App) searchMedia(c *gin.Context) {
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
|
deadline := started.Add(22 * time.Second)
|
||||||
var req struct {
|
var req struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
Platforms []string `json:"platforms"`
|
Platforms []string `json:"platforms"`
|
||||||
@@ -321,16 +322,16 @@ func (a *App) searchMedia(c *gin.Context) {
|
|||||||
scored := services.RankSearchResults(rankQuery, results)
|
scored := services.RankSearchResults(rankQuery, results)
|
||||||
a.debug("search ranked summary", summarizeSearchResults(scored, time.Since(started), services.GeminiCandidateLimit(len(scored)), ""))
|
a.debug("search ranked summary", summarizeSearchResults(scored, time.Since(started), services.GeminiCandidateLimit(len(scored)), ""))
|
||||||
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "analyzing all candidate visuals with Gemini Vision", "progress": 75})
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "analyzing all candidate visuals with Gemini Vision", "progress": 75})
|
||||||
recommended, geminiStats, geminiErr := services.EvaluateAllCandidatesWithGemini(a.GeminiService, req.Query, scored)
|
recommended, geminiStats, geminiErr := services.EvaluateAllCandidatesWithGeminiBudget(a.GeminiService, req.Query, scored, deadline)
|
||||||
a.debug("search gemini evaluation", geminiStats)
|
a.debug("search gemini evaluation", geminiStats)
|
||||||
if services.NeedsSupplementalExploration(recommended) {
|
if services.NeedsSupplementalExploration(recommended) && time.Now().Before(deadline.Add(-4*time.Second)) {
|
||||||
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "Gemini 평가가 약해 추가 후보를 탐색하는 중", "progress": 82})
|
a.Hub.Broadcast("progress", gin.H{"type": "search", "status": "Gemini 평가가 약해 추가 후보를 탐색하는 중", "progress": 82})
|
||||||
explorationQueries := buildSupplementalQueries(req.Query, queryVariants)
|
explorationQueries := buildSupplementalQueries(req.Query, queryVariants)
|
||||||
extraResults, extraErr := a.SearchService.SearchMedia(explorationQueries, enabledPlatforms)
|
extraResults, extraErr := a.SearchService.SearchMedia(explorationQueries, enabledPlatforms)
|
||||||
if extraErr == nil && len(extraResults) > 0 {
|
if extraErr == nil && len(extraResults) > 0 {
|
||||||
results = mergeSearchResults(results, extraResults)
|
results = mergeSearchResults(results, extraResults)
|
||||||
scored = services.RankSearchResults(strings.Join(explorationQueries[:min(len(explorationQueries), 3)], " "), results)
|
scored = services.RankSearchResults(strings.Join(explorationQueries[:min(len(explorationQueries), 3)], " "), results)
|
||||||
recommended, geminiStats, geminiErr = services.EvaluateAllCandidatesWithGemini(a.GeminiService, req.Query, scored)
|
recommended, geminiStats, geminiErr = services.EvaluateAllCandidatesWithGeminiBudget(a.GeminiService, req.Query, scored, deadline)
|
||||||
a.debug("search supplemental query variants", gin.H{"variants": explorationQueries, "variantCount": len(explorationQueries)})
|
a.debug("search supplemental query variants", gin.H{"variants": explorationQueries, "variantCount": len(explorationQueries)})
|
||||||
a.debug("search gemini evaluation after supplemental search", geminiStats)
|
a.debug("search gemini evaluation after supplemental search", geminiStats)
|
||||||
}
|
}
|
||||||
@@ -361,6 +362,9 @@ func (a *App) searchMedia(c *gin.Context) {
|
|||||||
if geminiErr != nil {
|
if geminiErr != nil {
|
||||||
warning = geminiErr.Error()
|
warning = geminiErr.Error()
|
||||||
}
|
}
|
||||||
|
if geminiStats.TimedOut && warning == "" {
|
||||||
|
warning = "search returned partial Gemini-reviewed results before the gateway timeout budget"
|
||||||
|
}
|
||||||
a.debug("search complete summary", summarizeRecommendationResults(merged, time.Since(started), warning))
|
a.debug("search complete summary", summarizeRecommendationResults(merged, time.Since(started), warning))
|
||||||
response := gin.H{"results": merged, "queries": queryVariants}
|
response := gin.H{"results": merged, "queries": queryVariants}
|
||||||
if warning != "" {
|
if warning != "" {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type GeminiBatchStats struct {
|
|||||||
Failed int `json:"failed"`
|
Failed int `json:"failed"`
|
||||||
SequentialRetried int `json:"sequentialRetried"`
|
SequentialRetried int `json:"sequentialRetried"`
|
||||||
RecommendedCount int `json:"recommendedCount"`
|
RecommendedCount int `json:"recommendedCount"`
|
||||||
|
TimedOut bool `json:"timedOut,omitempty"`
|
||||||
Errors []string `json:"errors,omitempty"`
|
Errors []string `json:"errors,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +91,10 @@ func GeminiCandidateLimit(total int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EvaluateAllCandidatesWithGemini(service *GeminiService, query string, ranked []SearchResult) ([]AIRecommendation, GeminiBatchStats, error) {
|
func EvaluateAllCandidatesWithGemini(service *GeminiService, query string, ranked []SearchResult) ([]AIRecommendation, GeminiBatchStats, error) {
|
||||||
|
return EvaluateAllCandidatesWithGeminiBudget(service, query, ranked, time.Time{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvaluateAllCandidatesWithGeminiBudget(service *GeminiService, query string, ranked []SearchResult, deadline time.Time) ([]AIRecommendation, GeminiBatchStats, error) {
|
||||||
if service == nil {
|
if service == nil {
|
||||||
return nil, GeminiBatchStats{}, fmt.Errorf("gemini service is not configured")
|
return nil, GeminiBatchStats{}, fmt.Errorf("gemini service is not configured")
|
||||||
}
|
}
|
||||||
@@ -107,6 +112,10 @@ func EvaluateAllCandidatesWithGemini(service *GeminiService, query string, ranke
|
|||||||
merged := make([]AIRecommendation, 0, len(ranked))
|
merged := make([]AIRecommendation, 0, len(ranked))
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
for idx := 0; idx < limit; idx++ {
|
for idx := 0; idx < limit; idx++ {
|
||||||
|
if !deadline.IsZero() && time.Now().After(deadline) {
|
||||||
|
stats.TimedOut = true
|
||||||
|
break
|
||||||
|
}
|
||||||
recommendations, err := recoverGeminiCandidateSequentially(service, query, ranked[idx])
|
recommendations, err := recoverGeminiCandidateSequentially(service, query, ranked[idx])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stats.Failed++
|
stats.Failed++
|
||||||
@@ -131,6 +140,10 @@ func EvaluateAllCandidatesWithGemini(service *GeminiService, query string, ranke
|
|||||||
stats.RecommendedCount = len(merged)
|
stats.RecommendedCount = len(merged)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case stats.TimedOut && len(merged) > 0:
|
||||||
|
return merged, stats, fmt.Errorf("gemini vision evaluation stopped early to avoid request timeout")
|
||||||
|
case stats.TimedOut && len(merged) == 0:
|
||||||
|
return nil, stats, fmt.Errorf("gemini vision evaluation timed out before any usable results were returned")
|
||||||
case len(merged) > 0 && stats.Failed == 0:
|
case len(merged) > 0 && stats.Failed == 0:
|
||||||
return merged, stats, nil
|
return merged, stats, nil
|
||||||
case len(merged) > 0 && stats.Failed > 0:
|
case len(merged) > 0 && stats.Failed > 0:
|
||||||
|
|||||||
Reference in New Issue
Block a user