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