在前四篇中,我们实现了Agent的工具调用、输出和耗时统计,但核心逻辑仍偏「指令→执行」的单向流程。本篇基于ReAct(Reasoning + Acting)架构,让Agent具备「思考→决策→行动→反馈→再思考」的闭环能力——就像人类解决问题一样,先分析问题、规划步骤,再执行操作,最后根据结果修正思路。
一、ReAct架构核心:让模型「有思考过程」
1. ReAct是什么?
ReAct不是单一技术,而是一种「让大模型边推理边行动」的范式:
- • Reasoning(推理):模型先分析用户指令,明确「要做什么、怎么做、调用什么工具」,并把思考过程说出来;
- • Acting(行动):根据推理结果调用工具执行具体操作;
- • 闭环:工具返回结果后,模型再根据结果判断「是否完成任务/是否需要继续行动」。
2. 为什么需要ReAct?
- • 无ReAct:模型直接调用工具(黑盒操作,出错难排查);
- • 有ReAct:模型先输出思考过程(如「用户要算150+250,需要调用calculator工具,参数是150 + 250」),再执行操作,既直观又便于调试。
3. 最佳示例选择
选择「多步骤数学应用题」作为示例(而非简单的单步计算),能充分体现ReAct的价值:
示例指令:「小明有150元零花钱,妈妈又给了250元,他花了80元买文具,最后还剩多少钱?」
无ReAct:模型可能直接算错(如150+250-80=320,但无过程);
有ReAct:模型会分步思考→先算150+250=400,再算400-80=320,每一步都有推理+行动。
二、完整ReAct架构Agent Demo(多步骤数学应用题)
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/sashabaranov/go-openai"
)
// ==================== 1. Agent 核心结构体 ====================
// Agent 保存 ReAct 运行时的状态、思考过程与记忆。
type Agent struct {
UserPrompt string // 用户自然语言指令(多步骤数学应用题)
ThoughtHistory []string // ReAct 每轮的思考记录
Memory string // 工具执行结果记忆
FinalAnswer string // 最终输出答案
TotalLatency int64 // 总耗时(毫秒)
}
// ==================== 2. 四则运算工具(Agent 的「手脚」) ====================
// parseCalculation 从文本解析「数字1 运算符 数字2」。
func parseCalculation(text string) (num1 float64, op rune, num2 float64, err error) {
_, err = fmt.Sscanf(strings.TrimSpace(text), "%f %c %f", &num1, &op, &num2)
return num1, op, num2, err
}
// calculator 执行加减乘除四则运算,返回可读的计算结果字符串。
func calculator(expression string) string {
num1, op, num2, err := parseCalculation(expression)
if err != nil {
return fmt.Sprintf("解析失败:%v", err)
}
var res float64
switch op {
case '+':
res = num1 + num2
case '-':
res = num1 - num2
case '*':
res = num1 * num2
case '/':
if num2 == 0 {
return "错误:除数不能为0"
}
res = num1 / num2
default:
return fmt.Sprintf("不支持运算符:%c", op)
}
return fmt.Sprintf("%.2f %c %.2f = %.2f", num1, op, num2, res)
}
// ==================== 3. 工具定义:给 LLM 看的工具清单 ====================
// reactTools 声明 Agent 可用的工具,LLM 据此自主拆解多步骤任务并调用。
var reactTools = []openai.Tool{
{
Type: openai.ToolTypeFunction,
Function: &openai.FunctionDefinition{
Name: "calculator",
Description: "四则运算计算器,支持加减乘除,用于分步解决数学应用题,每次仅计算一步",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"expression": map[string]interface{}{
"type": "string",
"description": "单步数学表达式,格式:数字 + 空格 + 运算符 + 空格 + 数字(如 150 + 250)",
},
},
"required": []string{"expression"},
},
},
},
}
// ==================== 4. ReAct System Prompt ====================
// reactSystemPrompt 是 ReAct 循环的 System 指令,强制 LLM 每轮只走一步。
const reactSystemPrompt = `你是一个使用 ReAct(思考-行动-观察)范式解题的 Agent。
面对多步骤数学应用题,你需要在每轮回复中做以下两件事之一:
1. 思考并行动:
- 在 content 中输出「思考:[分析当前一步]」
- 通过 tool_calls 调用 calculator 工具计算这一步
2. 给出最终答案:
- 当所有步骤都已计算完毕,不要再调用任何工具
- 在 content 中输出「答案:[最终结果]」
规则:
- 每次工具调用只计算一步(如 150 + 250),禁止在一次调用中混合多步运算;
- 你会收到 tool 角色的工具结果,请基于结果继续思考下一步是否需要;
- 全部计算完成后,必须停止调用工具,直接给出最终答案。`
// ==================== 5. 单轮 LLM 调用(Agent 的「大脑」) ====================
// callLLM 调用云端大模型一次,返回 LLM 的完整消息(含思考内容与工具调用指令)。
func (a *Agent) callLLM(client *openai.Client, messages []openai.ChatCompletionMessage) (openai.ChatCompletionMessage, error) {
resp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
Model: "deepseek-v4-flash", // DeepSeek 官方模型名,第三方模型替换对应名称
Messages: messages,
Tools: reactTools,
Temperature: 0.1, // 低温度,保证推理精准
})
if err != nil {
return openai.ChatCompletionMessage{}, fmt.Errorf("LLM 调用失败:%w", err)
}
if len(resp.Choices) == 0 {
return openai.ChatCompletionMessage{}, fmt.Errorf("LLM 未返回任何结果")
}
return resp.Choices[0].Message, nil
}
// ==================== 6. 工具执行分发 ====================
// executeTool 根据工具调用指令执行对应工具,返回结果字符串。
func executeTool(toolCall openai.ToolCall) string {
if toolCall.Function.Name != "calculator" {
return fmt.Sprintf("未知工具:%s", toolCall.Function.Name)
}
// 解析参数:{"expression": "150 + 250"}
var cal struct {
Expression string `json:"expression"`
}
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &cal); err != nil {
return fmt.Sprintf("参数解析失败:%v", err)
}
return calculator(cal.Expression)
}
// ==================== 7. ReAct 核心循环:思考 → 行动 → 观察 → 再思考 ====================
// maxReactSteps 限制 ReAct 最大循环次数,防止 LLM 死循环。
const maxReactSteps = 5
// RunReAct 运行真正的 ReAct 循环:
// 每轮思考(调 LLM)→ 若调用工具则执行(行动)→ 结果回传 LLM(观察)→ 继续推理,
// 直到 LLM 不再调用工具(给出最终答案)或达到最大步数。
func (a *Agent) RunReAct() {
fmt.Println("===== ReAct Agent 启动 =====")
fmt.Printf("用户指令:%s\n\n", a.UserPrompt)
// 初始化客户端
apikey := os.Getenv("DEEPSEEK_API_KEY")
if apikey == "" {
a.FinalAnswer = "DEEPSEEK_API_KEY 环境变量未设置"
return
}
cfg := openai.DefaultConfig(apikey)
cfg.BaseURL = "https://api.deepseek.com"
client := openai.NewClientWithConfig(cfg)
// 初始化消息:System Prompt + 用户指令
messages := []openai.ChatCompletionMessage{
{Role: openai.ChatMessageRoleSystem, Content: reactSystemPrompt},
{Role: openai.ChatMessageRoleUser, Content: a.UserPrompt},
}
start := time.Now()
// ReAct 核心循环
for step := 1; step <= maxReactSteps; step++ {
fmt.Printf("----- 第 %d 步 -----\n", step)
// 1. 思考:调用 LLM
msg, err := a.callLLM(client, messages)
if err != nil {
a.FinalAnswer = fmt.Sprintf("ReAct 执行失败:%v", err)
return
}
// 打印并记录本轮思考
if msg.Content != "" {
fmt.Println(msg.Content)
a.ThoughtHistory = append(a.ThoughtHistory, msg.Content)
}
// 2. 判断是否结束:LLM 未调用工具 → 已给出最终答案
if len(msg.ToolCalls) == 0 {
// a.Memory += msg.Content
fmt.Println("\n【ReAct 完成】LLM 未再调用工具,任务结束")
break
}
// 3. 行动:把 assistant 的工具调用决策追加到消息历史
messages = append(messages, msg)
// 执行工具调用,并把结果以 tool 消息回传 LLM(观察阶段)
for _, toolCall := range msg.ToolCalls {
result := executeTool(toolCall)
fmt.Printf(" 工具[%s] 结果:%s\n", toolCall.Function.Name, result)
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleTool,
Content: result,
ToolCallID: toolCall.ID,
})
if a.Memory != "" {
a.Memory += ";"
}
a.Memory += result
}
// 达到最大步数仍未完成
if step == maxReactSteps {
fmt.Printf("\n【ReAct 警告】达到最大步数 %d,强制结束\n", maxReactSteps)
}
}
// 统计耗时并生成最终答案
a.TotalLatency = time.Since(start).Milliseconds()
thoughtStr := strings.Join(a.ThoughtHistory, "\n")
if thoughtStr == "" {
thoughtStr = "无结构化思考过程"
}
a.FinalAnswer = fmt.Sprintf(
"✅ ReAct 解题完成\n\n思考过程:\n%s\n\n计算步骤:\n%s\n\n总耗时:%dms",
thoughtStr, a.Memory, a.TotalLatency,
)
}
// ==================== 主函数:测试 ReAct Agent ====================
func main() {
// 测试用例:多步骤数学应用题(ReAct 核心示例,需多步计算)
agent := &Agent{
UserPrompt: "小明有150元零花钱,妈妈又给了250元,他花了80元买文具,最后还剩多少钱?",
}
agent.RunReAct()
fmt.Println("\n===== ReAct Agent 最终输出 =====")
fmt.Println(agent.FinalAnswer)
}
三、ReAct核心逻辑解析
1. ReAct Prompt设计(关键)
System Prompt强制模型遵守「思考→行动→反馈」规则:
- • 思考阶段:要求模型用「思考:」开头拆解问题(如「思考:首先需要计算小明总共有多少零花钱,即150 + 250」);
- • 行动阶段:要求模型调用
calculator工具执行单步计算; - • 反馈阶段:要求模型根据工具结果判断是否继续计算。
2. 思考过程捕获
通过输出捕获模型的思考内容,并记录到ThoughtHistory字段:
if msg.Content != "" {
fmt.Println(msg.Content)
a.ThoughtHistory = append(a.ThoughtHistory, msg.Content)
}3. 多步骤闭环
示例中实现了「两步计算」的闭环:
- 1. 第一步:计算150+250=400(总零花钱);
- 2. 第二步:用第一步结果计算400-80=320(剩余零花钱);
- 3. 最终整合所有步骤,输出完整解题过程。
四、运行效果(ReAct「边思考边行动」)
===== ReAct Agent 启动 =====
用户指令:小明有150元零花钱,妈妈又给了250元,他花了80元买文具,最后还剩多少钱?
----- 第 1 步 -----
工具[calculator] 结果:150.00 + 250.00 = 400.00
----- 第 2 步 -----
工具[calculator] 结果:400.00 - 80.00 = 320.00
----- 第 3 步 -----
答案:小明最后还剩320元。
【ReAct 完成】LLM 未再调用工具,任务结束
===== ReAct Agent 最终输出 =====
✅ ReAct 解题完成
思考过程:
答案:小明最后还剩320元。
计算步骤:
150.00 + 250.00 = 400.00;400.00 - 80.00 = 320.00
总耗时:3709ms
五、ReAct架构核心总结
- 1. 示例选择原则:
- • 优先选「多步骤任务」(如数学应用题、多工具组合调用),能充分体现ReAct的「分步思考+行动」价值;
- • 避免单步任务(如简单的150+250),此类场景ReAct优势不明显。
- 2. ReAct实现关键:
- • Prompt强制模型输出「思考过程」,而非直接调用工具;
- • 输出捕获思考内容,便于调试和展示;
- • 工具调用后根据结果反馈,形成「思考→行动→反馈」闭环。
- 3. 工程化扩展方向:
- • 支持多轮工具调用(循环执行「思考→行动」);
- • 增加思考过程的结构化存储(如JSON格式);
- • 支持多工具组合(如计算器+单位转换工具)。
ReAct架构让Agent从「黑盒执行」变成「透明思考+可解释行动」,不仅提升了任务成功率,也让调试和优化更有方向——这是工业级Agent的核心能力之一。
MiaoAll