gemini/fix/llm-retries #50
No files matched your search
+37
-5
@@ -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
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user