Agent 设计模式系列

Agent 设计模式 10:ReAct:推理、行动与工具调用

通过计算、天气和订单示例阅读工具调用与会话上下文,区分真实计算和预设业务返回。

本讲离开招聘场景,介绍 ReAct(推理与行动)模式:模型结合当前上下文决定下一步,再通过工具获取结果,继续处理问题。下面先看原文的模式示意图。

ReAct 推理与行动循环

原文配图:ReAct 推理与行动循环。

原文用“思考—行动”的循环来解释这一模式:针对当前问题选择行动,必要时调用工具,再根据结果继续处理后续问题。

定义 ReAct Agent

public interface ReActAssistant {
    @SystemMessage("""
            你是一个使用ReAct(Reasoning and Acting)模式的智能助手。
            请按照以下步骤思考:
            1. 理解用户的问题
            2. 思考解决问题需要什么信息
            3. 如果需要计算或查询,选择合适的工具
            4. 使用工具获取结果
            5. 基于结果给出最终答案
            
            请用中文思考和回答。
            当使用工具时,明确说明你要使用的工具。
            """)
    @UserMessage("问:{{request}}")
    @Agent("基于用户提供的问题进行思考和回答")
    String chat(@V("request") String request);

}

接口通过系统提示词约定推理与行动的循环,下面将它接入应用。

接入应用与工具

@SpringBootApplication
public class ReActAgentApplication {

    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext context = SpringApplication.run(AgentDesignPatternApplication.class, args);
        ChatModel model = context.getBean("ollamaChatModel", ChatModel.class);
        SampleTools sampleTools = context.getBean("sampleTools", SampleTools.class);

        ReActAssistant agent = AgenticServices
                .agentBuilder(ReActAssistant.class)
                .chatModel(model)
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(15))
                .tools(sampleTools)
                .build();


        String[] testQueries = {
                "计算 15 加上 27 等于多少?",
                "北京现在的天气怎么样?",
                "计算半径为5的圆的面积",
                "现在是几点?",
                "计算长方体的体积,长10,宽5,高3",
                "帮我算一下 (25 × 4) ÷ 2 等于多少?",
                "快递单123456,现在到哪了?",
                "我的订单56789,退款到账了没?"
        };

        for (String query : testQueries) {
            System.out.println("问: " + query);
            try {
                String response = agent.chat(query);
                System.out.println("答: " + response);
                System.out.println("-".repeat(50));
                // 避免请求过快
                Thread.sleep(1000);
            } catch (Exception e) {
                System.err.println("查询失败: " + e.getMessage());
            }
        }

    }
}

这些问题会用到工具。下面的 SampleTools 包含普通计算和时间读取;天气、快递、退款查询返回示例数据,没有接入真实业务查询服务。

/**
 * @author junmingyang
 */
@Component("sampleTools")
public class SampleTools {

    @Tool("计算两个数的加法运算")
    public String add(double a, double b) {
        double result = a + b;
        System.out.printf("[工具调用] 加法: %.2f + %.2f = %.2f\n", a, b, result);
        return String.format("%.2f + %.2f = %.2f", a, b, result);
    }

    @Tool("计算两个数的减法运算")
    public String subtract(double a, double b) {
        double result = a - b;
        System.out.printf("[工具调用] 减法: %.2f - %.2f = %.2f\n", a, b, result);
        return String.format("%.2f - %.2f = %.2f", a, b, result);
    }

    @Tool("计算两个数的乘法运算")
    public String multiply(double a, double b) {
        double result = a * b;
        System.out.printf("[工具调用] 乘法: %.2f × %.2f = %.2f\n", a, b, result);
        return String.format("%.2f × %.2f = %.2f", a, b, result);
    }

    @Tool("计算两个数的除法运算")
    public String divide(double a, double b) {
        if (b == 0) {
            return "错误:除数不能为零";
        }
        double result = a / b;
        System.out.printf("[工具调用] 除法: %.2f ÷ %.2f = %.2f\n", a, b, result);
        return String.format("%.2f ÷ %.2f = %.2f", a, b, result);
    }

    @Tool("获取当前日期和时间")
    public String getCurrentDateTime() {
        String datetime = LocalDateTime.now().format(
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
        );
        System.out.println("[工具调用] 当前时间: " + datetime);
        return datetime;
    }

    @Tool("查询指定城市的天气信息")
    public String getWeather(String city) {
        System.out.println("[工具调用] 查询天气: " + city);
        // 这里可以集成真实的天气API
        return String.format("%s的天气:晴转多云,温度22-28°C,湿度65%%,东南风2级", city);
    }

    @Tool("计算圆的面积")
    public String calculateCircleArea(double radius) {
        double area = Math.PI * radius * radius;
        System.out.printf("[工具调用] 圆面积计算: 半径=%.2f, 面积=%.2f\n", radius, area);
        return String.format("半径为 %.2f 的圆的面积是 %.2f", radius, area);
    }

    @Tool("查询快递单")
    public String queryExpressOrder(String expressOrderNo) {
        System.out.printf("[工具调用] 快递单: %s,已经在运输途中,预订明天送达\n",
                expressOrderNo);
        return String.format("[工具调用] 快递单: %s,已经在运输途中,预订明天送达\n",
                expressOrderNo);
    }

    @Tool("查询退款进度")
    public String queryRefundProgress(String orderNo) {
        System.out.printf("[工具调用] 订单: %s,退款已审批通过,预计1-3个工作日按原路退回\n",
                orderNo);
        return String.format("[工具调用] 订单: %s,退款已审批通过,预计1-3个工作日按原路退回\n",
                orderNo);
    }
}

原文运行输出

展开原文日志或请求/响应(未复测)
问: 计算 15 加上 27 等于多少?
[工具调用] 加法: 15.00 + 27.00 = 42.00
答: 计算结果是:15 + 27 = 42。
--------------------------------------------------
问: 北京现在的天气怎么样?
[工具调用] 查询天气: 北京
答: 北京当前的天气情况是:
- 天气状况:晴转多云
- 温度:22-28°C
- 湿度:65%
- 风力:东南风2级

总体来说天气比较舒适,适合户外活动。
--------------------------------------------------
问: 计算半径为5的圆的面积
[工具调用] 圆面积计算: 半径=5.00, 面积=78.54
答: 半径为5的圆的面积是 **78.54**(平方单位)。

圆的面积计算公式为:π × 半径² = π × 5² = π × 25 ≈ 78.54
--------------------------------------------------
问: 现在是几点?
[工具调用] 当前时间: 2026-01-31 19:47:39
答: 现在的时间是:2026年1月31日 19:47:39
--------------------------------------------------
问: 计算长方体的体积,长10,宽5,高3
[工具调用] 乘法: 10.00 × 5.00 = 50.00
[工具调用] 乘法: 50.00 × 3.00 = 150.00
答: 长方体的体积是 **150**(立方单位)。

长方体体积计算公式:长 × 宽 × 高 = 10 × 5 × 3 = 150
--------------------------------------------------
问: 帮我算一下 (25 × 4) ÷ 2 等于多少?
[工具调用] 乘法: 25.00 × 4.00 = 100.00
[工具调用] 除法: 100.00 ÷ 2.00 = 50.00
答: (25 × 4) ÷ 2 = **50**

计算过程:
- 25 × 4 = 100
- 100 ÷ 2 = 50
--------------------------------------------------
问: 快递单123456,现在到哪了?
[工具调用] 快递单: 123456,已经在运输途中,预订明天送达
答: 您的快递单123456目前状态:
- **物流状态**:已经在运输途中
- **预计送达时间**:明天送达

快递正在正常运输中,请耐心等待。
--------------------------------------------------
问: 我的订单56789,退款到账了没?
[工具调用] 订单: 56789,退款已审批通过,预计1-3个工作日按原路退回
答: 您的订单56789退款状态:
- **退款状态**:退款已审批通过
- **预计到账时间**:1-3个工作日内按原路退回

退款申请已经通过审核,资金将在1-3个工作日内原路返回到您的支付账户,请注意查收。
--------------------------------------------------

上面的输出省略了模型交互的请求与响应。要对照工具选择过程,可以继续查看原文保留的最后一个问题的请求:

展开原文日志或请求/响应(未复测)
{
  "model": "minimax-m2:cloud",
  "messages": [
    {
      "role": "system",
      "content": "你是一个使用ReAct(Reasoning and Acting)模式的智能助手。\n请按照以下步骤思考:\n1. 理解用户的问题\n2. 思考解决问题需要什么信息\n3. 如果需要计算或查询,选择合适的工具\n4. 使用工具获取结果\n5. 基于结果给出最终答案\n\n请用中文思考和回答。\n当使用工具时,明确说明你要使用的工具。\n"
    },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "function": {
            "name": "multiply",
            "arguments": {
              "a": 50,
              "b": 3
            }
          }
        }
      ]
    },
    {
      "role": "tool",
      "content": "50.00 × 3.00 = 150.00"
    },
    {
      "role": "assistant",
      "content": "长方体的体积是 **150** 立方单位。\n\n计算过程:\n- 长方体体积 = 长 × 宽 × 高\n- 10 × 5 × 3 = 150\n\n所以这个长方体的体积为150立方单位。",
      "tool_calls": []
    },
    {
      "role": "user",
      "content": "问:帮我算一下 (25 × 4) ÷ 2 等于多少?"
    },
    {
      "role": "assistant",
      "content": "我来为您计算 (25 × 4) ÷ 2。",
      "tool_calls": [
        {
          "function": {
            "name": "multiply",
            "arguments": {
              "a": 25,
              "b": 4
            }
          }
        }
      ]
    },
    {
      "role": "tool",
      "content": "25.00 × 4.00 = 100.00"
    },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "function": {
            "name": "divide",
            "arguments": {
              "a": 100,
              "b": 2
            }
          }
        }
      ]
    },
    {
      "role": "tool",
      "content": "100.00 ÷ 2.00 = 50.00"
    },
    {
      "role": "assistant",
      "content": "(25 × 4) ÷ 2 = **50**\n\n计算过程:\n- 先算 25 × 4 = 100\n- 再算 100 ÷ 2 = 50\n\n所以答案是 50。",
      "tool_calls": []
    },
    {
      "role": "user",
      "content": "问:快递单123456,现在到哪了?"
    },
    {
      "role": "assistant",
      "content": "我来帮您查询快递单123456的状态。",
      "tool_calls": [
        {
          "function": {
            "name": "queryExpressOrder",
            "arguments": {
              "expressOrderNo": "123456"
            }
          }
        }
      ]
    },
    {
      "role": "tool",
      "content": "[工具调用] 快递单: 123456,已经在运输途中,预订明天送达\n"
    },
    {
      "role": "assistant",
      "content": "您的快递单123456目前状态:**已经在运输途中**,预计**明天送达**。\n\n快递正在正常运输中,请耐心等待。",
      "tool_calls": []
    },
    {
      "role": "user",
      "content": "问:我的订单56789,退款到账了没?"
    }
  ],
  "options": {
    "stop": []
  },
  "stream": false,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "getWeather",
        "description": "查询指定城市的天气信息",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string"
            }
          },
          "required": [
            "city"
          ]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "queryExpressOrder",
        "description": "查询快递单",
        "parameters": {
          "type": "object",
          "properties": {
            "expressOrderNo": {
              "type": "string"
            }
          },
          "required": [
            "expressOrderNo"
          ]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "queryRefundProgress",
        "description": "查询退款进度",
        "parameters": {
          "type": "object",
          "properties": {
            "orderNo": {
              "type": "string"
            }
          },
          "required": [
            "orderNo"
          ]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "calculateCircleArea",
        "description": "计算圆的面积",
        "parameters": {
          "type": "object",
          "properties": {
            "radius": {
              "type": "number"
            }
          },
          "required": [
            "radius"
          ]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "getCurrentDateTime",
        "description": "获取当前日期和时间"
      }
    },
    {
      "type": "function",
      "function": {
        "name": "add",
        "description": "计算两个数的加法运算",
        "parameters": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number"
            },
            "b": {
              "type": "number"
            }
          },
          "required": [
            "a",
            "b"
          ]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "multiply",
        "description": "计算两个数的乘法运算",
        "parameters": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number"
            },
            "b": {
              "type": "number"
            }
          },
          "required": [
            "a",
            "b"
          ]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "subtract",
        "description": "计算两个数的减法运算",
        "parameters": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number"
            },
            "b": {
              "type": "number"
            }
          },
          "required": [
            "a",
            "b"
          ]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "divide",
        "description": "计算两个数的除法运算",
        "parameters": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number"
            },
            "b": {
              "type": "number"
            }
          },
          "required": [
            "a",
            "b"
          ]
        }
      }
    }
  ]
}

原文请求的第 106 行进入最后一个问题;第 113—281 行提供可用工具,第 8—103 行包含前面的问答。这里的会话上下文来自随请求发送的历史消息。

原文模型响应

展开原文日志或请求/响应(未复测)
{
  "model": "minimax-m2:cloud",
  "remote_model": "minimax-m2",
  "remote_host": "https://ollama.com:443",
  "created_at": "2026-01-31T11:52:21.93218197Z",
  "message": {
    "role": "assistant",
    "content": "我来帮您查询订单56789的退款进度。",
    "thinking": "用户询问订单56789的退款进度,我需要使用queryRefundProgress工具来查询这个订单号的退款情况。",
    "tool_calls": [
      {
        "id": "call_dmgzch57",
        "function": {
          "index": 0,
          "name": "queryRefundProgress",
          "arguments": {
            "orderNo": "56789"
          }
        }
      }
    ]
  },
  "done": true,
  "done_reason": "stop",
  "total_duration": 816309591,
  "prompt_eval_count": 1023,
  "eval_count": 63
}

原文响应第 9 行选择了 queryRefundProgress 工具。

原文时序图

ReAct:推理、行动与工具调用:简化时序图

原文配图:ReAct:推理、行动与工具调用:简化时序图。