This commit is contained in:
+49
-20
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
const GeminiFallbackReason = "Gemini Vision 응답이 부족해 키워드 기준으로 보강된 결과입니다."
|
||||
const FallbackPreviewReason = "Fallback due to missing provider preview."
|
||||
const PendingVisualReason = "Ranked candidate pending stronger visual evidence."
|
||||
|
||||
type GeminiBatchStats struct {
|
||||
CandidateCap int `json:"candidateCap"`
|
||||
@@ -19,6 +21,7 @@ type GeminiBatchStats struct {
|
||||
Failed int `json:"failed"`
|
||||
SequentialRetried int `json:"sequentialRetried"`
|
||||
RecommendedCount int `json:"recommendedCount"`
|
||||
VisualRejectCount int `json:"visualRejectCount"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
@@ -58,19 +61,25 @@ func RankSearchResults(query string, results []SearchResult) []SearchResult {
|
||||
score -= 4
|
||||
}
|
||||
}
|
||||
if result.ThumbnailURL != "" {
|
||||
score += 2
|
||||
}
|
||||
if result.PreviewVideoURL != "" {
|
||||
score += 3
|
||||
score += 10
|
||||
}
|
||||
if hasUsableThumbnail(result.ThumbnailURL) {
|
||||
score += 5
|
||||
}
|
||||
if isLowValueThumbnail(result.ThumbnailURL) {
|
||||
score -= 8
|
||||
}
|
||||
if strings.TrimSpace(result.PreviewVideoURL) == "" && !hasUsableThumbnail(result.ThumbnailURL) {
|
||||
score -= 10
|
||||
}
|
||||
switch result.Source {
|
||||
case "Google Video":
|
||||
score -= 1
|
||||
score -= 2
|
||||
case "Envato":
|
||||
score += 7
|
||||
score += 5
|
||||
case "Artgrid":
|
||||
score += 7
|
||||
score += 4
|
||||
}
|
||||
scored = append(scored, scoredResult{item: result, score: score})
|
||||
}
|
||||
@@ -106,6 +115,11 @@ func EvaluateAllCandidatesWithGeminiWithDeadline(service *GeminiService, query s
|
||||
CandidateCap: limit,
|
||||
Requested: min(limit, len(ranked)),
|
||||
}
|
||||
for _, item := range ranked[:min(limit, len(ranked))] {
|
||||
if strings.TrimSpace(item.PreviewVideoURL) == "" && !hasUsableThumbnail(item.ThumbnailURL) {
|
||||
stats.VisualRejectCount++
|
||||
}
|
||||
}
|
||||
type batchResult struct {
|
||||
index int
|
||||
recommendations []AIRecommendation
|
||||
@@ -231,7 +245,7 @@ func BuildFallbackRecommendations(ranked []SearchResult, limit int, reason strin
|
||||
|
||||
fallback := make([]AIRecommendation, 0, min(limit, len(ranked)))
|
||||
for _, item := range ranked[:min(limit, len(ranked))] {
|
||||
fallback = append(fallback, AIRecommendation{
|
||||
fallback = append(fallback, DecorateRecommendationMedia(AIRecommendation{
|
||||
Title: item.Title,
|
||||
Link: item.Link,
|
||||
Snippet: item.Snippet,
|
||||
@@ -240,7 +254,7 @@ func BuildFallbackRecommendations(ranked []SearchResult, limit int, reason strin
|
||||
Source: item.Source,
|
||||
Reason: reason,
|
||||
Recommended: false,
|
||||
})
|
||||
}))
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -385,6 +399,8 @@ 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 {
|
||||
@@ -394,7 +410,7 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
|
||||
continue
|
||||
}
|
||||
seen[item.Link] = true
|
||||
merged = append(merged, item)
|
||||
merged = append(merged, DecorateRecommendationMedia(item))
|
||||
}
|
||||
|
||||
for _, item := range recommended {
|
||||
@@ -404,8 +420,11 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
|
||||
if looksNegativeReason(item.Reason) || strings.Contains(item.Reason, GeminiFallbackReason) {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(item.PreviewVideoURL) == "" && !hasUsableThumbnail(item.ThumbnailURL) {
|
||||
continue
|
||||
}
|
||||
seen[item.Link] = true
|
||||
merged = append(merged, item)
|
||||
merged = append(merged, DecorateRecommendationMedia(item))
|
||||
}
|
||||
|
||||
if len(merged) < min(12, limit) {
|
||||
@@ -413,20 +432,24 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
|
||||
if len(merged) >= min(12, limit) || item.Link == "" || seen[item.Link] {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(item.ThumbnailURL) == "" && strings.TrimSpace(item.PreviewVideoURL) == "" {
|
||||
if fillerCount >= maxFiller {
|
||||
break
|
||||
}
|
||||
if strings.TrimSpace(item.PreviewVideoURL) == "" && !hasUsableThumbnail(item.ThumbnailURL) {
|
||||
continue
|
||||
}
|
||||
seen[item.Link] = true
|
||||
merged = append(merged, AIRecommendation{
|
||||
merged = append(merged, DecorateRecommendationMedia(AIRecommendation{
|
||||
Title: item.Title,
|
||||
Link: item.Link,
|
||||
Snippet: item.Snippet,
|
||||
ThumbnailURL: item.ThumbnailURL,
|
||||
PreviewVideoURL: item.PreviewVideoURL,
|
||||
Source: item.Source,
|
||||
Reason: "Gemini 검토가 부족해 편집용 후보로 추가된 결과입니다.",
|
||||
Reason: PendingVisualReason,
|
||||
Recommended: false,
|
||||
})
|
||||
}))
|
||||
fillerCount++
|
||||
}
|
||||
}
|
||||
return merged
|
||||
@@ -435,31 +458,37 @@ func MergeRecommendations(recommended []AIRecommendation, ranked []SearchResult,
|
||||
func BackfillRecommendations(existing []AIRecommendation, ranked []SearchResult, limit int, reason string) []AIRecommendation {
|
||||
merged := make([]AIRecommendation, 0, min(limit, len(ranked)))
|
||||
seen := map[string]bool{}
|
||||
fillerCount := 0
|
||||
maxFiller := min(4, limit)
|
||||
for _, item := range existing {
|
||||
if item.Link == "" || seen[item.Link] {
|
||||
continue
|
||||
}
|
||||
seen[item.Link] = true
|
||||
merged = append(merged, item)
|
||||
merged = append(merged, DecorateRecommendationMedia(item))
|
||||
}
|
||||
for _, item := range ranked {
|
||||
if len(merged) >= limit || item.Link == "" || seen[item.Link] {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(item.ThumbnailURL) == "" && strings.TrimSpace(item.PreviewVideoURL) == "" {
|
||||
if fillerCount >= maxFiller {
|
||||
break
|
||||
}
|
||||
if strings.TrimSpace(item.PreviewVideoURL) == "" && !hasUsableThumbnail(item.ThumbnailURL) {
|
||||
continue
|
||||
}
|
||||
seen[item.Link] = true
|
||||
merged = append(merged, AIRecommendation{
|
||||
merged = append(merged, DecorateRecommendationMedia(AIRecommendation{
|
||||
Title: item.Title,
|
||||
Link: item.Link,
|
||||
Snippet: item.Snippet,
|
||||
ThumbnailURL: item.ThumbnailURL,
|
||||
PreviewVideoURL: item.PreviewVideoURL,
|
||||
Source: item.Source,
|
||||
Reason: reason,
|
||||
Reason: firstNonEmpty(strings.TrimSpace(reason), FallbackPreviewReason),
|
||||
Recommended: false,
|
||||
})
|
||||
}))
|
||||
fillerCount++
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user