极简 AI Agent Demo:一个文件,跑完 Thought → Act → Observation
摘要:基于CloudWeGo Eino框架的ReAct Agent天气查询系统 本文介绍了一个基于CloudWeGo Eino框架实现的ReAct模式AI Agent,用于智能天气查询服务。该系统通过Reason+Act交替执行的方式,解决了传统LLM直接应答在复杂业务场景中的局限性。Agent具备自动判断是否需要工具、多步推理完成任务的能力,能够处理不完整信息、依赖外部系统和多步决策等场景。文章
目录
一、背景:为什么我们需要“可推理”的 Agent
在很多 AI 应用的早期阶段,我们通常采用一种非常直接的方式:
用户问题 → LLM → 返回答案
比如ollama模型调用,或者直接调用模型生成接口。但只要进入稍微复杂一点的业务场景,这种方式很快就会遇到瓶颈,例如:
- 用户问题信息不完整(没给城市,却问天气)
- 答案需要依赖外部系统(天气 API、数据库、内部服务)
- 需要多步决策(先判断,再查询,再组合)
此时,我们真正需要的并不是一个“聊天机器人”,而是一个 具备推理与行动能力的 AI Agent。
本文将基于 CloudWeGo Eino 框架,使用 ReAct Agent 模式,实现一个:
能够自动判断是否需要工具、并通过多步推理完成任务的天气查询 Agent
二、什么是 ReAct Agent
ReAct(Reason + Act)是一种经典的 Agent 设计模式,其核心思想是:
推理(Reason)和行动(Act)交替进行
一个典型的 ReAct Agent 执行流程如下:

这与传统的「一次 LLM 调用」有本质区别。
三、为什么选择 Eino
Eino 是 CloudWeGo 生态下的 AI 应用框架,面向 工程化 AI Agent 开发,具备以下特点:
- 原生支持 Tool Calling
- 提供 ReAct Agent Loop
- Tool 使用 强类型 Schema
- 对 Go 开发者非常友好
- 易于和现有后端系统(IM、RPC、HTTP)集成
在 Go 生态中,这是一个非常适合做 生产级 Agent 的框架。
四、Agent 目标设计
本文实现的 Weather Agent 目标非常明确:
用户输入示例
我这边今天天气怎么样?有什么穿衣建议吗?
Agent 行为逻辑
- 判断用户是否明确给出了城市
- 如果没有 → 调用定位工具
- 查询指定城市天气
- 根据天气给出穿衣 / 出行建议
- 输出自然语言回答
这是一个典型的顺序依赖型 Agent 场景。
答案示例

五、整体架构设计
整体结构可以抽象为:

关键点在于:
LLM 不只是生成文本,而是 Agent 的“决策中枢”。
六、三种 Tool 封装方式(工程实战重点)
在这个 Demo 中,我们刻意使用了 三种不同的 Tool 封装方式,这是 Eino 非常有价值的一点。
为了方便Demo的实现,Tool中的功能我们不去真正的实现,而是模拟一个返回^ - ^
1. 使用 utils.NewTool:定位城市
return utils.NewTool(info, func(ctx context.Context, _ *LocateCityParams) (string, error) {
return `{"city":"北京"}`, nil
})
特点:
- 手动定义 Tool Schema
- 参数、返回值完全可控
- 适合核心基础能力(定位、鉴权、用户信息)
2. 使用 utils.InferTool:查询天气
queryWeatherTool, _ := utils.InferTool(
"query_weather",
"Query weather by city name",
QueryWeatherFunc,
)
特点:
- 直接基于 Go 函数
- 自动推断参数 Schema
- 业务代码最简洁
非常适合已有服务函数快速接入 Agent。
3. 实现 InvokableTool 接口:天气建议
type WeatherAdviceTool struct{}
特点:
- Tool 内部可维护复杂逻辑
- 可接数据库、配置中心、缓存
- 企业级项目中最常见
小结
Eino 允许在同一个 Agent 中混用多种 Tool 形式,这是非常工程化的设计。
七、为什么必须使用 ReAct Agent,而不是普通 Chain
在第一次使用 Eino 时最容易踩的坑,直接使用官方教程的Chain方法,导致出现的结果是不正常的。
普通 Chain 的执行模式
LLM → Tool → 结束
这种模式下:
- Tool 只会执行一次
- Tool 结果不会触发新的推理
- 不适合顺序依赖场景
ReAct Agent 的执行模式
LLM → Tool → LLM → Tool → LLM → Answer
这个天气场景:
定位 → 查天气 → 给建议
本质上就必须是 ReAct Agent。
八、System Prompt:Agent 的“行为协议”
在 ReAct Agent 中,System Prompt 的作用远大于普通对话场景。
示例:
你是一个天气助手。
当用户询问天气或穿衣建议时:
1. 如果不知道城市,先调用 locate_city
2. 拿到城市后,调用 query_weather
3. 拿到天气后,调用 weather_advice
4. 最后用自然语言回复用户,不要再调用工具
这段 Prompt 实际上是:
Agent 的执行契约(Execution Contract)
没有它,Agent 只是一个“随便试试工具的 LLM”。
九、Agent 执行效果说明
当用户输入:
今天天气怎么样?有什么穿衣建议吗?
Agent 的行为大致为:
- 调用
locate_city→ 根据Ip获取定位 - 调用
query_weather(city=北京) - 调用
weather_advice - 汇总信息
- 输出最终回答
整个过程无需手写任何循环逻辑。
十、总结与思考
通过这个 Demo,我们可以得出几个非常重要的结论:
- Agent ≠ Chatbot
- Chain ≠ Agent Loop
- Tool 是 Agent 的“能力扩展”,不是普通函数
- System Prompt 是 Agent 行为的核心控制器
Eino 提供的 ReAct Agent 能力,使得在 Go 生态中构建 工程级 AI Agent 成为一件现实且可控的事情。
代码附录
附Demo代码参考
package main
import (
"context"
"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"
"go.uber.org/zap"
)
var logger *zap.Logger
func init() {
var err error
logger, err = initLogger()
if err != nil {
panic(err)
}
}
func main() {
openAIAPIKey := os.Getenv("OPENAI_API_KEY")
openAIModelName := os.Getenv("OPENAI_MODEL_NAME")
openAIBaseURL := os.Getenv("OPENAI_BASE_URL")
ctx := context.Background()
queryWeatherTool, err := utils.InferTool(
"query_weather",
"Query weather by city name",
QueryWeatherFunc,
)
if err != nil {
logger.Error("InferTool failed!", zap.Error(err))
return
}
// 初始化 tools
baseTools := []tool.BaseTool{
getLocateCityTool(), // 使用 NewTool 方式
queryWeatherTool, // 使用 InferTool 方式
&WeatherAdviceTool{}, // 使用结构体实现方式, 此处未实现底层逻辑
}
weatherTools := compose.ToolsNodeConfig{
Tools: baseTools,
}
temp := float32(0.7)
// 创建并配置 ChatModel
chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: openAIBaseURL,
Model: openAIModelName,
APIKey: openAIAPIKey,
Temperature: &temp,
})
if err != nil {
logger.Error("NewChatModel failed!", zap.Error(err))
return
}
// 构建完整的处理链
agent, err := react.NewAgent(ctx, &react.AgentConfig{
ToolCallingModel: chatModel,
ToolsConfig: weatherTools,
MaxStep: 10,
})
if err != nil {
logger.Error("NewAgent failed!", zap.Error(err))
}
systemMsg := &schema.Message{
Role: schema.System,
Content: `
你是一个天气助手。
当用户询问天气或穿衣建议时:
1. 如果不知道城市,先调用 locate_city
2. 拿到城市后,调用 query_weather
3. 拿到天气后,调用 weather_advice
4. 最后用自然语言回复用户,不要再调用工具
`,
}
// 运行示例
resp, err := agent.Generate(ctx, []*schema.Message{
systemMsg,
{
Role: schema.User,
Content: "今天天气怎么样?有什么穿衣建议吗?",
},
})
if err != nil {
logger.Error("agent.Invoke failed", zap.Error(err))
return
}
// 输出结果
logger.Sugar().Infof("message: %s: %s", resp.Role, resp.Content)
}
// getLocateCityTool 获取当前用户的位置
func getLocateCityTool() tool.InvokableTool {
info := &schema.ToolInfo{
Name: "locate_city",
Desc: "Locate user's current city",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"ip": {
Desc: "user ip address",
Type: schema.String,
},
}),
}
return utils.NewTool(info, func(ctx context.Context, _ *LocateCityParams) (string, error) {
logger.Info("invoke locate city")
// Mock 实现
return `{"city":"北京"}`, nil
})
}
// WeatherAdviceTool
// 获取天气的建议
// 自行实现 InvokableTool 接口
type WeatherAdviceTool struct{}
func (w *WeatherAdviceTool) Info(_ context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "weather_advice",
Desc: "Give clothing and travel advice based on weather",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"weather": {
Type: schema.String,
Required: true,
},
"temp": {
Type: schema.String,
Required: true,
},
}),
}, nil
}
func (w *WeatherAdviceTool) InvokableRun(
_ context.Context,
args string,
_ ...tool.Option,
) (string, error) {
logger.Sugar().Infof("invoke weather_advice: %s", args)
return `{"advice":"天气晴朗,气温适中,适合外出,建议穿短袖"}`, nil
}
func QueryWeatherFunc(_ context.Context, params *WeatherQueryParams) (string, error) {
logger.Info("invoke QueryWeatherFunc")
if params.City == "北京" {
return `{"city":"北京","weather":"晴","temp":"26℃","wind":"微风"}`, nil
}
return `{"city":"` + params.City + `","weather":"未知"}`, nil
}
// -------- Tool Params --------
type WeatherQueryParams struct {
City string `json:"city" jsonschema_description:"city name"`
}
type LocateCityParams struct {
IP *string `json:"ip,omitempty" jsonschema_description:"user ip address"`
}
func initLogger() (*zap.Logger, error) {
return zap.NewDevelopment()
}
参考文章
Agent-让大模型拥有双手
什么是AI Agent?AI Agent综述,看这一篇就够了!
带你用Eino实现Agent与RAG
深入解析CloudWeGo Eino项目中React Agent的并发工具调用问题
更多推荐


所有评论(0)