Files

65 lines
2.2 KiB
Go

package agent
import (
"context"
"log/slog"
"strings"
"time"
"github.com/nextlevelbuilder/goclaw/internal/providers"
usagecaps "github.com/nextlevelbuilder/goclaw/internal/usage/caps"
)
const titleGenerateTimeout = 120 * time.Second
const titleSystemPrompt = `Generate a short title (max 15 words) for this conversation based on the user's message. Reply with only the title, no quotes or punctuation wrapping.`
// GenerateTitle uses a lightweight LLM call to create a short conversation title
// from the user's first message. Returns empty string on error.
func GenerateTitle(ctx context.Context, provider providers.Provider, model, userMessage string) string {
return GenerateTitleWithUsageCaps(ctx, nil, provider, model, userMessage)
}
func GenerateTitleWithUsageCaps(ctx context.Context, usageCaps *usagecaps.Service, provider providers.Provider, model, userMessage string) string {
ctx, cancel := context.WithTimeout(ctx, titleGenerateTimeout)
defer cancel()
req := providers.ChatRequest{
Messages: []providers.Message{
{Role: "system", Content: titleSystemPrompt},
{Role: "user", Content: userMessage},
},
Model: model,
Options: map[string]any{
// Larger budget: thinking-capable models (Gemini 2.5/3, GPT-5 reasoning)
// can consume output tokens on reasoning traces. 256 leaves room for a
// 15-word title even when the provider allocates some budget to thinking.
providers.OptMaxTokens: 256,
providers.OptTemperature: 0.3,
// Disable extended thinking for title generation — it's a trivial task
// that doesn't benefit from reasoning and defaults (esp. Gemini's "high")
// otherwise eat the entire max_tokens budget, truncating the title to 1 word.
providers.OptThinkingLevel: "off",
},
}
resp, err := usageCaps.Chat(ctx, provider, req, usagecaps.ChatOptions{
ModelID: model,
Purpose: "session-title",
MaxOutputTokens: 256,
})
if err != nil {
slog.Warn("title generation failed", "error", err)
return ""
}
title := strings.TrimSpace(resp.Content)
// Strip surrounding quotes if present.
title = strings.Trim(title, "\"'`")
title = strings.TrimSpace(title)
if runes := []rune(title); len(runes) > 100 {
title = string(runes[:100])
}
return title
}