001/*
002 *  Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.agentsflex.core.agent.react;
017
018import com.agentsflex.core.agent.IAgent;
019import com.agentsflex.core.message.AiMessage;
020import com.agentsflex.core.message.Message;
021import com.agentsflex.core.message.ToolCall;
022import com.agentsflex.core.model.chat.ChatModel;
023import com.agentsflex.core.model.chat.ChatOptions;
024import com.agentsflex.core.model.chat.StreamResponseListener;
025import com.agentsflex.core.model.chat.response.AiMessageResponse;
026import com.agentsflex.core.model.chat.tool.Tool;
027import com.agentsflex.core.model.chat.tool.ToolExecutor;
028import com.agentsflex.core.model.chat.tool.ToolInterceptor;
029import com.agentsflex.core.model.client.StreamContext;
030import com.agentsflex.core.prompt.MemoryPrompt;
031import com.agentsflex.core.util.StringUtil;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import java.util.ArrayList;
036import java.util.List;
037
038/**
039 * ReActAgent 是一个通用的 ReAct 模式 Agent,支持 Reasoning + Action 的交互方式。
040 */
041public class ReActAgent implements IAgent {
042
043    public static final String PARENT_AGENT_KEY = "__parent_react_agent";
044
045    private static final Logger log = LoggerFactory.getLogger(ReActAgent.class);
046
047    private static final String DEFAULT_PROMPT_TEMPLATE =
048        "你是一个 ReAct Agent,结合 Reasoning(推理)和 Action(行动)来解决问题。\n" +
049            "但在处理用户问题时,请首先判断:\n" +
050            "1. 如果问题可以通过你的常识或已有知识直接回答 → 请忽略 ReAct Agent 框架,直接输出自然语言回答。\n" +
051            "2. 如果问题需要调用特定工具才能解决(如查询、计算、获取外部信息等)→ 请严格按照 ReAct 格式响应。\n" +
052            "\n" +
053            "如果你选择使用 ReAct 模式,请遵循以下格式:\n" +
054            "Thought: 描述你对当前问题的理解,包括已知信息和缺失信息,说明你下一步将采取什么行动及其原因。\n" +
055            "Action: 从下方列出的工具中选择一个合适的工具,仅输出工具名称,不得虚构。\n" +
056            "Action Input: 使用标准 JSON 格式提供该工具所需的参数,确保字段名与工具描述一致。\n" +
057            "\n" +
058            "在 ReAct Agent 模式下,如果你已获得足够信息可以直接回答用户,请输出:\n" +
059            "Final Answer: [你的回答]\n" +
060            "\n" +
061            "如果你发现用户的问题缺少关键信息(例如时间、地点、具体目标、主体信息等),且无法通过工具获取,\n" +
062            "请主动向用户提问,格式如下:\n" +
063            "Request: [你希望用户澄清的问题]" +
064            "\n" +
065            "注意事项:\n" +
066            "1. 每次只能选择一个工具并执行一个动作。\n" +
067            "2. 在未收到工具执行结果前,不要自行假设其输出。\n" +
068            "3. 不得编造工具或参数,所有工具均列于下方。\n" +
069            "4. 输出顺序必须为:Thought → Action → Action Input。\n" +
070            "\n" +
071            "### 可用工具列表:\n" +
072            "{tools}\n" +
073            "\n" +
074            "### 用户问题如下:\n" +
075            "{user_input}";
076
077    private static final int DEFAULT_MAX_ITERATIONS = 20;
078
079    private final ChatModel chatModel;
080    private final List<Tool> tools;
081    private final ReActAgentState state;
082
083    private ReActStepParser reActStepParser = ReActStepParser.DEFAULT; // 默认解析器
084    private final MemoryPrompt memoryPrompt;
085    private ChatOptions chatOptions;
086    private ReActMessageBuilder messageBuilder = new ReActMessageBuilder();
087
088    // 监听器集合
089    private final List<ReActAgentListener> listeners = new ArrayList<>();
090
091    // 拦截器集合
092    private final List<ToolInterceptor> toolInterceptors = new ArrayList<>();
093
094
095    public ReActAgent(ChatModel chatModel, List<Tool> tools, String userQuery) {
096        this.chatModel = chatModel;
097        this.tools = tools;
098        this.state = new ReActAgentState();
099        this.state.userQuery = userQuery;
100        this.state.promptTemplate = DEFAULT_PROMPT_TEMPLATE;
101        this.state.maxIterations = DEFAULT_MAX_ITERATIONS;
102        this.memoryPrompt = new MemoryPrompt();
103    }
104
105    public ReActAgent(ChatModel chatModel, List<Tool> tools, String userQuery, MemoryPrompt memoryPrompt) {
106        this.chatModel = chatModel;
107        this.tools = tools;
108        this.state = new ReActAgentState();
109        this.state.userQuery = userQuery;
110        this.state.promptTemplate = DEFAULT_PROMPT_TEMPLATE;
111        this.state.maxIterations = DEFAULT_MAX_ITERATIONS;
112        this.memoryPrompt = memoryPrompt;
113    }
114
115    public ReActAgent(ChatModel chatModel, List<Tool> tools, ReActAgentState state) {
116        this.chatModel = chatModel;
117        this.tools = tools;
118        this.state = state;
119        this.memoryPrompt = new MemoryPrompt();
120        if (state.messageHistory != null) {
121            this.memoryPrompt.addMessages(state.messageHistory);
122        }
123    }
124
125    /**
126     * 注册监听器
127     */
128    public void addListener(ReActAgentListener listener) {
129        listeners.add(listener);
130    }
131
132    /**
133     * 移除监听器
134     */
135    public void removeListener(ReActAgentListener listener) {
136        listeners.remove(listener);
137    }
138
139    public void addToolInterceptor(ToolInterceptor interceptor) {
140        toolInterceptors.add(interceptor);
141    }
142
143    public List<ToolInterceptor> getToolInterceptors() {
144        return toolInterceptors;
145    }
146
147    public ChatModel getChatModel() {
148        return chatModel;
149    }
150
151    public List<Tool> getTools() {
152        return tools;
153    }
154
155    public ReActStepParser getReActStepParser() {
156        return reActStepParser;
157    }
158
159    public void setReActStepParser(ReActStepParser reActStepParser) {
160        this.reActStepParser = reActStepParser;
161    }
162
163    public List<ReActAgentListener> getListeners() {
164        return listeners;
165    }
166
167    public boolean isStreamable() {
168        return this.state.streamable;
169    }
170
171    public void setStreamable(boolean streamable) {
172        this.state.streamable = streamable;
173    }
174
175
176    public MemoryPrompt getMemoryPrompt() {
177        return memoryPrompt;
178    }
179
180    public ReActMessageBuilder getMessageBuilder() {
181        return messageBuilder;
182    }
183
184    public void setMessageBuilder(ReActMessageBuilder messageBuilder) {
185        this.messageBuilder = messageBuilder;
186    }
187
188    public ChatOptions getChatOptions() {
189        return chatOptions;
190    }
191
192    public void setChatOptions(ChatOptions chatOptions) {
193        this.chatOptions = chatOptions;
194    }
195
196    public ReActAgentState getState() {
197        state.messageHistory = memoryPrompt.getMessages();
198        return state;
199    }
200
201    /**
202     * 运行 ReAct Agent 流程
203     */
204    @Override
205    public void execute() {
206        try {
207            List<Message> messageHistory = state.getMessageHistory();
208            if (messageHistory == null || messageHistory.isEmpty()) {
209                String toolsDescription = Util.buildToolsDescription(tools);
210                String prompt = state.promptTemplate
211                    .replace("{tools}", toolsDescription)
212                    .replace("{user_input}", state.userQuery);
213
214                Message message = messageBuilder.buildStartMessage(prompt, tools, state.userQuery);
215                memoryPrompt.addMessage(message);
216            }
217            if (this.isStreamable()) {
218                startNextReActStepStream();
219            } else {
220                startNextReactStepNormal();
221            }
222        } catch (Exception e) {
223            log.error("运行 ReAct Agent 出错:" + e);
224            notifyOnError(e);
225        }
226    }
227
228    private void startNextReactStepNormal() {
229        while (state.iterationCount < state.maxIterations) {
230
231            state.iterationCount++;
232
233            AiMessageResponse response = chatModel.chat(memoryPrompt, chatOptions);
234            notifyOnChatResponse(response);
235
236            String content = response.getMessage().getContent();
237            AiMessage message = new AiMessage(content);
238
239            // 请求用户输入
240            if (isRequestUserInput(content)) {
241                String question = extractRequestQuestion(content);
242                message.addMetadata("type", "reActRequest");
243                memoryPrompt.addMessage(message);
244                notifyOnRequestUserInput(question); // 新增监听器回调
245                break; // 暂停执行,等待用户回复
246            }
247            //  ReAct 动作
248            else if (isReActAction(content)) {
249                message.addMetadata("type", "reActAction");
250                memoryPrompt.addMessage(message);
251                if (!processReActSteps(content)) {
252                    break;
253                }
254            }
255
256            // 最终答案
257            else if (isFinalAnswer(content)) {
258                String flag = reActStepParser.getFinalAnswerFlag();
259                String answer = content.substring(content.indexOf(flag) + flag.length());
260                message.addMetadata("type", "reActFinalAnswer");
261                memoryPrompt.addMessage(message);
262                notifyOnFinalAnswer(answer);
263                break;
264            }
265            //  不是 Action
266            else {
267                memoryPrompt.addMessage(message);
268                notifyOnNonActionResponse(response);
269                break;
270            }
271        }
272
273        // 显式通知达到最大迭代
274        if (state.iterationCount >= state.maxIterations) {
275            notifyOnMaxIterationsReached();
276        }
277    }
278
279
280    private void startNextReActStepStream() {
281        if (state.iterationCount >= state.maxIterations) {
282            notifyOnMaxIterationsReached();
283            return;
284        }
285
286        state.iterationCount++;
287
288        chatModel.chatStream(memoryPrompt, new StreamResponseListener() {
289
290            @Override
291            public void onMessage(StreamContext context, AiMessageResponse response) {
292                notifyOnChatResponseStream(context, response);
293            }
294
295            @Override
296            public void onStop(StreamContext context) {
297                AiMessage lastAiMessage = context.getFullMessage();
298                if (lastAiMessage == null) {
299                    notifyOnError(new RuntimeException("没有收到任何回复"));
300                    return;
301                }
302
303                String content = lastAiMessage.getFullContent();
304                if (StringUtil.noText(content)) {
305                    notifyOnError(new RuntimeException("没有收到任何回复"));
306                    return;
307                }
308
309                AiMessage message = new AiMessage(content);
310
311                // 请求用户输入
312                if (isRequestUserInput(content)) {
313                    String question = extractRequestQuestion(content);
314                    message.addMetadata("type", "reActRequest");
315                    memoryPrompt.addMessage(message);
316                    notifyOnRequestUserInput(question); // 新增监听器回调
317                }
318
319                //  ReAct 动作
320                else if (isReActAction(content)) {
321                    message.addMetadata("type", "reActAction");
322                    memoryPrompt.addMessage(message);
323                    if (processReActSteps(content)) {
324                        // 递归继续执行下一个 ReAct 步骤
325                        startNextReActStepStream();
326                    }
327                }
328
329                // 最终答案
330                else if (isFinalAnswer(content)) {
331                    message.addMetadata("type", "reActFinalAnswer");
332                    memoryPrompt.addMessage(message);
333                    String flag = reActStepParser.getFinalAnswerFlag();
334                    String answer = content.substring(content.indexOf(flag) + flag.length());
335                    notifyOnFinalAnswer(answer);
336                } else {
337                    memoryPrompt.addMessage(message);
338                    //  不是 Action
339                    notifyOnNonActionResponseStream(context);
340                }
341            }
342
343            @Override
344            public void onFailure(StreamContext context, Throwable throwable) {
345                notifyOnError((Exception) throwable);
346            }
347        }, chatOptions);
348    }
349
350
351    private boolean isFinalAnswer(String content) {
352        return reActStepParser.isFinalAnswer(content);
353    }
354
355    private boolean isReActAction(String content) {
356        return reActStepParser.isReActAction(content);
357    }
358
359    private boolean isRequestUserInput(String content) {
360        return reActStepParser.isRequest(content);
361    }
362
363    private String extractRequestQuestion(String content) {
364        return reActStepParser.extractRequestQuestion(content);
365    }
366
367
368    private boolean processReActSteps(String content) {
369        List<ReActStep> reActSteps = reActStepParser.parse(content);
370        if (reActSteps.isEmpty()) {
371            notifyOnStepParseError(content);
372            return false;
373        }
374
375        for (ReActStep step : reActSteps) {
376            boolean stepExecuted = false;
377            for (Tool tool : tools) {
378                if (tool.getName().equals(step.getAction())) {
379                    try {
380                        notifyOnActionStart(step);
381
382                        Object result = null;
383                        try {
384                            ToolCall toolCall = new ToolCall();
385                            toolCall.setId("react_call_" + state.iterationCount + "_" + System.currentTimeMillis());
386                            toolCall.setName(step.getAction());
387                            toolCall.setArguments(step.getActionInput());
388
389                            ToolExecutor executor = new ToolExecutor(tool, toolCall, toolInterceptors);
390
391                            // 方便 “子Agent” 或者 tool, 获取当前的 ReActAgent
392                            executor.addInterceptor((context, chain) -> {
393                                context.setAttribute(PARENT_AGENT_KEY, ReActAgent.this);
394                                return chain.proceed(context);
395                            });
396
397                            result = executor.execute();
398                        } catch (Exception e) {
399                            throw new RuntimeException(e);
400                        } finally {
401                            notifyOnActionEnd(step, result);
402                        }
403
404                        Message message = messageBuilder.buildObservationMessage(step, result);
405                        memoryPrompt.addMessage(message);
406                        stepExecuted = true;
407                    } catch (Exception e) {
408                        log.error(e.toString(), e);
409                        notifyOnActionInvokeError(e);
410
411                        if (!state.continueOnActionInvokeError) {
412                            return false;
413                        }
414
415                        Message message = messageBuilder.buildActionErrorMessage(step, e);
416                        memoryPrompt.addMessage(message);
417                        return true;
418                    }
419                    break;
420                }
421            }
422
423            if (!stepExecuted) {
424                notifyOnActionNotMatched(step, tools);
425                return false;
426            }
427        }
428
429
430        return true;
431    }
432
433
434    // ========== 通知监听器的方法 ==========
435    private void notifyOnChatResponse(AiMessageResponse response) {
436        for (ReActAgentListener listener : listeners) {
437            try {
438                listener.onChatResponse(response);
439            } catch (Exception e) {
440                log.error(e.toString(), e);
441            }
442        }
443    }
444
445    private void notifyOnNonActionResponse(AiMessageResponse response) {
446        for (ReActAgentListener listener : listeners) {
447            try {
448                listener.onNonActionResponse(response);
449            } catch (Exception e) {
450                log.error(e.toString(), e);
451            }
452        }
453    }
454
455    private void notifyOnNonActionResponseStream(StreamContext context) {
456        for (ReActAgentListener listener : listeners) {
457            try {
458                listener.onNonActionResponseStream(context);
459            } catch (Exception e) {
460                log.error(e.toString(), e);
461            }
462        }
463    }
464
465    private void notifyOnChatResponseStream(StreamContext context, AiMessageResponse response) {
466        for (ReActAgentListener listener : listeners) {
467            try {
468                listener.onChatResponseStream(context, response);
469            } catch (Exception e) {
470                log.error(e.toString(), e);
471            }
472        }
473    }
474
475    private void notifyOnFinalAnswer(String finalAnswer) {
476        for (ReActAgentListener listener : listeners) {
477            try {
478                listener.onFinalAnswer(finalAnswer);
479            } catch (Exception e) {
480                log.error(e.toString(), e);
481            }
482        }
483    }
484
485    private void notifyOnRequestUserInput(String question) {
486        for (ReActAgentListener listener : listeners) {
487            try {
488                listener.onRequestUserInput(question);
489            } catch (Exception e) {
490                log.error(e.toString(), e);
491            }
492        }
493    }
494
495    private void notifyOnActionStart(ReActStep reActStep) {
496        for (ReActAgentListener listener : listeners) {
497            try {
498                listener.onActionStart(reActStep);
499            } catch (Exception e) {
500                log.error(e.toString(), e);
501            }
502        }
503    }
504
505    private void notifyOnActionEnd(ReActStep reActStep, Object result) {
506        for (ReActAgentListener listener : listeners) {
507            try {
508                listener.onActionEnd(reActStep, result);
509            } catch (Exception e) {
510                log.error(e.toString(), e);
511            }
512        }
513    }
514
515    private void notifyOnMaxIterationsReached() {
516        for (ReActAgentListener listener : listeners) {
517            try {
518                listener.onMaxIterationsReached();
519            } catch (Exception e) {
520                log.error(e.toString(), e);
521            }
522        }
523    }
524
525    private void notifyOnStepParseError(String content) {
526        for (ReActAgentListener listener : listeners) {
527            try {
528                listener.onStepParseError(content);
529            } catch (Exception e) {
530                log.error(e.toString(), e);
531            }
532        }
533    }
534
535    private void notifyOnActionNotMatched(ReActStep step, List<Tool> tools) {
536        for (ReActAgentListener listener : listeners) {
537            try {
538                listener.onActionNotMatched(step, tools);
539            } catch (Exception e) {
540                log.error(e.toString(), e);
541            }
542        }
543    }
544
545    private void notifyOnActionInvokeError(Exception e) {
546        for (ReActAgentListener listener : listeners) {
547            try {
548                listener.onActionInvokeError(e);
549            } catch (Exception e1) {
550                log.error(e.toString(), e1);
551            }
552        }
553    }
554
555    private void notifyOnError(Exception e) {
556        for (ReActAgentListener listener : listeners) {
557            try {
558                listener.onError(e);
559            } catch (Exception e1) {
560                log.error(e.toString(), e1);
561            }
562        }
563    }
564}