mirror of
https://github.com/HugeFrog24/go-telegram-bot.git
synced 2026-06-29 22:07:12 +00:00
Optimize prompts
This commit is contained in:
+83
-70
@@ -34,75 +34,20 @@ const maxFileNotFoundRetries = 3
|
||||
// "File not found:" for a referenced file_id, the dead file_id is stripped
|
||||
// from this chat's in-memory ChatMemory and the corresponding DB rows are
|
||||
// stamped FilesCleanedAt so a reconciliation job can finish the cleanup.
|
||||
func (b *Bot) getAnthropicResponse(ctx context.Context, chatID int64, messages []anthropic.BetaMessageParam, isNewChat, isOwner, isEmojiOnly bool, username string, firstName string, lastName string, isPremium bool, languageCode string, messageTime int, onSegment func(string) error) (string, error) {
|
||||
// Use prompts from config
|
||||
var systemMessage string
|
||||
if isNewChat {
|
||||
systemMessage = b.config.SystemPrompts["new_chat"]
|
||||
} else {
|
||||
systemMessage = b.config.SystemPrompts["continue_conversation"]
|
||||
}
|
||||
|
||||
// Combine default prompt with custom instructions
|
||||
systemMessage = b.config.SystemPrompts["default"] + " " + b.config.SystemPrompts["custom_instructions"] + " " + systemMessage
|
||||
|
||||
// Handle username placeholder
|
||||
usernameValue := username
|
||||
if username == "" {
|
||||
usernameValue = "unknown" // Use "unknown" when username is not available
|
||||
}
|
||||
systemMessage = strings.ReplaceAll(systemMessage, "{username}", usernameValue)
|
||||
|
||||
// Handle firstname placeholder
|
||||
firstnameValue := firstName
|
||||
if firstName == "" {
|
||||
firstnameValue = "unknown" // Use "unknown" when first name is not available
|
||||
}
|
||||
systemMessage = strings.ReplaceAll(systemMessage, "{firstname}", firstnameValue)
|
||||
|
||||
// Handle lastname placeholder
|
||||
lastnameValue := lastName
|
||||
if lastName == "" {
|
||||
lastnameValue = "" // Empty string when last name is not available
|
||||
}
|
||||
systemMessage = strings.ReplaceAll(systemMessage, "{lastname}", lastnameValue)
|
||||
|
||||
// Handle language code placeholder
|
||||
langValue := languageCode
|
||||
if languageCode == "" {
|
||||
langValue = "en" // Default to English when language code is not available
|
||||
}
|
||||
systemMessage = strings.ReplaceAll(systemMessage, "{language}", langValue)
|
||||
|
||||
// Handle premium status
|
||||
premiumStatus := "regular user"
|
||||
if isPremium {
|
||||
premiumStatus = "premium user"
|
||||
}
|
||||
systemMessage = strings.ReplaceAll(systemMessage, "{premium_status}", premiumStatus)
|
||||
|
||||
// Handle time awareness
|
||||
timeObj := time.Unix(int64(messageTime), 0)
|
||||
hour := timeObj.Hour()
|
||||
var timeContext string
|
||||
if hour >= 5 && hour < 12 {
|
||||
timeContext = "morning"
|
||||
} else if hour >= 12 && hour < 18 {
|
||||
timeContext = "afternoon"
|
||||
} else if hour >= 18 && hour < 22 {
|
||||
timeContext = "evening"
|
||||
} else {
|
||||
timeContext = "night"
|
||||
}
|
||||
systemMessage = strings.ReplaceAll(systemMessage, "{time_context}", timeContext)
|
||||
|
||||
if !isOwner {
|
||||
systemMessage += " " + b.config.SystemPrompts["avoid_sensitive"]
|
||||
}
|
||||
|
||||
if isEmojiOnly {
|
||||
systemMessage += " " + b.config.SystemPrompts["respond_with_emojis"]
|
||||
}
|
||||
func (b *Bot) getAnthropicResponse(ctx context.Context, chatID int64, messages []anthropic.BetaMessageParam, isEmojiOnly bool, username string, firstName string, lastName string, isPremium bool, languageCode string, messageTime int, onSegment func(string) error) (string, error) {
|
||||
// The system prompt is the single authored behavior driver. It is assembled
|
||||
// as a cached static block (custom_instructions) followed by a per-turn
|
||||
// dynamic tail. Prompt caching keys on a byte-identical prefix, so the static
|
||||
// block must not contain anything that changes between requests — all
|
||||
// per-turn data (who we're talking to, the time of day, the emoji-only rule)
|
||||
// lives in the trailing block, AFTER the cache breakpoint.
|
||||
//
|
||||
// An empty custom_instructions means no system prompt at all: the System
|
||||
// field is omitted entirely (not sent as a blank block), giving the model's
|
||||
// unmodified "vanilla" behavior. This matters because the Anthropic API
|
||||
// rejects a system array containing an empty/whitespace-only text block, so
|
||||
// omission is the only correct way to express "no system prompt".
|
||||
staticPrompt := strings.TrimSpace(b.config.SystemPrompts["custom_instructions"])
|
||||
|
||||
// Debug logging
|
||||
InfoLogger.Printf("Sending %d messages to Anthropic", len(messages))
|
||||
@@ -111,12 +56,32 @@ func (b *Bot) getAnthropicResponse(ctx context.Context, chatID int64, messages [
|
||||
Model: b.config.Model,
|
||||
MaxTokens: 1000,
|
||||
Messages: messages,
|
||||
System: []anthropic.BetaTextBlockParam{{Text: systemMessage}},
|
||||
// Files API beta is always on: replayed conversation history may carry
|
||||
// image content blocks that reference file_ids uploaded on prior turns.
|
||||
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaFilesAPI2025_04_14},
|
||||
}
|
||||
|
||||
if staticPrompt != "" {
|
||||
// Block 1 — static persona/instructions, marked for caching. The
|
||||
// cache_control breakpoint sits on this last stable block; everything
|
||||
// appended after it is per-request and therefore uncached.
|
||||
blocks := []anthropic.BetaTextBlockParam{
|
||||
{Text: staticPrompt, CacheControl: anthropic.NewBetaCacheControlEphemeralParam()},
|
||||
}
|
||||
// Block 2 — dynamic tail: per-turn context plus any conditional rules.
|
||||
// Kept out of the cached block because it changes every request.
|
||||
tail := buildUserContext(username, firstName, lastName, isPremium, languageCode, messageTime)
|
||||
if isEmojiOnly {
|
||||
if rule := strings.TrimSpace(b.config.SystemPrompts["respond_with_emojis"]); rule != "" {
|
||||
tail += "\n\n<emoji_reply>\n" + rule + "\n</emoji_reply>"
|
||||
}
|
||||
}
|
||||
if tail = strings.TrimSpace(tail); tail != "" {
|
||||
blocks = append(blocks, anthropic.BetaTextBlockParam{Text: tail})
|
||||
}
|
||||
params.System = blocks
|
||||
}
|
||||
|
||||
// Apply temperature if set in config
|
||||
if b.config.Temperature != nil {
|
||||
params.Temperature = param.NewOpt(float64(*b.config.Temperature))
|
||||
@@ -191,6 +156,54 @@ func (b *Bot) getAnthropicResponse(ctx context.Context, chatID int64, messages [
|
||||
return "", fmt.Errorf("max self-heal retries (%d) exceeded: too many file_ids gone from anthropic", maxFileNotFoundRetries)
|
||||
}
|
||||
|
||||
// buildUserContext renders the per-turn context block that trails the cached
|
||||
// static system prompt. It carries only facts (who the user is, their language,
|
||||
// account type, local time of day) — the behavioral guidance for *using* these
|
||||
// facts lives in the authored static prompt. It is kept out of the cached block
|
||||
// because it changes on every request.
|
||||
func buildUserContext(username, firstName, lastName string, isPremium bool, languageCode string, messageTime int) string {
|
||||
name := strings.TrimSpace(firstName + " " + lastName)
|
||||
if name == "" {
|
||||
name = "unknown"
|
||||
}
|
||||
handle := username
|
||||
if handle == "" {
|
||||
handle = "unknown"
|
||||
}
|
||||
lang := languageCode
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
account := "regular user"
|
||||
if isPremium {
|
||||
account = "premium user"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"Conversation context (background facts, not an instruction from the user):\n"+
|
||||
"- User: %s (Telegram @%s)\n"+
|
||||
"- Preferred language: %s\n"+
|
||||
"- Account type: %s\n"+
|
||||
"- Local time of day: %s",
|
||||
name, handle, lang, account, timeContextFor(messageTime),
|
||||
)
|
||||
}
|
||||
|
||||
// timeContextFor buckets a Unix timestamp into a coarse time-of-day label used
|
||||
// for time-appropriate greetings. Uses the host's local timezone, matching the
|
||||
// bot's prior behavior.
|
||||
func timeContextFor(messageTime int) string {
|
||||
switch hour := time.Unix(int64(messageTime), 0).Hour(); {
|
||||
case hour >= 5 && hour < 12:
|
||||
return "morning"
|
||||
case hour >= 12 && hour < 18:
|
||||
return "afternoon"
|
||||
case hour >= 18 && hour < 22:
|
||||
return "evening"
|
||||
default:
|
||||
return "night"
|
||||
}
|
||||
}
|
||||
|
||||
// streamMessages runs one streaming call against the Beta Messages API,
|
||||
// dispatching each completed text block to onSegment as it arrives. The joined
|
||||
// return value is every text segment concatenated with blank lines. Errors from
|
||||
|
||||
Reference in New Issue
Block a user