Agents

React Agent的实现方式?

想要实现ReAct Agent,有两个关键点: 1、要让LLM按照ReAct的方式运行。 2、我们需要通过代码让Agent的"思考结果"、"行动"等串起来。 ReAct Prompt 想要让你的Agent能够按照思考 + 行动 + …

TL;DR

想要实现ReAct Agent,有两个关键点: 1、要让LLM按照ReAct的方式运行。 2、我们需要通过代码让Agent的"思考结果"、"行动"等串起来。 ReAct Prompt 想要让你的Agent能够按照思考 + 行动 + …

想要实现ReAct Agent,有两个关键点: 1、要让LLM按照ReAct的方式运行。 2、我们需要通过代码让Agent的"思考结果"、"行动"等串起来。

ReAct Prompt

想要让你的Agent能够按照思考 + 行动 + 观察的思路运行,提示词必须要给出说明,这是至关重要的一步,如果没有这一步,别的做了再多都是白搭。 以下是一个典型的ReAct System Prompt :

你是一个基于React架构(Reasoning-Act-Observation)的智能助手,你擅长使用工具帮我解决问题。

你的工作流程是:
思考:基于当前获得的信息进行推理和反思,明确下一步行动的目标。
行动:用于表示需要调用的工具,每一步行动必须是以下两种之一:
  1、工具调用 [Function Calling]:根据任务需要,确定调用工具。
  2、Finish[答案]:得出明确答案后使用此操作,返回答案并终止任务。
观察:记录前一步行动的结果。

你可以进行多轮推理和检索,但必须严格按照上述格式进行操作,尤其是每一步“行动”只能使用上述两种类型之一。

按照以上提示词运行的话,一个LLM每一轮的运行输出结果有两种情况: 1、工具调用 2、Finish[答案] 如果解决问题的过程中,LLM认为还需要调用工具,则返回具体要调用的工具。如果LLM认为可以回答了,则返回 Finish+答案

ReAct Agent

通过以上Prompt约束之后,模型的输出结果,如果是要调用工具的话,那么就要继续执行,代码做工具调用,把调用结果组装给大模型,然后继续运行。 直到最终大模型输出不需要工具调用的时候,就可以返回了。那么整个运行过程就有以下流程:

while (true) {
  // 1、(Thought)大模型调用,根据大模型输出,判断是否需要工具调用,调用哪个工具,入参是什么

  if(无需工具调用) {
    break;
  }

  // 2、(Action)工具调用

  // 3、(Observation)拿到工具调用的执行结果,追加到prompt中,回到1,进行下一轮LLM调用
}

这部分需要靠代码实现的,因为我们前面讲过的,LLM不会具体调用工具,他只会告诉我们调用哪个工具和入参,所以,整个编排的过程需要靠代码实现。 openManus 接下来可以看看open manus中ReAct的实现(https://github.com/FoundationAgents/OpenManus/tree/main ): app/agent/base.py中的run方法,核心逻辑就是循环调用step方法,执行多步,直至达到最大步数或达到FINISHED状态,step方法是一个抽象方法,由BaseAgent类的子类实现:

async def run(self, request: Optional[str] = None) -> str:
        """Execute the agent's main loop asynchronously.

        Args:
            request: Optional initial user request to process.

        Returns:
            A string summarizing the execution results.

        Raises:
            RuntimeError: If the agent is not in IDLE state at start.
        """
        if self.state != AgentState.IDLE:
            raise RuntimeError(f"Cannot run agent from state: {self.state}")

        if request:
            self.update_memory("user", request)

        results: List[str] = []
        async with self.state_context(AgentState.RUNNING):
            while (
                self.current_step < self.max_steps and self.state != AgentState.FINISHED
            ):
                self.current_step += 1
                logger.info(f"Executing step {self.current_step}/{self.max_steps}")
                step_result = await self.step()

                # Check for stuck state
                if self.is_stuck():
                    self.handle_stuck_state()

                results.append(f"Step {self.current_step}: {step_result}")

            if self.current_step >= self.max_steps:
                self.current_step = 0
                self.state = AgentState.IDLE
                results.append(f"Terminated: Reached max steps ({self.max_steps})")
        await SANDBOX_CLIENT.cleanup()
        return "\n".join(results) if results else "No steps executed"

app/agent/react.py 实现了step方法,核心逻辑就是实现ReAct框架,在每步执行时,先调用think方法进行思考,再调用act方法进行行动

async def step(self) -> str:
      """Execute a single step: think and act."""
      should_act = await self.think()
      if not should_act:
          return "Thinking complete - no action needed"
      return await self.act()

app/agent/toolcall.py中实现了think方法和act方法,think方法中,基于Tool Call,将可用的工具集和历史消息一并发送给大语言模型,由大语言模型进行思考,返回需要调用的工具和参数,act方法中,根据大语言模型返回的结果,调用工具,返回工具执行的结果。 类似的实现,在前面我们提到的jd的agent框架中,实现也基本都一样,如run方法: 如果映射到java代码的话,大致流程就是这样的:

@GetMapping("/chat")
public String chat(String conversationId) {
    //定义ChatOptions
    ChatOptions chatOptions = ToolCallingChatOptions.builder()
            //指定工具
            .toolCallbacks(ToolCallbacks.from(new StockTools()))
            .build();

    //定义提示词,要求按照React架构运行
    Prompt prompt = new Prompt(
            List.of(new SystemMessage("""
                    你是一个基于React架构(Reasoning-Act-Observation)的智能助手,你擅长使用工具帮我解决问题。

                    你的工作流程是:
                    思考:基于当前获得的信息进行推理和反思,明确下一步行动的目标。
                    行动:用于表示需要调用的工具,每一步行动必须是以下两种之一:
                      1、工具调用 [Function Calling]:根据任务需要,确定调用工具。
                      2、Finish[答案]:得出明确答案后使用此操作,返回答案并终止任务。
                    观察:记录前一步行动的结果。

                    你可以进行多轮推理和检索,但必须严格按照上述格式进行操作,尤其是每一步“行动”只能使用上述两种类型之一。

                    """), new UserMessage("帮我分析最近三个月特斯拉(TSLA)的股价走势,并结合新闻事件解释可能的影响因素。")),
            chatOptions);

    //添加提示词到记忆
    chatMemory.add(conversationId, prompt.getInstructions());

    Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);

    //调用模型
    ChatResponse chatResponse = chatModel.call(promptWithMemory);

    //添加模型返回结果到记忆
    chatMemory.add(conversationId, chatResponse.getResult().getOutput());

    //循环处理工具调用
    while (!chatResponse.getResult().getOutput().getText().contains("Finish")) {
        //执行工具调用

        //添加工具调用结果到记忆

        //创建新的提示词

        //调用模型

        //添加模型返回结果到记忆
    }

    return chatResponse.getResult().getOutput().getText();
}

以上代码我并没有把所有流程都实现出来,主要是因为这个方案其实并不好,因为他需要根据模型的输出结果判断要不要继续执行action,其实我们可以利用spring ai中的功能,spring ai提供了功能,可以在模型结果中判断是否需要调模型。

Spring AI实现

但是需要注意的是,默认情况下spring ai会自动调用工具,所以我们需要把他设置为不自动调用,一个完整的react 的代码如下:

@GetMapping("/chat")
public String chat(String conversationId) {
    //定义ChatOptions
    ChatOptions chatOptions = ToolCallingChatOptions.builder()
            //指定工具
            .toolCallbacks(ToolCallbacks.from(new StockTools()))
            //指定不自动执行工具
            .internalToolExecutionEnabled(false)
            .build();

    //定义提示词,要求按照React架构运行
    Prompt prompt = new Prompt(
            List.of(new SystemMessage("你是一个基于React架构(Reasoning-Act-Observation)的智能助手,你擅长使用工具帮我解决问题。" +
                    "你的工作流程是:" +
                    "1、思考:先根据用户的提问进行思考,推理出下一步需要进行的具体系统" +
                    "2、行动:做具体的行动,这一步可以使用工具" +
                    "3、观察:记录前一步行动的结果。你可以进行多轮思考和行动。如果要使用工具,请务必调用工具,不要自己随便捏造结果。"), new UserMessage("帮我分析最近三个月特斯拉(TSLA)的股价走势,并结合新闻事件解释可能的影响因素。")),
            chatOptions);

    //添加提示词到记忆
    chatMemory.add(conversationId, prompt.getInstructions());

    Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);

    //调用模型
    ChatResponse chatResponse = chatModel.call(promptWithMemory);

    //添加模型返回结果到记忆
    chatMemory.add(conversationId, chatResponse.getResult().getOutput());

    //循环处理工具调用
    while (chatResponse.hasToolCalls()) {
        //执行工具调用
        ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(promptWithMemory,
                chatResponse);

        //添加工具调用结果到记忆
        chatMemory.add(conversationId, toolExecutionResult.conversationHistory()
                .get(toolExecutionResult.conversationHistory().size() - 1));

        //创建新的提示词
        promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);

        //调用模型
        chatResponse = chatModel.call(promptWithMemory);

        //添加模型返回结果到记忆
        chatMemory.add(conversationId, chatResponse.getResult().getOutput());
    }

    for (Message message11 : chatMemory.get(conversationId)) {
        System.out.println(message11);
    }

    return chatResponse.getResult().getOutput().getText();
}

这里首先在prompt中,移除了关于输出要求的内容,并且指定了工具toolCallbacks(ToolCallbacks.from(new StockTools())) ,也设置了工具的不自动执行internalToolExecutionEnabled(false)。 详见:https://docs.spring.io/spring-ai/reference/api/tools.html#_user_controlled_tool_execution 并且在工具执行后,LLM调用后,都把结果放到记忆中,这样LLM才能观察到之前的结果。

Spring AI Alibaba实现

在spring ai alibaba 的新版本中(>= 1.1.0.0-RC1) ,已经内置了一个React Agent,可以直接用:

@GetMapping("/chat")
public String chat(String conversationId) throws GraphRunnerException {

    String systemPrompt = String.format("你是一个基于React架构(Reasoning-Act-Observation)的智能助手,你擅长使用工具帮我解决问题。" +
            "你的工作流程是:" +
            "1、思考:先根据用户的提问进行思考,推理出下一步需要进行的具体系统" +
            "2、行动:做具体的行动,这一步可以使用工具" +
            "3、观察:记录前一步行动的结果。你可以进行多轮思考和行动。如果要使用工具,请务必调用工具,不要自己随便捏造结果。");

    ReactAgent agent = ReactAgent.builder()
            .name("executor")
            .model(chatModel)
            .tools(ToolCallbacks.from(new StockTools()))
            .systemPrompt(systemPrompt)
            .saver(new MemorySaver())
            .build();

    RunnableConfig config = RunnableConfig.builder()
            .threadId(conversationId)
            .build();

    AssistantMessage chatResponse = agent.call("帮我分析最近三个月特斯拉(TSLA)的股价走势,并结合新闻事件解释可能的影响因素。", config);

    return chatResponse.getText();
}

同样能实现类似的功能。他的原理后面单独讲。 但是需要注意的是,这个react agent只能代替我们实现代码部分的功能,即他会自动的根据是否有工具执行来判断是不是要继续执行,以及设置记忆等。但是他无法代替我们的提示词的react的指定,如果没有关于提示词的指定的话,它的输出就不稳定:

@GetMapping("/chat")
public String chat(String conversationId) throws GraphRunnerException {

    String systemPrompt = String.format("你是一个智能助手,你擅长使用工具帮我解决问题。注意:你应该先查询时间,然后再查询航班帮我解决问题");

    ReactAgent agent = ReactAgent.builder()
            .name("executor")
            .model(chatModel)
            .tools(ToolCallbacks.from(new StockTools()))
            .systemPrompt(systemPrompt)
            .saver(new MemorySaver())
            .build();

    RunnableConfig config = RunnableConfig.builder()
            .threadId(conversationId)
            .build();

    AssistantMessage chatResponse = agent.call("帮我分析最近三个月特斯拉(TSLA)的股价走势,并结合新闻事件解释可能的影响因素。", config);

    return chatResponse.getText();
}

(ModelScope的实现,我们在ModelScope章节单独介绍)

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。