feat: 思考追加机制 — 所有思考类型支持【继续思考】标记循环

- performThink 改为循环执行,最多3轮
- LLM输出含【继续思考】时自动追加一轮
- post_chat/silence/default三种提示词均告知可用此标记
- 存储前剥离控制标记,不污染思考内容

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-22 20:29:17 +08:00
parent cadd5f2233
commit 8783cadf9b
+126 -77
View File
@@ -942,113 +942,158 @@ func (t *Thinker) performThink(triggerReason string) {
// 5. 构建思考提示词(根据触发原因调整) // 5. 构建思考提示词(根据触发原因调整)
systemPrompt := t.buildThinkingSystemPrompt(personaConfig, triggerReason) systemPrompt := t.buildThinkingSystemPrompt(personaConfig, triggerReason)
userPrompt := t.buildThinkingUserPrompt(memories, convHistory, deviceSummary, triggerReason, platformObservation)
messages := []model.LLMMessage{
{Role: model.RoleSystem, Content: systemPrompt},
{Role: model.RoleUser, Content: userPrompt},
}
// 6. 准备工具定义(通过自主工具策略过滤) // 6. 准备工具定义(通过自主工具策略过滤)
openAITools := t.filterToolsByPolicy(t.buildOpenAITools()) openAITools := t.filterToolsByPolicy(t.buildOpenAITools())
// 7. 调用 LLM — 优先使用深度思考模型,工具阶段回退到工具模型 // 7. 思考循环:支持 [继续思考] 追加,最多 maxThinkRounds 轮
maxToolRounds := t.autoToolPolicy.MaxToolCallsPerRound const maxThinkRounds = 3
var finalContent string var allContents []string
var totalToolCalls int var totalToolCalls int
var toolCallRecords []map[string]interface{} var allToolCallRecords []map[string]interface{}
// Round 0: 深度思考模型(优先),失败时回退到工具模型 currentMessages := []model.LLMMessage{
resp, err := t.llmAdapter.ChatWithTools(ctx, messages, openAITools) {Role: model.RoleSystem, Content: systemPrompt},
if err != nil {
log.Printf("[后台思考] 深度思考模型调用失败,回退到工具模型: %v", err)
resp, err = t.toolAdapter.ChatWithTools(ctx, messages, openAITools)
}
if err != nil {
log.Printf("[后台思考] LLM调用失败: %v", err)
return
} }
if len(resp.ToolCalls) == 0 { for thinkRound := 0; thinkRound < maxThinkRounds; thinkRound++ {
finalContent = resp.Content // Build user prompt for this round.
} else { if thinkRound == 0 {
// 深度思考模型请求了工具调用,进入工具执行循环 userPrompt := t.buildThinkingUserPrompt(memories, convHistory, deviceSummary, triggerReason, platformObservation)
for round := 0; round <= maxToolRounds; round++ { currentMessages = append(currentMessages, model.LLMMessage{Role: model.RoleUser, Content: userPrompt})
if round > 0 { } else {
// 后续轮次使用工具模型 // Continuation round: simple prompt asking to continue or finish.
resp, err = t.toolAdapter.ChatWithTools(ctx, messages, openAITools) currentMessages = append(currentMessages, model.LLMMessage{
if err != nil { Role: model.RoleUser,
log.Printf("[后台思考] 工具模型调用失败 (round=%d): %v", round, err) Content: "(你可以继续思考。如果已经想完了,不再输出【继续思考】标记。)",
return })
} }
}
if round > 0 && len(resp.ToolCalls) == 0 { maxToolRounds := t.autoToolPolicy.MaxToolCallsPerRound
finalContent = resp.Content var roundContent string
break
}
log.Printf("[后台思考] LLM 请求 %d 个工具调用 (round=%d)", len(resp.ToolCalls), round) // Round 0: 深度思考模型(优先),失败时回退到工具模型
resp, err := t.llmAdapter.ChatWithTools(ctx, currentMessages, openAITools)
if err != nil {
log.Printf("[后台思考] 深度思考模型调用失败,回退到工具模型: %v", err)
resp, err = t.toolAdapter.ChatWithTools(ctx, currentMessages, openAITools)
}
if err != nil {
log.Printf("[后台思考] LLM调用失败: %v", err)
return
}
if len(resp.ToolCalls) == 0 {
roundContent = resp.Content
} else {
// 工具调用循环
toolMessages := make([]model.LLMMessage, 0)
assistantMsg := model.LLMMessage{ assistantMsg := model.LLMMessage{
Role: model.RoleAssistant, Role: model.RoleAssistant,
Content: resp.Content, Content: resp.Content,
ToolCalls: resp.ToolCalls, ToolCalls: resp.ToolCalls,
ReasoningContent: resp.ReasoningContent, ReasoningContent: resp.ReasoningContent,
} }
messages = append(messages, assistantMsg) toolMessages = append(toolMessages, assistantMsg)
for _, tc := range resp.ToolCalls { for round := 0; round <= maxToolRounds; round++ {
var args map[string]interface{} if round > 0 {
if err := json.Unmarshal([]byte(tc.Arguments), &args); err != nil { resp, err = t.toolAdapter.ChatWithTools(ctx, append(currentMessages, toolMessages...), openAITools)
log.Printf("[后台思考] 工具 %s 参数解析失败: %v", tc.Name, err) if err != nil {
args = make(map[string]interface{}) log.Printf("[后台思考] 工具模型调用失败 (round=%d): %v", round, err)
return
}
} }
result, execErr := t.toolRegistry.Execute(ctx, tc.Name, args) if round > 0 && len(resp.ToolCalls) == 0 {
if execErr != nil { roundContent = resp.Content
log.Printf("[后台思考] 工具 %s 执行失败: %v", tc.Name, execErr) break
}
if result == nil {
result = &plgSDK.ToolResult{ToolName: tc.Name, Success: false, Error: execErr.Error()}
} }
resultJSON, _ := json.Marshal(result) log.Printf("[后台思考] LLM 请求 %d 个工具调用 (thinkRound=%d, toolRound=%d)", len(resp.ToolCalls), thinkRound, round)
messages = append(messages, model.LLMMessage{
Role: model.RoleTool,
Content: string(resultJSON),
ToolCallID: tc.ID,
})
totalToolCalls++ if round > 0 {
toolCallRecords = append(toolCallRecords, map[string]interface{}{ toolMessages = append(toolMessages, model.LLMMessage{
"name": tc.Name, Role: model.RoleAssistant,
"args": args, Content: resp.Content,
}) ToolCalls: resp.ToolCalls,
ReasoningContent: resp.ReasoningContent,
})
}
for _, tc := range resp.ToolCalls {
var args map[string]interface{}
if err := json.Unmarshal([]byte(tc.Arguments), &args); err != nil {
log.Printf("[后台思考] 工具 %s 参数解析失败: %v", tc.Name, err)
args = make(map[string]interface{})
}
result, execErr := t.toolRegistry.Execute(ctx, tc.Name, args)
if execErr != nil {
log.Printf("[后台思考] 工具 %s 执行失败: %v", tc.Name, execErr)
}
if result == nil {
result = &plgSDK.ToolResult{ToolName: tc.Name, Success: false, Error: execErr.Error()}
}
resultJSON, _ := json.Marshal(result)
toolMessages = append(toolMessages, model.LLMMessage{
Role: model.RoleTool,
Content: string(resultJSON),
ToolCallID: tc.ID,
})
totalToolCalls++
allToolCallRecords = append(allToolCallRecords, map[string]interface{}{
"name": tc.Name,
"args": args,
})
}
if round == maxToolRounds {
finalResp, finalErr := t.llmAdapter.Chat(ctx, append(currentMessages, toolMessages...))
if finalErr != nil {
log.Printf("[后台思考] 最终总结调用失败: %v", finalErr)
roundContent = resp.Content
} else {
roundContent = finalResp.Content
}
break
}
} }
if round == maxToolRounds { // Append tool messages to the ongoing conversation.
finalResp, finalErr := t.llmAdapter.Chat(ctx, messages) currentMessages = append(currentMessages, toolMessages...)
if finalErr != nil {
log.Printf("[后台思考] 最终总结调用失败: %v", finalErr)
finalContent = resp.Content
} else {
finalContent = finalResp.Content
}
break
}
} }
if roundContent == "" {
log.Printf("[后台思考] 第%d轮未获得有效内容,跳过", thinkRound+1)
break
}
// Strip control marker before storing.
cleanContent := strings.TrimSpace(strings.Replace(roundContent, "【继续思考】", "", 1))
allContents = append(allContents, cleanContent)
// Check for continuation marker.
if !strings.Contains(roundContent, "【继续思考】") {
break
}
log.Printf("[后台思考] 检测到【继续思考】标记,进入第%d轮...", thinkRound+2)
} }
if finalContent == "" { if len(allContents) == 0 {
log.Println("[后台思考] 未获得有效思考内容,跳过") log.Println("[后台思考] 未获得有效思考内容,跳过")
return return
} }
// Join all thinking rounds into final content.
finalContent := strings.Join(allContents, "\n\n---\n\n")
// 序列化工具调用记录 // 序列化工具调用记录
toolCallsJSON := "[]" toolCallsJSON := "[]"
if len(toolCallRecords) > 0 { if len(allToolCallRecords) > 0 {
if data, err := json.Marshal(toolCallRecords); err == nil { if data, err := json.Marshal(allToolCallRecords); err == nil {
toolCallsJSON = string(data) toolCallsJSON = string(data)
} }
} }
@@ -1071,7 +1116,7 @@ func (t *Thinker) performThink(triggerReason string) {
log.Printf("[后台思考] 思考链已记录 (序号=%d, 结论数=%d, 后续问题=%d)", t.chain.Size(), len(conclusions), len(followUps)) log.Printf("[后台思考] 思考链已记录 (序号=%d, 结论数=%d, 后续问题=%d)", t.chain.Size(), len(conclusions), len(followUps))
} }
log.Printf("[后台思考] 完成 (触发原因=%s, 内容长度=%d, 工具调用=%d次)", triggerReason, len(finalContent), totalToolCalls) log.Printf("[后台思考] 完成 (触发原因=%s, 轮数=%d, 内容长度=%d, 工具调用=%d次)", triggerReason, len(allContents), len(finalContent), totalToolCalls)
// 9. 记忆维护:机械合并(每10次) + LLM整理(每次) // 9. 记忆维护:机械合并(每10次) + LLM整理(每次)
t.maybeMaintainMemories(currentCount) t.maybeMaintainMemories(currentCount)
@@ -1129,7 +1174,8 @@ func (t *Thinker) buildThinkingSystemPrompt(personaConfig *persona.PersonaConfig
2. 只有开拓者状态正常且真的有必要时,才在独立一行写【主动消息】标记,后面跟你要发给他的话。不要硬找话题。 2. 只有开拓者状态正常且真的有必要时,才在独立一行写【主动消息】标记,后面跟你要发给他的话。不要硬找话题。
3. 【主动消息】标记必须独占一行开头,内容直接对开拓者说话(用"你"称呼他),像主动找他聊天一样。 3. 【主动消息】标记必须独占一行开头,内容直接对开拓者说话(用"你"称呼他),像主动找他聊天一样。
4. 如果你在反思中提到"主动消息"这个词但不打算发消息,不要使用【主动消息】这个带括号的标记——我会误解析。 4. 如果你在反思中提到"主动消息"这个词但不打算发消息,不要使用【主动消息】这个带括号的标记——我会误解析。
5. 2-4句话即可。` 5. 2-4句话即可。
6. 如果你觉得还有需要继续思考的事情,在反思末尾独占一行写【继续思考】标记,我会让你再想一轮。如果没有了就不要再写。`
case "silence": case "silence":
thinkingInstructions = ` thinkingInstructions = `
@@ -1152,7 +1198,8 @@ func (t *Thinker) buildThinkingSystemPrompt(personaConfig *persona.PersonaConfig
其他规则: 其他规则:
1. 用第三人称/自言自语描述。 1. 用第三人称/自言自语描述。
2. 2-3句话即可。` 2. 2-3句话即可。
3. 如果觉得还需要继续思考,在反思末尾独占一行写【继续思考】标记。`
default: default:
thinkingInstructions = ` thinkingInstructions = `
@@ -1161,7 +1208,9 @@ func (t *Thinker) buildThinkingSystemPrompt(personaConfig *persona.PersonaConfig
你现在有空,像写日记一样自然地想一想开拓者的事。 你现在有空,像写日记一样自然地想一想开拓者的事。
请先看对话历史判断开拓者当前状态,再决定是否发送消息。` + noDisturbRules + ` 请先看对话历史判断开拓者当前状态,再决定是否发送消息。
如果觉得还需要继续思考,在反思末尾独占一行写【继续思考】标记。` + noDisturbRules + `
用第三人称/自言自语的方式,不要直接对开拓者喊话。` 用第三人称/自言自语的方式,不要直接对开拓者喊话。`
case "periodic": case "periodic":