mirror of
https://github.com/HugeFrog24/go-telegram-bot.git
synced 2026-08-28 22:11:38 +00:00
Debounce and caching
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MCPServer struct {
|
||||
@@ -15,6 +16,15 @@ type MCPServer struct {
|
||||
AllowedTools []string `json:"allowed_tools,omitempty"`
|
||||
}
|
||||
|
||||
type WebSearchConfig struct {
|
||||
AllowedDomains []string `json:"allowed_domains,omitempty"`
|
||||
BlockedDomains []string `json:"blocked_domains,omitempty"`
|
||||
FetchAllowedDomains []string `json:"fetch_allowed_domains,omitempty"`
|
||||
MaxUses int `json:"max_uses,omitempty"`
|
||||
Fetch bool `json:"fetch,omitempty"`
|
||||
MaxContentTokens int `json:"max_content_tokens,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
ThinkingModeAdaptive = "adaptive"
|
||||
ThinkingModeDisabled = "disabled"
|
||||
@@ -22,6 +32,28 @@ const (
|
||||
ThinkingDisplayOmitted = "omitted"
|
||||
)
|
||||
|
||||
// maxDebounceMs bounds debounce_ms. Beyond this the bot reads as unresponsive
|
||||
// rather than deliberate, and the coalesced turn drifts far enough from the
|
||||
// user's last message that the reply feels stale.
|
||||
const maxDebounceMs = 30000
|
||||
|
||||
// DebounceWindow is the quiet period an intake buffer waits before dispatching a
|
||||
// coalesced turn. Zero disables debouncing entirely (the default), matching the
|
||||
// opt-in behavior of comparable gateways.
|
||||
func (c *BotConfig) DebounceWindow() time.Duration {
|
||||
if c.DebounceMs <= 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(c.DebounceMs) * time.Millisecond
|
||||
}
|
||||
|
||||
// CacheHistoryEnabled reports whether a cache_control breakpoint should be placed
|
||||
// on the trailing conversation block in addition to the system prompt. Defaults
|
||||
// to true; set "cache_history": false to opt out.
|
||||
func (c *BotConfig) CacheHistoryEnabled() bool {
|
||||
return c.CacheHistory == nil || *c.CacheHistory
|
||||
}
|
||||
|
||||
type BotConfig struct {
|
||||
ID string `json:"id"`
|
||||
TelegramToken string `json:"telegram_token"`
|
||||
@@ -34,6 +66,8 @@ type BotConfig struct {
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Thinking string `json:"thinking,omitempty"`
|
||||
ThinkingDisplay string `json:"thinking_display,omitempty"`
|
||||
DebounceMs int `json:"debounce_ms,omitempty"`
|
||||
CacheHistory *bool `json:"cache_history,omitempty"`
|
||||
SystemPrompts map[string]string `json:"system_prompts"`
|
||||
Active bool `json:"active"`
|
||||
OwnerTelegramID int64 `json:"owner_telegram_id"`
|
||||
@@ -43,6 +77,7 @@ type BotConfig struct {
|
||||
ElevenLabsModel string `json:"elevenlabs_model"`
|
||||
DebugScreening bool `json:"debug_screening"`
|
||||
MCPServers []MCPServer `json:"mcp_servers,omitempty"`
|
||||
WebSearch *WebSearchConfig `json:"web_search,omitempty"`
|
||||
ConfigFilePath string `json:"-"`
|
||||
}
|
||||
|
||||
@@ -107,10 +142,7 @@ func loadAllConfigs(dir string) ([]BotConfig, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if config.Thinking == ThinkingModeAdaptive && config.MaxTokens > 0 && config.MaxTokens < 4000 {
|
||||
InfoLogger.Printf("[%s] thinking=adaptive with max_tokens=%d: thinking tokens count toward max_tokens; consider >= 4000",
|
||||
config.ID, config.MaxTokens)
|
||||
}
|
||||
logConfigAdvisories(&config)
|
||||
|
||||
config.ConfigFilePath = validPath
|
||||
configs = append(configs, config)
|
||||
@@ -124,6 +156,41 @@ func loadAllConfigs(dir string) ([]BotConfig, error) {
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// logConfigAdvisories emits non-fatal boot-time notes about settings that are
|
||||
// valid but likely to surprise: silently ineffective, or costlier than intended.
|
||||
func logConfigAdvisories(config *BotConfig) {
|
||||
if config.Thinking == ThinkingModeAdaptive && config.MaxTokens > 0 && config.MaxTokens < 4000 {
|
||||
InfoLogger.Printf("[%s] thinking=adaptive with max_tokens=%d: thinking tokens count toward max_tokens; consider >= 4000",
|
||||
config.ID, config.MaxTokens)
|
||||
}
|
||||
|
||||
if config.DebounceMs > 0 {
|
||||
InfoLogger.Printf("[%s] intake debounce enabled: coalescing rapid text messages over a %dms quiet window",
|
||||
config.ID, config.DebounceMs)
|
||||
} else {
|
||||
InfoLogger.Printf("[%s] intake debounce disabled: every message dispatches its own turn (set debounce_ms to coalesce rapid follow-ups)",
|
||||
config.ID)
|
||||
}
|
||||
|
||||
if ws := config.WebSearch; ws != nil && len(ws.AllowedDomains) == 0 && len(ws.BlockedDomains) == 0 {
|
||||
InfoLogger.Printf("[%s] web_search enabled with no allowed_domains/blocked_domains: the model may search the open web",
|
||||
config.ID)
|
||||
}
|
||||
|
||||
if ws := config.WebSearch; ws != nil && len(ws.FetchAllowedDomains) > 0 {
|
||||
if !ws.Fetch {
|
||||
InfoLogger.Printf("[%s] web_search.fetch_allowed_domains is set but fetch is disabled: it has no effect",
|
||||
config.ID)
|
||||
}
|
||||
for _, d := range ws.FetchAllowedDomains {
|
||||
if strings.Contains(d, "/") {
|
||||
InfoLogger.Printf("[%s] web_search.fetch_allowed_domains entry %q includes a path: web_fetch matches host-only, so the whole host is fetchable",
|
||||
config.ID, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateConfig(config *BotConfig, ids, tokens map[string]bool) error {
|
||||
if config.ID == "" {
|
||||
return fmt.Errorf("missing 'id' field")
|
||||
@@ -168,6 +235,26 @@ func validateConfig(config *BotConfig, ids, tokens map[string]bool) error {
|
||||
return fmt.Errorf("'max_tokens' must be greater than 0 when set")
|
||||
}
|
||||
|
||||
if config.DebounceMs < 0 {
|
||||
return fmt.Errorf("'debounce_ms' must be greater than 0 when set")
|
||||
}
|
||||
if config.DebounceMs > maxDebounceMs {
|
||||
return fmt.Errorf("'debounce_ms' of %d exceeds the maximum of %d (Telegram drops long-idle updates and users read silence as failure)",
|
||||
config.DebounceMs, maxDebounceMs)
|
||||
}
|
||||
|
||||
if ws := config.WebSearch; ws != nil {
|
||||
if len(ws.AllowedDomains) > 0 && len(ws.BlockedDomains) > 0 {
|
||||
return fmt.Errorf("'web_search' cannot set both allowed_domains and blocked_domains (the API rejects that)")
|
||||
}
|
||||
if ws.MaxUses < 0 {
|
||||
return fmt.Errorf("'web_search.max_uses' must be greater than 0 when set")
|
||||
}
|
||||
if ws.MaxContentTokens < 0 {
|
||||
return fmt.Errorf("'web_search.max_content_tokens' must be greater than 0 when set")
|
||||
}
|
||||
}
|
||||
|
||||
if config.MessagePerHour <= 0 {
|
||||
return fmt.Errorf("'messages_per_hour' must be greater than 0")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user