This commit is contained in:
+26
-36
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
const GeminiFallbackReason = "Gemini Vision 응답이 부족해 키워드 기준으로 보강된 결과입니다."
|
||||
const FallbackPreviewReason = "Fallback due to missing provider preview."
|
||||
const PendingVisualReason = "Ranked candidate pending stronger visual evidence."
|
||||
const SupplementalFallbackReason = "추가 탐색 후에도 충분한 확신 후보가 부족해 시각 자산이 있는 후보를 제한적으로 보강했습니다."
|
||||
|
||||
type GeminiBatchStats struct {
|
||||
CandidateCap int `json:"candidateCap"`
|
||||
@@ -258,6 +258,7 @@ func BuildFallbackRecommendations(ranked []SearchResult, limit int, reason strin
|
||||
Source: item.Source,
|
||||
Reason: reason,
|
||||
Recommended: false,
|
||||
Assessment: "unclear",
|
||||
}))
|
||||
}
|
||||
return fallback
|
||||
@@ -370,18 +371,22 @@ func NeedsSupplementalExploration(items []AIRecommendation) bool {
|
||||
|
||||
recommendedCount := 0
|
||||
negativeCount := 0
|
||||
unclearCount := 0
|
||||
for _, item := range items {
|
||||
if item.Recommended {
|
||||
if item.Recommended && item.Assessment == "positive" {
|
||||
recommendedCount++
|
||||
}
|
||||
if looksNegativeReason(item.Reason) {
|
||||
if IsExcludedAssessment(item.Assessment) || looksNegativeReason(item.Reason) {
|
||||
negativeCount++
|
||||
}
|
||||
if item.Assessment == "unclear" {
|
||||
unclearCount++
|
||||
}
|
||||
}
|
||||
if recommendedCount >= 3 {
|
||||
if recommendedCount >= 4 {
|
||||
return false
|
||||
}
|
||||
return negativeCount >= max(2, len(items)/2)
|
||||
return negativeCount >= max(2, len(items)/3) || unclearCount >= max(2, len(items)/2)
|
||||
}
|
||||
|
||||
func looksNegativeReason(reason string) bool {
|
||||
@@ -403,11 +408,9 @@ func looksNegativeReason(reason string) bool {
|
||||
func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult, limit int) []AIRecommendation {
|
||||
merged := make([]AIRecommendation, 0, min(limit, len(ranked)))
|
||||
seen := map[string]bool{}
|
||||
fillerCount := 0
|
||||
maxFiller := min(4, limit)
|
||||
|
||||
for _, item := range recommended {
|
||||
if !item.Recommended {
|
||||
if !item.Recommended || item.Assessment != "positive" {
|
||||
continue
|
||||
}
|
||||
if item.Link == "" || seen[item.Link] {
|
||||
@@ -421,7 +424,10 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
|
||||
if item.Recommended || item.Link == "" || seen[item.Link] || len(merged) >= limit {
|
||||
continue
|
||||
}
|
||||
if looksNegativeReason(item.Reason) || strings.Contains(item.Reason, GeminiFallbackReason) {
|
||||
if IsExcludedAssessment(item.Assessment) || looksNegativeReason(item.Reason) || strings.Contains(item.Reason, GeminiFallbackReason) {
|
||||
continue
|
||||
}
|
||||
if item.Assessment == "unclear" {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(item.PreviewVideoURL) == "" && !hasUsableThumbnail(item.ThumbnailURL) {
|
||||
@@ -430,32 +436,6 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
|
||||
seen[item.Link] = true
|
||||
merged = append(merged, DecorateRecommendationMedia(item))
|
||||
}
|
||||
|
||||
if len(merged) < min(16, limit) {
|
||||
for _, item := range ranked {
|
||||
if len(merged) >= min(16, limit) || item.Link == "" || seen[item.Link] {
|
||||
continue
|
||||
}
|
||||
if fillerCount >= maxFiller {
|
||||
break
|
||||
}
|
||||
if strings.TrimSpace(item.PreviewVideoURL) == "" && !hasUsableThumbnail(item.ThumbnailURL) {
|
||||
continue
|
||||
}
|
||||
seen[item.Link] = true
|
||||
merged = append(merged, DecorateRecommendationMedia(AIRecommendation{
|
||||
Title: item.Title,
|
||||
Link: item.Link,
|
||||
Snippet: item.Snippet,
|
||||
ThumbnailURL: item.ThumbnailURL,
|
||||
PreviewVideoURL: item.PreviewVideoURL,
|
||||
Source: item.Source,
|
||||
Reason: PendingVisualReason,
|
||||
Recommended: false,
|
||||
}))
|
||||
fillerCount++
|
||||
}
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
@@ -489,14 +469,24 @@ func BackfillRecommendations(existing []AIRecommendation, ranked []SearchResult,
|
||||
ThumbnailURL: item.ThumbnailURL,
|
||||
PreviewVideoURL: item.PreviewVideoURL,
|
||||
Source: item.Source,
|
||||
Reason: firstNonEmpty(strings.TrimSpace(reason), FallbackPreviewReason),
|
||||
Reason: firstNonEmpty(strings.TrimSpace(reason), SupplementalFallbackReason),
|
||||
Recommended: false,
|
||||
Assessment: "unclear",
|
||||
}))
|
||||
fillerCount++
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func IsExcludedAssessment(assessment string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(assessment)) {
|
||||
case "irrelevant", "inappropriate":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
|
||||
Reference in New Issue
Block a user