运行 Agent 循环
(String userInput)
| 117 | * 运行 Agent 循环 |
| 118 | */ |
| 119 | public String run(String userInput) { |
| 120 | log.info("ReAct run started: inputLength={}", userInput == null ? 0 : userInput.length()); |
| 121 | pruneHistoricalImagePayloads(); |
| 122 | // 存入短期记忆 |
| 123 | memoryManager.addUserMessage(userInput); |
| 124 | storeExplicitBrowserMemoryHint(userInput); |
| 125 | |
| 126 | // 检索相关长期记忆,注入到 system prompt |
| 127 | ContextProfile contextProfile = memoryManager.getContextProfile(); |
| 128 | String memoryContext = memoryManager.buildContextForQuery(userInput, contextProfile.memoryContextTokens()); |
| 129 | updateSystemPromptWithMemory(memoryContext); |
| 130 | |
| 131 | // 添加用户输入到历史(如有 skill body 注入,前置到原文之前) |
| 132 | String userMessageContent = prependSkillBodies(userInput); |
| 133 | conversationHistory.add(ImageReferenceParser.userMessage( |
| 134 | userMessageContent, |
| 135 | Path.of(toolRegistry.getProjectPath()))); |
| 136 | StringBuilder reasoningTranscript = new StringBuilder(); |
| 137 | StreamRenderer streamRenderer = new StreamRenderer(renderer()); |
| 138 | |
| 139 | long startNanos = System.nanoTime(); |
| 140 | AgentBudget budget = AgentBudget.fromLlmClient(llmClient); |
| 141 | pushStatus(budget, startNanos, "running"); |
| 142 | |
| 143 | // 主退出条件 = LLM 自己决定(不再调用工具就返回); |
| 144 | // budget 仅在 token 用尽 / 检测到死循环 / 超出硬轮数时兜底。 |
| 145 | while (true) { |
| 146 | if (CancellationContext.isCancelled()) { |
| 147 | log.info("ReAct run cancelled before iteration"); |
| 148 | pushStatus(budget, startNanos, "idle"); |
| 149 | return "⏹️ 已取消当前任务。"; |
| 150 | } |
| 151 | // 调 LLM 前评估 conversationHistory 是否接近 window 上限;超阈值就把早期消息压缩成摘要。 |
| 152 | // 这是与第 3 期 Memory 短期记忆压缩并行的另一道压缩——后者只压 shortTermMemory, |
| 153 | // 真正决定下一轮 LLM input token 的是这里。 |
| 154 | injectPendingLspDiagnostics(); |
| 155 | maybeCompactHistory(); |
| 156 | AgentBudget.ExitReason exitReason = budget.check(); |
| 157 | if (exitReason != AgentBudget.ExitReason.WITHIN_BUDGET) { |
| 158 | String description = budget.describeExit(exitReason); |
| 159 | log.warn("ReAct run exhausted budget: reason={}, iteration={}, tokens={}/{}", |
| 160 | exitReason, budget.iteration(), |
| 161 | budget.totalInputTokens() + budget.totalOutputTokens(), budget.tokenBudget()); |
| 162 | pushStatus(budget, startNanos, "idle"); |
| 163 | return "❌ " + description; |
| 164 | } |
| 165 | |
| 166 | int iteration = budget.beginIteration(); |
| 167 | |
| 168 | try { |
| 169 | List<LlmClient.Tool> toolDefinitions = toolRegistry.getToolDefinitions(); |
| 170 | logRequestContext("react iteration=" + iteration, toolDefinitions); |
| 171 | streamRenderer.beginThinking(); |
| 172 | // 调用 LLM |
| 173 | LlmClient.ChatResponse response = llmClient.chat( |
| 174 | conversationHistory, |
| 175 | toolDefinitions, |
| 176 | streamRenderer |