自行实现ReAct Agent
2026年4月18日 · 792 字 · 4 分钟
使用Eino框架的图编排能力,自行实现一个支持动态循环的ReAct Agent ReAct Agent(推理-行动代理)是一种更智能的Agent模式,它通过"思考-行动-观察"的循环来解决问题: 关键设计: 分支判断:ChatModel的输出决定下一步 循环机制:Tool → HistoryNode → ChatModel,形成循环 状态管理:通过图状态维护对话历史 输出: 关键观察: 通过 通过边和分支实现循环: 每轮循环都更新历史: 模型可以同时调用多个工具: ReAct Agent的循环次数取决于:什么是ReAct Agent
ReAct vs Chain
特性
Chain Agent
ReAct Agent
调用次数
固定(2次ChatModel)
动态(可能多次循环)
执行流程
线性,不可变
循环,根据条件动态决定
适用场景
简单任务
复杂推理任务
实现复杂度
简单
稍复杂(需要分支和循环)
架构设计
┌─────────────────────────────────────────────────────────────┐
│ ReAct Agent 架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [START] │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ ChatModel │◀─────────────────────────────┐ │
│ └─────────────┘ │ │
│ │ │ │
│ │ 有ToolCalls? │ │
│ ├─────┴─────┐ │ │
│ │ │ │ │
│ YES NO │ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌──────┐ [END] │ │
│ │ Tool │ │ │
│ └──────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │ HistoryNode │────────────────────────────┘ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
代码实现
定义图状态
type HistoryMessage struct {
History []*schema.Message // 历史消息
SystemMessage string // 系统消息
UserMessage string // 用户消息
}
创建ChatModel和工具
// 创建ChatModel
config := config.NewConfig("../config/config.json")
model, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
APIKey: config.APIKey,
Model: config.Model,
BaseURL: config.Url,
})
// 创建工具
timeTool := tools.NewTimeTool() // 获取时间
poemTool := tools.NewSayTool() // 获取古诗
tools := []tool.BaseTool{timeTool, poemTool}
// 绑定工具到ChatModel
toolInfos := make([]*schema.ToolInfo, 0, len(tools))
for _, tool := range tools {
toolInfo, _ := tool.Info(ctx)
toolInfos = append(toolInfos, toolInfo)
}
model.BindTools(toolInfos)
创建图
初始化图和状态
graph := compose.NewGraph[[]*schema.Message, *schema.Message](
compose.WithGenLocalState(func(ctx context.Context) *HistoryMessage {
return &HistoryMessage{
History: make([]*schema.Message, 0, 4),
UserMessage: "",
SystemMessage: "",
}
}),
)
添加节点
// 历史消息管理节点
historyNode := compose.InvokableLambda(
func(ctx context.Context, input []*schema.Message) ([]*schema.Message, error) {
return // 逻辑通过图状态实现
},
)
graph.AddLambdaNode("history_node", historyNode,
compose.WithStatePostHandler(
func(ctx context.Context, out []*schema.Message, state *HistoryMessage) ([]*schema.Message, error) {
result := []*schema.Message{
{Role: schema.System, Content: state.SystemMessage},
}
result = append(result, state.History...)
return result, nil
},
))
// ChatModel节点
graph.AddChatModelNode("chatmodel", model,
compose.WithStatePreHandler(func(ctx context.Context, input []*schema.Message, state *HistoryMessage) ([]*schema.Message, error) {
// 提取系统消息和用户消息
for _, msg := range input {
if msg.Role == schema.System {
state.SystemMessage = msg.Content
} else if msg.Role == schema.User {
state.UserMessage = msg.Content
}
}
return input, nil
}),
compose.WithStatePostHandler(func(ctx context.Context, output *schema.Message, state *HistoryMessage) (*schema.Message, error) {
// 将模型输出加入历史
state.History = append(state.History, output)
return output, nil
}))
// 工具节点
toolsNode, _ := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
Tools: tools,
})
graph.AddToolsNode("tool", toolsNode,
compose.WithStatePostHandler(func(ctx context.Context, out []*schema.Message, state *HistoryMessage) ([]*schema.Message, error) {
// 将工具输出加入历史
state.History = append(state.History, out...)
return out, nil
}))
连接节点
// 开始到ChatModel
graph.AddEdge(compose.START, "chatmodel")
// Tool到HistoryNode
graph.AddEdge("tool", "history_node")
// HistoryNode到ChatModel(形成循环)
graph.AddEdge("history_node", "chatmodel")
// ChatModel的分支判断
graph.AddBranch("chatmodel", compose.NewGraphBranch(
func(ctx context.Context, in *schema.Message) (string, error) {
if len(in.ToolCalls) > 0 {
return "tool", nil // 有工具调用,走Tool节点
}
return compose.END, nil // 无工具调用,结束
},
map[string]bool{
"tool": true,
compose.END: true,
},
))
编译和运行
编译图
runnable, err := graph.Compile(ctx)
if err != nil {
log.Fatal(err)
}
非流式调用
input := []*schema.Message{
{Role: schema.User, Content: "我在泰国可以告诉我几点了吗,并且和我说和节日有关的古诗?"},
}
msg, err := runnable.Invoke(ctx, input)
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Content)
完整示例
测试代码
func TestReact(t *testing.T) {
BuildRagWithGraph()
}
运行结果
go test -v -run TestReact ./agent/
=== RUN TestReact
【非流式输出】
根据查询结果:
## 🕐 时间信息
当前曼谷时区的时间是:**2026年4月18日 21:23:17**
## 📜 节日古诗词
为您推荐一首关于中秋节的诗句:
> **"好时节,愿得年年,常见中秋月。"**
> — 徐有贞《中秋月·中秋月》
这句诗描绘了中秋佳节的美好时刻,表达了希望每年都能欣赏到中秋明月的美好愿望。
--- PASS: TestReact (7.57s)
PASS
执行流程分析
第1轮循环:
ChatModel
↓ 思考:用户需要时间和古诗,我要调用工具
↓ ToolCalls: [get_time, get_quote]
Tool
↓ 执行:get_time("Asia/Bangkok") + get_quote("jieri")
↓ 结果:时间 + 古诗
HistoryNode
↓ 更新历史:[用户问题, ToolCalls, 工具结果]
第2轮循环:
ChatModel
↓ 观察:工具返回了时间和古诗
↓ 思考:我现在可以回答用户的问题了
↓ 无ToolCalls
END
↓ 输出最终答案
关键技术点
1. 分支判断
ToolCalls判断是否需要继续:graph.AddBranch("chatmodel", compose.NewGraphBranch(
func(ctx context.Context, in *schema.Message) (string, error) {
if len(in.ToolCalls) > 0 {
return "tool", nil
}
return compose.END, nil
},
map[string]bool{"tool": true, compose.END: true},
))
2. 循环机制
chatmodel → tool → history_node → chatmodel → ...
3. 状态传递
// ChatModel输出加入历史
state.History = append(state.History, output)
// Tool输出加入历史
state.History = append(state.History, out...)
4. 多工具支持
// out是[]*schema.Message,可能包含多个工具的结果
state.History = append(state.History, out...)
ReAct循环详解
思考-行动-观察
┌─────────────────────────────────────────────────────────────┐
│ ReAct循环过程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第1轮: │
│ ┌─────────────┐ │
│ │ 思考 │ "我需要知道泰国时间和节日古诗" │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 行动 │ 调用 get_time + get_quote │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 观察 │ 时间: 21:23:17, 古诗: "好时节..." │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ 第2轮: │
│ ┌─────────────┐ │
│ │ 思考 │ "我已经有了所有信息,可以回答了" │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 最终答案 │ 输出完整回答 │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
循环次数控制
对比Chain Agent
代码差异
Chain Agent
// 固定顺序:chatmodel1 → tool → history_node → chatmodel2
graph.AddEdge(compose.START, "chatmodel1")
graph.AddEdge("chatmodel1", "tool")
graph.AddEdge("tool", "history_node")
graph.AddEdge("history_node", "chatmodel2")
graph.AddEdge("chatmodel2", compose.END)
ReAct Agent
// 动态循环:chatmodel → tool → history_node → chatmodel → ...
graph.AddEdge(compose.START, "chatmodel")
graph.AddEdge("tool", "history_node")
graph.AddEdge("history_node", "chatmodel")
graph.AddBranch("chatmodel", ...) // 动态判断
执行差异
场景
Chain Agent
ReAct Agent
简单问题
2次ChatModel
1次ChatModel
复杂问题
2次ChatModel(可能不够)
多次循环直到完成
不需要工具
仍然调用Tool
直接返回答案