6004 字
约 20 分钟
26
6. ReAct Agent实战
无标签
  1. ReAct Agent实战 秀才 2026年4月16日 大约 19 分钟 Go Agent Go Agent 大模型 Eino ReAct Agent Go语言 AI Agent Agent教程 此页内容 1. ReAct 模式回顾 2. 创建第一个 ReAct Agent 3. AgentConfig 配置详解 3.1 ToolCallingModel 3.2 ToolsConfig 3.3 MaxStep 3.4 MessageModifier 4. 流式输出 5. 多工具协作 6. 多轮对话 7. 实战注意事项 8. 小结 前面几篇文章,我们从 ChatModel 到 Prompt 模板再到工具定义,我们了解了Eino 的核心组件。但到目前为止,这些组件还是各管各的——模型只会聊天,工具只能手动调用,两者之间并没有真正串起来。这篇文章要做的,就是把它们全部组装在一起,构建一个真正的 ReAct Agent。 所谓 ReAct,是 Reasoning + Acting 的缩写。它不是什么复杂的理论,核心思想就一句话: 让模型先想清楚再动手,动完手再看看结果,然后接着想、接着干,直到任务完成。 这跟我们人类做事的方式一模一样——遇到问题先分析,想到思路就行动,看到结果再调整。Eino 的 react.NewAgent 把这个循环封装得很干净,你只需要告诉它"用哪个模型、有哪些工具",剩下的推理-调用-反馈循环它帮你搞定。 1. ReAct 模式回顾 在 Agent 认知篇里我们从理论层面聊过 ReAct,这里快速回顾一下它在代码层面的运作流程,帮你建立直觉。 一个 ReAct Agent 的执行过程是这样的:用户提了一个问题,Agent 把问题交给大模型;模型分析之后,如果觉得自己能直接回答,就输出最终答案,流程结束。但如果模型判断需要借助外部信息,比如查天气、算数学、搜数据库,它就不会直接给答案,而是生成一个工具调用请求——"我要调 get_weather 这个工具,参数是 city=北京"。Agent 框架收到这个请求后,去执行对应的工具函数,拿到结果,再把结果塞回给模型。模型看了工具返回的数据,可能就能给出最终答案了;也可能觉得还不够,于是再调另一个工具。如此循环,直到模型认为信息足够,输出最终回答。 这个"思考 → 行动 → 观察 → 再思考"的循环,就是 ReAct 的精髓。Eino 的实现里,每一轮循环叫一个 Step,你可以通过 MaxStep 来限制最多循环多少次,防止 Agent 陷入死循环。
  2. 创建第一个 ReAct Agent 理论讲完,直接上手。我们来创建一个能查天气的 ReAct Agent——用户问天气,Agent 自动调用天气工具获取信息,然后组织语言回答用户。
package main

import (
 "context"
 "fmt"
 "log"
 "os"

 "github.com/cloudwego/eino-ext/components/model/openai"
 "github.com/cloudwego/eino/components/tool"
 "github.com/cloudwego/eino/components/tool/utils"
 "github.com/cloudwego/eino/compose"
 "github.com/cloudwego/eino/flow/agent/react"
 "github.com/cloudwego/eino/schema"
)

// 天气查询的入参
type WeatherRequest struct {
 City string `json:"city"`
}

// 天气查询的返回
type WeatherResponse struct {
 City string `json:"city"`
 Temp string `json:"temp"`
 Weather string `json:"weather"`
}

func getWeather(ctx context.Context, req *WeatherRequest) (*WeatherResponse, error) {
 // 模拟天气数据,实际项目中替换为真实 API 调用
 mockData := map[string]WeatherResponse{
 "北京": {City: "北京", Temp: "22°C", Weather: "晴"},
 "上海": {City: "上海", Temp: "26°C", Weather: "多云"},
 "深圳": {City: "深圳", Temp: "30°C", Weather: "阵雨"},
 }
 if data, ok := mockData[req.City]; ok {
 return &data, nil
 }
 return &WeatherResponse{City: req.City, Temp: "未知", Weather: "未知"}, nil
}

func main() {
 ctx := context.Background()

 // 1. 创建 ChatModel(接入通义千问)
 chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
 BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
 APIKey: os.Getenv("DASHSCOPE_API_KEY"),
 Model: "qwen-plus",
 })
 if err != nil {
 log.Fatalf("创建 ChatModel 失败: %v", err)
 }

 // 2. 创建天气查询工具
 weatherTool := utils.NewTool(
 &schema.ToolInfo{
 Name: "get_weather",
 Desc: "查询指定城市的实时天气信息,包括温度和天气状况",
 ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
 "city": {
 Type: schema.String,
 Desc: "要查询天气的城市名称,如:北京、上海、深圳",
 Required: true,
 },
 }),
 },
 getWeather,
 )

 // 3. 创建 ReAct Agent
 agent, err := react.NewAgent(ctx, &react.AgentConfig{
 ToolCallingModel: chatModel,
 ToolsConfig: compose.ToolsNodeConfig{
 Tools: []tool.BaseTool{weatherTool},
 },
 })
 if err != nil {
 log.Fatalf("创建 Agent 失败: %v", err)
 }

 // 4. 向 Agent 提问
 answer, err := agent.Generate(ctx, []*schema.Message{
 schema.UserMessage("北京今天天气怎么样?"),
 })
 if err != nil {
 log.Fatalf("Agent 执行失败: %v", err)
 }

 fmt.Println("Agent 回答:", answer.Content)
}

运行结果:

Agent 回答: 北京今天天气晴朗,温度为22°C。

这段代码做了四件事:创建模型、创建工具、组装 Agent、发起提问。关键在第三步—— react.NewAgent 把模型和工具组装成了一个完整的 Agent。你不需要手动写"先调模型→解析工具调用→执行工具→把结果塞回去→再调模型"这套逻辑, Agent 内部全帮你处理了。 调用 agent.Generate 时,Agent 内部实际发生了这些事:先把用户消息发给模型,模型分析后决定调用 get_weather 工具,Agent 执行工具拿到天气数据,再把工具结果返回给模型,模型最终组织了一段自然语言回答。整个过程对你来说就是一个函数调用。 注意 import 里需要引入 "github.com/cloudwego/eino/components/tool" ,因为代码中用到了 tool.InvokableTool 类型。完整的 import 列表在上面的代码中没有列全,实际运行时 IDE 会帮你自动补全。

3. AgentConfig 配置详解 react.AgentConfig 是 ReAct Agent 的配置核心,前面的示例只用了最基础的两个字段。实际开发中你可能需要更多控制,下面逐个拆解。 3.1 ToolCallingModel 这个字段传入一个支持工具调用的 ChatModel。注意不是所有模型都支持 Function Calling——如果你传入一个不支持工具调用的模型,Agent 在运行时会报错。通义千问的 qwen-plus 和 qwen-max 都支持工具调用, qwen-turbo 也支持但效果相对差一些。 3.2 ToolsConfig 工具配置,类型是 compose.ToolsNodeConfig 。它有两个主要字段: InvokableTools 放同步工具, StreamableTools 放流式工具。绝大多数情况下你只会用到 InvokableTools 。工具数量不是越多越好,一般控制在 10 个以内效果最好——工具太多会让模型在选择时犯迷糊,反而降低准确率。 3.3 MaxStep 最大执行步数,默认值是 15。每一个 Step 包含一次模型调用和可能的一次工具执行。设置这个值是为了防止 Agent 陷入无限循环——比如模型反复调用同一个工具却得不到想要的结果,或者在多个工具之间来回跳。实际项目中,大多数任务在 3-5 个 Step 内就能完成,设置 10-15 是比较安全的上限。 3.4 MessageModifier 消息修改器,它是一个函数,在每次调用模型之前对消息列表进行处理。最常见的用途是注入 System Prompt:

agent, err := react.NewAgent(ctx, &react.AgentConfig{
 ToolCallingModel: chatModel,
 ToolsConfig: compose.ToolsNodeConfig{
 InvokableTools: []tool.InvokableTool{weatherTool},
 },
 MaxStep: 10,
 MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
 // 在消息列表最前面加上系统提示
 messages := make([]*schema.Message, 0, len(input)+1)
 messages = append(messages, schema.SystemMessage(
 "你是一个专业的天气助手,回答要简洁友好。如果用户问的城市没有天气数据,建议用户换个城市试试。",
 ))
 messages = append(messages, input...)
 return messages
 },
})

MessageModifier 的参数是当前的完整消息列表(包括用户消息、历史的工具调用记录等),你可以在里面做任何修改——加系统提示、过滤历史消息、注入额外上下文。返回的消息列表会被发送给模型。 为什么不直接在 agent.Generate 的入参里加 System Prompt?因为 ReAct Agent 内部会多轮调用模型,每一轮都需要这个 System Prompt,如果你只在最开始加了一次,第二轮循环时它就丢了。 MessageModifier 保证每一轮模型调用前都会执行,系统提示不会丢失。 下面这个示例把上面的配置组合起来,构建一个有性格的天气助手:

package main

import (
 "context"
 "fmt"
 "log"
 "os"

 "github.com/cloudwego/eino-ext/components/model/openai"
 "github.com/cloudwego/eino/components/tool"
 "github.com/cloudwego/eino/components/tool/utils"
 "github.com/cloudwego/eino/compose"
 "github.com/cloudwego/eino/flow/agent/react"
 "github.com/cloudwego/eino/schema"
)

type WeatherRequest struct {
 City string `json:"city"`
}

type WeatherResponse struct {
 City string `json:"city"`
 Temp string `json:"temp"`
 Weather string `json:"weather"`
 Wind string `json:"wind"`
}

func getWeather(ctx context.Context, req *WeatherRequest) (*WeatherResponse, error) {
 mockData := map[string]WeatherResponse{
 "北京": {City: "北京", Temp: "22°C", Weather: "晴", Wind: "北风3级"},
 "上海": {City: "上海", Temp: "26°C", Weather: "多云", Wind: "东南风2级"},
 "深圳": {City: "深圳", Temp: "30°C", Weather: "阵雨", Wind: "南风4级"},
 "杭州": {City: "杭州", Temp: "24°C", Weather: "晴转多云", Wind: "西风1级"},
 }
 if data, ok := mockData[req.City]; ok {
 return &data, nil
 }
 return &WeatherResponse{City: req.City, Temp: "暂无数据", Weather: "暂无数据", Wind: "暂无数据"}, nil
}

func main() {
 ctx := context.Background()

 chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
 BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
 APIKey: os.Getenv("DASHSCOPE_API_KEY"),
 Model: "qwen-plus",
 })
 if err != nil {
 log.Fatalf("创建 ChatModel 失败: %v", err)
 }

 weatherTool := utils.NewTool(
 &schema.ToolInfo{
 Name: "get_weather",
 Desc: "查询指定城市的实时天气信息,包括温度、天气状况和风力",
 ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
 "city": {
 Type: schema.String,
 Desc: "要查询天气的城市名称,如:北京、上海、深圳、杭州",
 Required: true,
 },
 }),
 },
 getWeather,
 )

 agent, err := react.NewAgent(ctx, &react.AgentConfig{
 ToolCallingModel: chatModel,
 ToolsConfig: compose.ToolsNodeConfig{
 Tools: []tool.BaseTool{weatherTool},
 },
 MaxStep: 10,
 MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
 messages := make([]*schema.Message, 0, len(input)+1)
 messages = append(messages, schema.SystemMessage(
 "你是一个热情的天气助手,回答简洁但有温度。天气好就鼓励用户出去玩,天气不好就贴心提醒带伞或注意防晒。",
 ))
 messages = append(messages, input...)
 return messages
 },
 })
 if err != nil {
 log.Fatalf("创建 Agent 失败: %v", err)
 }

 answer, err := agent.Generate(ctx, []*schema.Message{
 schema.UserMessage("深圳今天天气怎么样?需要带伞吗?"),
 })
 if err != nil {
 log.Fatalf("Agent 执行失败: %v", err)
 }

 fmt.Println("Agent 回答:", answer.Content)
}

运行结果:

Agent 回答: 深圳今天30°C,有阵雨,南风4级~🌧️ 
建议带伞出门,以防突然下雨,同时注意防晒和防风哦!需要我帮你看看其他城市吗?😊

和之前的示例对比,这个 Agent 多了 MaxStep 限制和 MessageModifier 注入的系统人设。模型的回答风格明显受到了 System Prompt 的影响——不再是干巴巴的天气数据播报,而是带上了关心用户的语气。

4. 流式输出 agent.Generate 是等 Agent 完全执行完毕后一次性返回结果。如果 Agent 需要多轮工具调用,用户可能要等很久才看到回答。 agent.Stream 提供了流式输出能力——模型边生成边返回,用户可以实时看到回答内容逐字出现。

package main

import (
 "context"
 "errors"
 "fmt"
 "io"
 "log"
 "os"

 "github.com/cloudwego/eino-ext/components/model/openai"
 "github.com/cloudwego/eino/components/tool"
 "github.com/cloudwego/eino/components/tool/utils"
 "github.com/cloudwego/eino/compose"
 "github.com/cloudwego/eino/flow/agent/react"
 "github.com/cloudwego/eino/schema"
)

type WeatherRequest struct {
 City string `json:"city"`
}

type WeatherResponse struct {
 City string `json:"city"`
 Temp string `json:"temp"`
 Weather string `json:"weather"`
}

func getWeather(ctx context.Context, req *WeatherRequest) (*WeatherResponse, error) {
 mockData := map[string]WeatherResponse{
 "北京": {City: "北京", Temp: "22°C", Weather: "晴"},
 "上海": {City: "上海", Temp: "26°C", Weather: "多云"},
 }
 if data, ok := mockData[req.City]; ok {
 return &data, nil
 }
 return &WeatherResponse{City: req.City, Temp: "未知", Weather: "未知"}, nil
}

func main() {
 ctx := context.Background()

 chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
 BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
 APIKey: os.Getenv("DASHSCOPE_API_KEY"),
 Model: "qwen-plus",
 })
 if err != nil {
 log.Fatalf("创建 ChatModel 失败: %v", err)
 }

 weatherTool := utils.NewTool(
 &schema.ToolInfo{
 Name: "get_weather",
 Desc: "查询指定城市的实时天气信息",
 ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
 "city": {
 Type: schema.String,
 Desc: "城市名称,如:北京、上海",
 Required: true,
 },
 }),
 },
 getWeather,
 )

 agent, err := react.NewAgent(ctx, &react.AgentConfig{
 ToolCallingModel: chatModel,
 ToolsConfig: compose.ToolsNodeConfig{
 Tools: []tool.BaseTool{weatherTool},
 },
 })
 if err != nil {
 log.Fatalf("创建 Agent 失败: %v", err)
 }

 // 使用 Stream 方法获取流式输出
 reader, err := agent.Stream(ctx, []*schema.Message{
 schema.UserMessage("上海今天天气如何?"),
 })
 if err != nil {
 log.Fatalf("Agent Stream 失败: %v", err)
 }

 fmt.Print("Agent 回答: ")
 for {
 msg, err := reader.Recv()
 if err != nil {
 if errors.Is(err, io.EOF) {
 break // 流结束
 }
 log.Fatalf("读取流失败: %v", err)
 }
 fmt.Print(msg.Content) // 逐块输出内容
 }
 fmt.Println()
}

运行结果(内容逐字打印出来):

Agent 回答: 上海今天的天气是多云,气温为26°C。

agent.Stream 返回的是一个 *schema.StreamReader[*schema.Message] ,你通过不断调用 Recv() 来接收消息片段。每个片段的 Content 可能只有几个字,拼接起来就是完整的回答。当 Recv() 返回 io.EOF 时表示流结束。 有一个细节值得注意:在流式模式下,Agent 内部的工具调用阶段不会产生流式输出——工具调用和执行是在后台同步完成的。你通过 Recv() 收到的流式内容,只有最终那轮模型生成回答时的逐字输出。换句话说,如果 Agent 先调了两次工具再回答,前两个 Step 的过程用户看不到,只有最终回答是流式的。 这在大多数场景下是合理的——用户关心的是最终答案,不需要看到中间的工具调用过程。如果你希望展示中间步骤(比如做一个调试界面),可以通过 Eino 的回调机制(Callback)来监听每个 Step 的开始和结束事件,这个在 Eino 进阶篇会详细讲。 5. 多工具协作 一个只有一个工具的 Agent 能做的事很有限。真实场景中,Agent 往往需要多个工具配合才能完成任务。比如用户问"北京和上海哪个更热?",Agent 需要分别查两个城市的天气,然后做对比。再比如用户问"帮我算一下北京气温和上海气温的差",Agent 需要先查天气再算数学。 下面这个示例给 Agent 配备了三个工具:天气查询、数学计算、时间查询。

package main

import (
 "context"
 "fmt"
 "log"
 "math"
 "os"
 "time"

 "github.com/cloudwego/eino-ext/components/model/openai"
 "github.com/cloudwego/eino/components/tool"
 "github.com/cloudwego/eino/components/tool/utils"
 "github.com/cloudwego/eino/compose"
 "github.com/cloudwego/eino/flow/agent/react"
 "github.com/cloudwego/eino/schema"
)

// ========== 天气查询工具 ==========

type WeatherRequest struct {
 City string `json:"city"`
}

type WeatherResponse struct {
 City string `json:"city"`
 Temp float64 `json:"temp"`
 Weather string `json:"weather"`
}

func getWeather(ctx context.Context, req *WeatherRequest) (*WeatherResponse, error) {
 mockData := map[string]WeatherResponse{
 "北京": {City: "北京", Temp: 22, Weather: "晴"},
 "上海": {City: "上海", Temp: 26, Weather: "多云"},
 "深圳": {City: "深圳", Temp: 30, Weather: "阵雨"},
 "哈尔滨": {City: "哈尔滨", Temp: 8, Weather: "小雪"},
 }
 if data, ok := mockData[req.City]; ok {
 return &data, nil
 }
 return &WeatherResponse{City: req.City, Temp: 0, Weather: "暂无数据"}, nil
}

// ========== 数学计算工具 ==========

type CalcRequest struct {
 A float64 `json:"a"`
 B float64 `json:"b"`
 Op string `json:"op"`
}

type CalcResponse struct {
 Expression string `json:"expression"`
 Result float64 `json:"result"`
}

func calculate(ctx context.Context, req *CalcRequest) (*CalcResponse, error) {
 var result float64
 switch req.Op {
 case "add":
 result = req.A + req.B
 case "subtract":
 result = req.A - req.B
 case "multiply":
 result = req.A * req.B
 case "divide":
 if req.B == 0 {
 return nil, fmt.Errorf("除数不能为零")
 }
 result = req.A / req.B
 default:
 return nil, fmt.Errorf("不支持的运算: %s", req.Op)
 }
 return &CalcResponse{
 Expression: fmt.Sprintf("%.1f %s %.1f", req.A, req.Op, req.B),
 Result: math.Round(result*100) / 100,
 }, nil
}

// ========== 时间查询工具 ==========

type TimeRequest struct {
 Timezone string `json:"timezone"`
}

type TimeResponse struct {
 Timezone string `json:"timezone"`
 CurrentTime string `json:"current_time"`
 Date string `json:"date"`
}

func getCurrentTime(ctx context.Context, req *TimeRequest) (*TimeResponse, error) {
 tz := req.Timezone
 if tz == "" {
 tz = "Asia/Shanghai"
 }
 loc, err := time.LoadLocation(tz)
 if err != nil {
 loc = time.FixedZone("CST", 8*3600)
 }
 now := time.Now().In(loc)
 return &TimeResponse{
 Timezone: tz,
 CurrentTime: now.Format("15:04:05"),
 Date: now.Format("2006-01-02"),
 }, nil
}

func main() {
 ctx := context.Background()

 chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
 BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
 APIKey: os.Getenv("DASHSCOPE_API_KEY"),
 Model: "qwen-plus",
 })
 if err != nil {
 log.Fatalf("创建 ChatModel 失败: %v", err)
 }

 // 创建三个工具
 weatherTool := utils.NewTool(
 &schema.ToolInfo{
 Name: "get_weather",
 Desc: "查询指定城市的实时天气信息,返回温度(数值)和天气状况",
 ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
 "city": {
 Type: schema.String,
 Desc: "城市名称,如:北京、上海、深圳、哈尔滨",
 Required: true,
 },
 }),
 },
 getWeather,
 )

 calcTool := utils.NewTool(
 &schema.ToolInfo{
 Name: "calculator",
 Desc: "对两个数字执行四则运算,返回计算结果",
 ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
 "a": {Type: schema.Number, Desc: "第一个数字", Required: true},
 "b": {Type: schema.Number, Desc: "第二个数字", Required: true},
 "op": {Type: schema.String, Desc: "运算类型", Required: true, Enum: []string{"add", "subtract", "multiply", "divide"}},
 }),
 },
 calculate,
 )

 timeTool := utils.NewTool(
 &schema.ToolInfo{
 Name: "get_current_time",
 Desc: "获取当前时间和日期",
 ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
 "timezone": {
 Type: schema.String,
 Desc: "时区,如 Asia/Shanghai、America/New_York,默认为中国时区",
 },
 }),
 },
 getCurrentTime,
 )

 // 组装 Agent,注册三个工具
 agent, err := react.NewAgent(ctx, &react.AgentConfig{
 ToolCallingModel: chatModel,
 ToolsConfig: compose.ToolsNodeConfig{
 Tools: []tool.BaseTool{weatherTool, calcTool, timeTool},
 },
 MaxStep: 10,
 MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
 messages := make([]*schema.Message, 0, len(input)+1)
 messages = append(messages, schema.SystemMessage(
 "你是一个多功能助手,可以查天气、做计算、查时间。回答要准确简洁。",
 ))
 messages = append(messages, input...)
 return messages
 },
 })
 if err != nil {
 log.Fatalf("创建 Agent 失败: %v", err)
 }

 // 测试1:需要多次工具调用的问题
 fmt.Println("===== 测试1:多城市天气对比 =====")
 answer1, err := agent.Generate(ctx, []*schema.Message{
 schema.UserMessage("北京和哈尔滨今天的温差是多少度?"),
 })
 if err != nil {
 log.Fatalf("执行失败: %v", err)
 }
 fmt.Println("回答:", answer1.Content)

 // 测试2:需要组合使用工具
 fmt.Println("\n===== 测试2:综合查询 =====")
 answer2, err := agent.Generate(ctx, []*schema.Message{
 schema.UserMessage("现在几点了?另外帮我查一下上海的天气。"),
 })
 if err != nil {
 log.Fatalf("执行失败: %v", err)
 }
 fmt.Println("回答:", answer2.Content)
}

运行结果:

===== 测试1:多城市天气对比 =====
回答: 北京今天的温度是22℃,哈尔滨今天的温度是8℃,温差为: 
22 - 8 = 14℃。

===== 测试2:综合查询 =====
回答: 现在是北京时间 11:36:17,日期是 2026年4月16日。 
上海当前天气:26℃,多云。

测试1 是一个很典型的多工具协作场景。Agent 需要先分别查北京和哈尔滨的天气拿到温度数值,然后可能再调计算器算温差(也可能模型自己算,22减8这种简单运算模型通常能直接算对)。整个过程中,Agent 自主决定调用哪些工具、按什么顺序调用、调用几次,你完全不需要干预。 测试2 展示了另一种模式:用户一句话里包含两个独立的需求,Agent 需要分别调用时间工具和天气工具,然后把两个结果合并成一个完整的回答。这种能力不需要你写任何特殊逻辑,模型在 ReAct 循环中自然就能处理。

6. 多轮对话 前面的示例都是单轮交互——问一句答一句。实际应用中,Agent 往往需要维护对话上下文,让用户可以连续追问。实现方式很直接:把历史消息一起传给 Generate 或 Stream 就行。

package main

import (
 "context"
 "fmt"
 "log"
 "os"

 "github.com/cloudwego/eino-ext/components/model/openai"
 "github.com/cloudwego/eino/components/tool"
 "github.com/cloudwego/eino/components/tool/utils"
 "github.com/cloudwego/eino/compose"
 "github.com/cloudwego/eino/flow/agent/react"
 "github.com/cloudwego/eino/schema"
)

type WeatherRequest struct {
 City string `json:"city"`
}

type WeatherResponse struct {
 City string `json:"city"`
 Temp string `json:"temp"`
 Weather string `json:"weather"`
}

func getWeather(ctx context.Context, req *WeatherRequest) (*WeatherResponse, error) {
 mockData := map[string]WeatherResponse{
 "北京": {City: "北京", Temp: "22°C", Weather: "晴"},
 "上海": {City: "上海", Temp: "26°C", Weather: "多云"},
 }
 if data, ok := mockData[req.City]; ok {
 return &data, nil
 }
 return &WeatherResponse{City: req.City, Temp: "未知", Weather: "未知"}, nil
}

func main() {
 ctx := context.Background()

 chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
 BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
 APIKey: os.Getenv("DASHSCOPE_API_KEY"),
 Model: "qwen-plus",
 })
 if err != nil {
 log.Fatalf("创建 ChatModel 失败: %v", err)
 }

 weatherTool := utils.NewTool(
 &schema.ToolInfo{
 Name: "get_weather",
 Desc: "查询指定城市的实时天气信息",
 ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
 "city": {Type: schema.String, Desc: "城市名称", Required: true},
 }),
 },
 getWeather,
 )

 agent, err := react.NewAgent(ctx, &react.AgentConfig{
 ToolCallingModel: chatModel,
 ToolsConfig: compose.ToolsNodeConfig{
 Tools: []tool.BaseTool{weatherTool},
 },
 MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message {
 messages := make([]*schema.Message, 0, len(input)+1)
 messages = append(messages, schema.SystemMessage("你是一个天气助手,回答简洁。"))
 messages = append(messages, input...)
 return messages
 },
 })
 if err != nil {
 log.Fatalf("创建 Agent 失败: %v", err)
 }

 // 维护对话历史
 history := make([]*schema.Message, 0)

 // 第一轮对话
 history = append(history, schema.UserMessage("北京天气怎么样?"))
 answer1, err := agent.Generate(ctx, history)
 if err != nil {
 log.Fatalf("执行失败: %v", err)
 }
 fmt.Println("第一轮回答:", answer1.Content)
 history = append(history, answer1) // 把 Agent 的回答也加入历史

 // 第二轮对话——追问,不需要重复说城市
 history = append(history, schema.UserMessage("那上海呢?"))
 answer2, err := agent.Generate(ctx, history)
 if err != nil {
 log.Fatalf("执行失败: %v", err)
 }
 fmt.Println("第二轮回答:", answer2.Content)
 history = append(history, answer2)

 // 第三轮对话——基于前两轮的对比问题
 history = append(history, schema.UserMessage("哪个城市更热?"))
 answer3, err := agent.Generate(ctx, history)
 if err != nil {
 log.Fatalf("执行失败: %v", err)
 }
 fmt.Println("第三轮回答:", answer3.Content)
}

运行结果:

第一轮回答: 北京当前天气晴,气温22°C。
第二轮回答: 上海当前天气多云,气温26°C。
第三轮回答: 上海更热,气温26°C,比北京的22°C高4°C。

关键在 history 这个切片——它充当了 Agent 的"短期记忆"。每一轮对话结束后,我们把用户消息和 Agent 回答都追加到 history 里,下一轮调用时把完整的历史传进去。这样 Agent 就能理解"那上海呢"里的"那"指的是天气,"哪个城市更热"指的是前面提到的北京和上海。 这种手动管理历史消息的方式虽然原始,但胜在灵活——你可以自由决定保留多少轮历史、是否需要压缩或摘要。随着对话越来越长,历史消息会占用更多 Token,这时候你可能需要做一些截断策略,只保留最近 N 轮对话,或者用 LLM 把早期对话压缩成摘要。这些高级的记忆管理策略在后续 Agent 记忆相关的文章里会详细展开。 7. 实战注意事项 到这里你已经能用 Eino 构建一个功能完备的 ReAct Agent 了。在实际项目中落地之前,有几个容易踩坑的地方值得提前了解。

工具描述决定了 Agent 的智商。 这一点怎么强调都不过分。模型是根据工具的 Name 和 Desc 来决定何时调用哪个工具的。如果你的工具描述含糊不清,模型就可能在不该调用时调用、该调用时不调用,或者调用了错误的工具。好的工具描述应该准确说明工具的功能边界,最好带上使用场景的暗示。比如"查询指定城市的实时天气信息"就比"获取天气"要好得多——后者太模糊了,模型不知道它能处理"昨天的天气"还是"未来一周的天气预报"。 参数的 Desc 和 Enum 同样重要。 很多人只关注工具整体描述,忽略了参数级别的描述。一个典型的坑:你定义了一个 language 参数,只写了 Desc 是"语言",模型可能填 "中文"、"Chinese"、"zh-CN"、"zh" 等各种格式。如果你加上 Enum: []string{"zh", "en"} ,模型就只会从这两个值里选,大幅降低参数格式错误的概率。 MaxStep 要根据业务合理设置。 默认15步对大多数场景来说足够了,但如果你的工具可能产生"误导性结果"导致模型反复重试,15步可能不够也可能太多(浪费 Token)。一般来说,简单的工具调用场景设 5-8 就够了,复杂的多步骤任务可以设到 15-20。 错误处理要优雅。 当工具执行出错时(比如外部 API 超时),Eino 会把错误信息以 Tool Message 的形式返回给模型。模型看到错误后通常会尝试换一种方式调用,或者直接告诉用户"抱歉,xx 服务暂时不可用"。你在工具函数里返回的 error 信息要对"模型"友好——不要返回一堆堆栈信息,而是返回一段模型能理解的错误描述,比如 fmt.Errorf("城市 %s 的天气数据查询失败,请稍后重试", city) 。 8. 小结 ReAct Agent 看起来智能,拆开来其实就是一个循环——模型想一想要不要用工具,用了就把结果拿回来再想一想,如此往复直到能给出答案。Eino 的 react.NewAgent 替你打理了这个循环的所有细节,你要做的就是准备好模型和工具,然后把问题丢给它。真正决定一个 Agent 好不好用的,往往不在框架代码上,而在那些看似不起眼的地方——工具描述写得清不清楚、System Prompt 定位准不准确、MaxStep 限制合不合理。把这些基本功做扎实了,一个简单的 ReAct Agent 就已经能处理相��多的实际任务了。 在 GitHub 上编辑此页 上次编辑于: 2026/4/16 14:18:14 贡献者: Percygu 上一页 5. 工具定义与使用

6. ReAct Agent实战
https://clxhxhhr.top/posts/3589/
作者
clxstart
发布于
2026-09-25
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。