Optimize backend search and evaluation pipeline
build-push / docker (push) Has been cancelled

This commit is contained in:
AI Assistant
2026-03-16 16:13:43 +09:00
parent c5f6c611ec
commit 60fdd7842c
7 changed files with 371 additions and 10 deletions
+59 -4
View File
@@ -14,6 +14,7 @@ import (
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
@@ -23,6 +24,14 @@ type GeminiService struct {
GenerateEndpoint string
TranslateEndpoint string
Debug func(message string, data any)
cacheMu sync.Mutex
visualCache map[string]cachedVisualData
}
type cachedVisualData struct {
data string
mimeType string
expiresAt time.Time
}
type AIRecommendation struct {
@@ -46,6 +55,7 @@ func NewGeminiService(apiKey string) *GeminiService {
Client: &http.Client{Timeout: 40 * time.Second},
GenerateEndpoint: "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
TranslateEndpoint: "https://translate.googleapis.com/translate_a/single",
visualCache: map[string]cachedVisualData{},
}
}
@@ -187,7 +197,7 @@ User query: ` + query,
maxImages := min(len(candidates), 10)
visualCount := 0
for idx := 0; idx < maxImages; idx++ {
img, mimeType, err := fetchCandidateVisualInlineData(g.Client, candidates[idx])
img, mimeType, err := g.fetchCandidateVisualInlineData(candidates[idx])
if err != nil {
g.debug("gemini:vision_candidate_visual_error", map[string]any{
"index": idx,
@@ -330,6 +340,32 @@ func fetchImageAsInlineData(client *http.Client, imageURL, referer string) (stri
return base64.StdEncoding.EncodeToString(data), mimeType, nil
}
func (g *GeminiService) getCachedVisual(key string) (string, string, bool) {
g.cacheMu.Lock()
defer g.cacheMu.Unlock()
entry, ok := g.visualCache[key]
if !ok {
return "", "", false
}
if time.Now().After(entry.expiresAt) {
delete(g.visualCache, key)
return "", "", false
}
return entry.data, entry.mimeType, true
}
func (g *GeminiService) setCachedVisual(key, data, mimeType string, ttl time.Duration) {
g.cacheMu.Lock()
defer g.cacheMu.Unlock()
g.visualCache[key] = cachedVisualData{
data: data,
mimeType: mimeType,
expiresAt: time.Now().Add(ttl),
}
}
func newBrowserStyleImageRequest(imageURL, referer string) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, imageURL, nil)
if err != nil {
@@ -344,21 +380,40 @@ func newBrowserStyleImageRequest(imageURL, referer string) (*http.Request, error
return req, nil
}
func fetchCandidateVisualInlineData(client *http.Client, candidate SearchResult) (string, string, error) {
func (g *GeminiService) fetchCandidateVisualInlineData(candidate SearchResult) (string, string, error) {
if candidate.PreviewVideoURL != "" && (candidate.Source == "Envato" || candidate.Source == "Artgrid") {
cacheKey := "frame\n" + candidate.PreviewVideoURL
if data, mimeType, ok := g.getCachedVisual(cacheKey); ok {
return data, mimeType, nil
}
data, mimeType, err := extractFrameFromVideo(candidate.PreviewVideoURL)
if err == nil {
g.setCachedVisual(cacheKey, data, mimeType, 10*time.Minute)
return data, mimeType, nil
}
}
if candidate.ThumbnailURL != "" {
data, mimeType, err := fetchImageAsInlineData(client, candidate.ThumbnailURL, candidate.Link)
cacheKey := "image\n" + candidate.ThumbnailURL
if data, mimeType, ok := g.getCachedVisual(cacheKey); ok {
return data, mimeType, nil
}
data, mimeType, err := fetchImageAsInlineData(g.Client, candidate.ThumbnailURL, candidate.Link)
if err == nil {
g.setCachedVisual(cacheKey, data, mimeType, 10*time.Minute)
return data, mimeType, nil
}
}
if candidate.PreviewVideoURL != "" {
return extractFrameFromVideo(candidate.PreviewVideoURL)
cacheKey := "frame\n" + candidate.PreviewVideoURL
if data, mimeType, ok := g.getCachedVisual(cacheKey); ok {
return data, mimeType, nil
}
data, mimeType, err := extractFrameFromVideo(candidate.PreviewVideoURL)
if err != nil {
return "", "", err
}
g.setCachedVisual(cacheKey, data, mimeType, 10*time.Minute)
return data, mimeType, nil
}
return "", "", fmt.Errorf("candidate has no thumbnail or preview video")
}