This commit is contained in:
+66
-29
@@ -65,43 +65,48 @@ User query: ` + query,
|
||||
},
|
||||
}
|
||||
|
||||
rawBody, _ := json.Marshal(body)
|
||||
endpoint := "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" + g.APIKey
|
||||
resp, err := g.Client.Post(endpoint, "application/json", bytes.NewReader(rawBody))
|
||||
rawText, err := g.generateText(body)
|
||||
if err != nil {
|
||||
return []string{query}, fmt.Errorf("gemini query expansion request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
data, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return []string{query}, fmt.Errorf("gemini query expansion returned status %d: %s", resp.StatusCode, strings.TrimSpace(string(data)))
|
||||
return []string{query}, err
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Candidates []struct {
|
||||
Content struct {
|
||||
Parts []struct {
|
||||
Text string `json:"text"`
|
||||
} `json:"parts"`
|
||||
} `json:"content"`
|
||||
} `json:"candidates"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
return []string{query}, fmt.Errorf("gemini query expansion response decode failed: %w", err)
|
||||
}
|
||||
if len(payload.Candidates) == 0 || len(payload.Candidates[0].Content.Parts) == 0 {
|
||||
return []string{query}, fmt.Errorf("gemini query expansion returned no candidates")
|
||||
}
|
||||
|
||||
jsonText, err := extractJSONObject(payload.Candidates[0].Content.Parts[0].Text)
|
||||
jsonText, err := extractJSONObject(rawText)
|
||||
if err != nil {
|
||||
return []string{query}, fmt.Errorf("gemini query expansion JSON extraction failed: %w", err)
|
||||
strictBody := map[string]any{
|
||||
"contents": []map[string]any{
|
||||
{
|
||||
"parts": []map[string]string{
|
||||
{
|
||||
"text": `STRICT JSON ONLY.
|
||||
Output must start with { and end with }.
|
||||
Do not add prose, explanations, markdown, code fences, or labels.
|
||||
Return exactly this shape: {"querywords":["..."]}.
|
||||
Generate up to 10 search queries for media discovery across Google Video, Envato, and Artgrid.
|
||||
If the original query is Korean, include strong English stock-footage search phrases.
|
||||
User query: ` + query,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"generationConfig": map[string]any{
|
||||
"responseMimeType": "application/json",
|
||||
"temperature": 0.1,
|
||||
"maxOutputTokens": 220,
|
||||
},
|
||||
}
|
||||
rawText, retryErr := g.generateText(strictBody)
|
||||
if retryErr != nil {
|
||||
return []string{query}, retryErr
|
||||
}
|
||||
jsonText, err = extractJSONObject(rawText)
|
||||
if err != nil {
|
||||
return []string{query}, fmt.Errorf("gemini query expansion JSON extraction failed after strict retry: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var parsed QueryExpansion
|
||||
if err := json.Unmarshal([]byte(jsonText), &parsed); err != nil {
|
||||
return []string{query}, fmt.Errorf("gemini query expansion JSON parse failed: %w; raw=%q", err, truncateForError(payload.Candidates[0].Content.Parts[0].Text, 200))
|
||||
return []string{query}, fmt.Errorf("gemini query expansion JSON parse failed: %w; raw=%q", err, truncateForError(rawText, 200))
|
||||
}
|
||||
|
||||
queries := []string{query}
|
||||
@@ -121,6 +126,38 @@ User query: ` + query,
|
||||
return queries, nil
|
||||
}
|
||||
|
||||
func (g *GeminiService) generateText(body map[string]any) (string, error) {
|
||||
rawBody, _ := json.Marshal(body)
|
||||
endpoint := "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=" + g.APIKey
|
||||
resp, err := g.Client.Post(endpoint, "application/json", bytes.NewReader(rawBody))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("gemini request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
data, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
return "", fmt.Errorf("gemini returned status %d: %s", resp.StatusCode, strings.TrimSpace(string(data)))
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Candidates []struct {
|
||||
Content struct {
|
||||
Parts []struct {
|
||||
Text string `json:"text"`
|
||||
} `json:"parts"`
|
||||
} `json:"content"`
|
||||
} `json:"candidates"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
return "", fmt.Errorf("gemini response decode failed: %w", err)
|
||||
}
|
||||
if len(payload.Candidates) == 0 || len(payload.Candidates[0].Content.Parts) == 0 {
|
||||
return "", fmt.Errorf("gemini returned no candidates")
|
||||
}
|
||||
return payload.Candidates[0].Content.Parts[0].Text, nil
|
||||
}
|
||||
|
||||
func (g *GeminiService) Recommend(query string, candidates []SearchResult) ([]AIRecommendation, error) {
|
||||
if g.APIKey == "" {
|
||||
return nil, fmt.Errorf("gemini api key is not configured")
|
||||
|
||||
Reference in New Issue
Block a user