gemini/fix/llm-retries #50

Merged
eleith merged 4 commits from gemini/fix/llm-retries into main 2025-08-19 19:01:27 +00:00
4 changed files with 50 additions and 12 deletions

No files matched your search

+37 -5
View File
@@ -3,11 +3,17 @@ package llm
import (
"context"
"errors"
"log"
"strings"
"time"
"google.golang.org/genai"
)
const GeminiModel = "gemini-1.5-flash"
const (
GeminiModel = "gemini-1.5-flash"
maxRetries = 3
)
type modelClient interface {
GenerateContent(ctx context.Context, model string, parts []*genai.Content, config *genai.GenerateContentConfig) (*genai.GenerateContentResponse, error)
@@ -33,15 +39,41 @@ func NewGeminiService(apiKey string) (*GeminiService, error) {
return &GeminiService{client: client.Models, modelName: GeminiModel}, nil
}
func (s *GeminiService) generateContentWithRetry(ctx context.Context, prompt string, schema *genai.Schema) (*genai.GenerateContentResponse, error) {
var resp *genai.GenerateContentResponse
var err error
for i := 0; i < maxRetries; i++ {
resp, err = s.client.GenerateContent(ctx, s.modelName, genai.Text(prompt), &genai.GenerateContentConfig{
ResponseMIMEType: "application/json",
ResponseSchema: schema,
})
if err == nil {
return resp, nil
}
var apiError *genai.APIError
if errors.As(err, &apiError) {
log.Printf("LLM API error: Code=%d, Status=%s, Message=%s", apiError.Code, apiError.Status, apiError.Message)
if apiError.Code == 503 && strings.Contains(apiError.Message, "overloaded") {
log.Printf("Retrying LLM call (%d/%d)...", i+1, maxRetries-1)
time.Sleep(time.Second * time.Duration(i+1)) // Wait 1, 2 seconds
continue
}
}
return nil, err
}
return nil, err
}
func (s *GeminiService) GenerateContent(ctx context.Context, prompt string, schema *genai.Schema) (string, error) {
if s.client == nil {
return "", errors.New("LLM service is disabled: no API key provided")
}
resp, err := s.client.GenerateContent(ctx, s.modelName, genai.Text(prompt), &genai.GenerateContentConfig{
ResponseMIMEType: "application/json",
ResponseSchema: schema,
})
resp, err := s.generateContentWithRetry(ctx, prompt, schema)
if err != nil {
return "", err
}
+1 -1
View File
@@ -29,7 +29,7 @@
<section class="summary">
<h2 class="group-entries-title">Summary</h2>
<p>{{.Summary}}</p>
<p>{{textToHTML .Summary}}</p>
</section>
<section class="entries">
+11 -1
View File
@@ -13,7 +13,9 @@ var staticFS embed.FS
func FuncMap() template.FuncMap {
return template.FuncMap{
"include": include,
"include": include,
"textToHTML": textToHTML,
"htmlEscape": htmlEscape,
}
}
@@ -32,3 +34,11 @@ func include(filename string) (any, error) {
return template.HTML(b), nil
}
}
func textToHTML(s string) template.HTML {
return template.HTML(strings.ReplaceAll(template.HTMLEscapeString(s), "\n", "<br>"))
}
func htmlEscape(s string) template.HTML {
return template.HTML(s)
}
+1 -5
View File
@@ -27,11 +27,7 @@ func init() {
archiveTemplateName := "entries.gohtml"
emailTemplateName := "email.gotxt"
ArchiveTemplate, err = htmlTemplate.New(archiveTemplateName).Funcs(FuncMap()).Funcs(htmlTemplate.FuncMap{
"htmlEscape": func(s string) htmlTemplate.HTML {
return htmlTemplate.HTML(s)
},
}).ParseFS(embedFS, archiveTemplateName)
ArchiveTemplate, err = htmlTemplate.New(archiveTemplateName).Funcs(FuncMap()).ParseFS(embedFS, archiveTemplateName)
if err != nil {
log.Fatalf("Error parsing archive template: %v", err)