mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-13 06:47:30 +08:00
reduce hardcoded agent text via config-driven prompts and recall keywords
This commit is contained in:
@@ -150,7 +150,13 @@ clawgo channel test --channel telegram --to <chat_id> -m "ping"
|
||||
"heartbeat": {
|
||||
"enabled": true,
|
||||
"every_sec": 1800,
|
||||
"ack_max_chars": 64
|
||||
"ack_max_chars": 64,
|
||||
"prompt_template": "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK."
|
||||
},
|
||||
"texts": {
|
||||
"no_response_fallback": "I've completed processing but have no response to give.",
|
||||
"think_only_fallback": "Thinking process completed.",
|
||||
"memory_recall_keywords": ["remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision"]
|
||||
},
|
||||
"context_compaction": {
|
||||
"enabled": true,
|
||||
|
||||
@@ -150,7 +150,13 @@ Heartbeat + context compaction config example:
|
||||
"heartbeat": {
|
||||
"enabled": true,
|
||||
"every_sec": 1800,
|
||||
"ack_max_chars": 64
|
||||
"ack_max_chars": 64,
|
||||
"prompt_template": "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK."
|
||||
},
|
||||
"texts": {
|
||||
"no_response_fallback": "I've completed processing but have no response to give.",
|
||||
"think_only_fallback": "Thinking process completed.",
|
||||
"memory_recall_keywords": ["remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision"]
|
||||
},
|
||||
"context_compaction": {
|
||||
"enabled": true,
|
||||
|
||||
@@ -587,5 +587,5 @@ func buildHeartbeatService(cfg *config.Config, msgBus *bus.MessageBus) *heartbea
|
||||
},
|
||||
})
|
||||
return "queued", nil
|
||||
}, hbInterval, cfg.Agents.Defaults.Heartbeat.Enabled)
|
||||
}, hbInterval, cfg.Agents.Defaults.Heartbeat.Enabled, cfg.Agents.Defaults.Heartbeat.PromptTemplate)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
"heartbeat": {
|
||||
"enabled": true,
|
||||
"every_sec": 1800,
|
||||
"ack_max_chars": 64
|
||||
"ack_max_chars": 64,
|
||||
"prompt_template": "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK."
|
||||
},
|
||||
"texts": {
|
||||
"no_response_fallback": "I've completed processing but have no response to give.",
|
||||
"think_only_fallback": "Thinking process completed.",
|
||||
"memory_recall_keywords": ["remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision"]
|
||||
},
|
||||
"context_compaction": {
|
||||
"enabled": true,
|
||||
|
||||
@@ -37,6 +37,9 @@ type AgentLoop struct {
|
||||
compactionTrigger int
|
||||
compactionKeepRecent int
|
||||
heartbeatAckMaxChars int
|
||||
memoryRecallKeywords []string
|
||||
noResponseFallback string
|
||||
thinkOnlyFallback string
|
||||
audit *triggerAudit
|
||||
running bool
|
||||
}
|
||||
@@ -148,6 +151,9 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
||||
compactionTrigger: cfg.Agents.Defaults.ContextCompaction.TriggerMessages,
|
||||
compactionKeepRecent: cfg.Agents.Defaults.ContextCompaction.KeepRecentMessages,
|
||||
heartbeatAckMaxChars: cfg.Agents.Defaults.Heartbeat.AckMaxChars,
|
||||
memoryRecallKeywords: cfg.Agents.Defaults.Texts.MemoryRecallKeywords,
|
||||
noResponseFallback: cfg.Agents.Defaults.Texts.NoResponseFallback,
|
||||
thinkOnlyFallback: cfg.Agents.Defaults.Texts.ThinkOnlyFallback,
|
||||
audit: newTriggerAudit(workspace),
|
||||
running: false,
|
||||
}
|
||||
@@ -309,7 +315,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
summary := al.sessions.GetSummary(msg.SessionKey)
|
||||
memoryRecallUsed := false
|
||||
memoryRecallText := ""
|
||||
if shouldRecallMemory(msg.Content) {
|
||||
if shouldRecallMemory(msg.Content, al.memoryRecallKeywords) {
|
||||
if recall, err := al.tools.Execute(ctx, "memory_search", map[string]interface{}{"query": msg.Content, "maxResults": 3}); err == nil && strings.TrimSpace(recall) != "" {
|
||||
memoryRecallUsed = true
|
||||
memoryRecallText = strings.TrimSpace(recall)
|
||||
@@ -459,7 +465,11 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
}
|
||||
|
||||
if finalContent == "" {
|
||||
finalContent = "I've completed processing but have no response to give."
|
||||
fallback := strings.TrimSpace(al.noResponseFallback)
|
||||
if fallback == "" {
|
||||
fallback = "I've completed processing but have no response to give."
|
||||
}
|
||||
finalContent = fallback
|
||||
}
|
||||
|
||||
// Filter out <think>...</think> content from user-facing response
|
||||
@@ -472,7 +482,11 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
// For now, let's assume thoughts are auxiliary and empty response is okay if tools did work.
|
||||
// If no tools ran and only thoughts, user might be confused.
|
||||
if iteration == 1 {
|
||||
userContent = "Thinking process completed."
|
||||
fallback := strings.TrimSpace(al.thinkOnlyFallback)
|
||||
if fallback == "" {
|
||||
fallback = "Thinking process completed."
|
||||
}
|
||||
userContent = fallback
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,14 +857,17 @@ func truncateString(s string, maxLen int) string {
|
||||
return s[:maxLen-3] + "..."
|
||||
}
|
||||
|
||||
func shouldRecallMemory(text string) bool {
|
||||
func shouldRecallMemory(text string, keywords []string) bool {
|
||||
s := strings.ToLower(strings.TrimSpace(text))
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
keywords := []string{"remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision", "日期", "when did", "what did we"}
|
||||
if len(keywords) == 0 {
|
||||
keywords = []string{"remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision", "日期", "when did", "what did we"}
|
||||
}
|
||||
for _, k := range keywords {
|
||||
if strings.Contains(s, k) {
|
||||
kk := strings.ToLower(strings.TrimSpace(k))
|
||||
if kk != "" && strings.Contains(s, kk) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +38,22 @@ type AgentDefaults struct {
|
||||
Temperature float64 `json:"temperature" env:"CLAWGO_AGENTS_DEFAULTS_TEMPERATURE"`
|
||||
MaxToolIterations int `json:"max_tool_iterations" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||||
Texts AgentTextConfig `json:"texts"`
|
||||
ContextCompaction ContextCompactionConfig `json:"context_compaction"`
|
||||
RuntimeControl RuntimeControlConfig `json:"runtime_control"`
|
||||
}
|
||||
|
||||
type AgentTextConfig struct {
|
||||
NoResponseFallback string `json:"no_response_fallback"`
|
||||
ThinkOnlyFallback string `json:"think_only_fallback"`
|
||||
MemoryRecallKeywords []string `json:"memory_recall_keywords"`
|
||||
}
|
||||
|
||||
type HeartbeatConfig struct {
|
||||
Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ENABLED"`
|
||||
EverySec int `json:"every_sec" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_EVERY_SEC"`
|
||||
AckMaxChars int `json:"ack_max_chars" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ACK_MAX_CHARS"`
|
||||
Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ENABLED"`
|
||||
EverySec int `json:"every_sec" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_EVERY_SEC"`
|
||||
AckMaxChars int `json:"ack_max_chars" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_ACK_MAX_CHARS"`
|
||||
PromptTemplate string `json:"prompt_template" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_PROMPT_TEMPLATE"`
|
||||
}
|
||||
|
||||
type RuntimeControlConfig struct {
|
||||
@@ -265,6 +273,12 @@ func DefaultConfig() *Config {
|
||||
Enabled: true,
|
||||
EverySec: 30 * 60,
|
||||
AckMaxChars: 64,
|
||||
PromptTemplate: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
|
||||
},
|
||||
Texts: AgentTextConfig{
|
||||
NoResponseFallback: "I've completed processing but have no response to give.",
|
||||
ThinkOnlyFallback: "Thinking process completed.",
|
||||
MemoryRecallKeywords: []string{"remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision", "日期", "when did", "what did we"},
|
||||
},
|
||||
ContextCompaction: ContextCompactionConfig{
|
||||
Enabled: true,
|
||||
|
||||
@@ -11,20 +11,22 @@ import (
|
||||
)
|
||||
|
||||
type HeartbeatService struct {
|
||||
workspace string
|
||||
onHeartbeat func(string) (string, error)
|
||||
interval time.Duration
|
||||
enabled bool
|
||||
runner *lifecycle.LoopRunner
|
||||
workspace string
|
||||
onHeartbeat func(string) (string, error)
|
||||
interval time.Duration
|
||||
enabled bool
|
||||
promptTemplate string
|
||||
runner *lifecycle.LoopRunner
|
||||
}
|
||||
|
||||
func NewHeartbeatService(workspace string, onHeartbeat func(string) (string, error), intervalS int, enabled bool) *HeartbeatService {
|
||||
func NewHeartbeatService(workspace string, onHeartbeat func(string) (string, error), intervalS int, enabled bool, promptTemplate string) *HeartbeatService {
|
||||
return &HeartbeatService{
|
||||
workspace: workspace,
|
||||
onHeartbeat: onHeartbeat,
|
||||
interval: time.Duration(intervalS) * time.Second,
|
||||
enabled: enabled,
|
||||
runner: lifecycle.NewLoopRunner(),
|
||||
workspace: workspace,
|
||||
onHeartbeat: onHeartbeat,
|
||||
interval: time.Duration(intervalS) * time.Second,
|
||||
enabled: enabled,
|
||||
promptTemplate: strings.TrimSpace(promptTemplate),
|
||||
runner: lifecycle.NewLoopRunner(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,12 +88,11 @@ func (hs *HeartbeatService) buildPrompt() string {
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04")
|
||||
|
||||
prompt := fmt.Sprintf(`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.
|
||||
|
||||
Current time: %s
|
||||
|
||||
%s
|
||||
`, now, notes)
|
||||
tpl := hs.promptTemplate
|
||||
if strings.TrimSpace(tpl) == "" {
|
||||
tpl = "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK."
|
||||
}
|
||||
prompt := fmt.Sprintf("%s\n\nCurrent time: %s\n\n%s\n", tpl, now, notes)
|
||||
|
||||
return prompt
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user