欢迎光临
我们一直在努力

极简 AI Agent Demo:一个文件,跑完 Thought → Act → Observation

目录

    • 一、背景:为什么我们需要“可推理”的 Agent
    • 二、什么是 ReAct Agent
    • 三、为什么选择 Eino
    • 四、Agent 目标设计
      • 用户输入示例
      • Agent 行为逻辑
      • 答案示例
    • 五、整体架构设计
    • 六、三种 Tool 封装方式(工程实战重点)
      • 1. 使用 `utils.NewTool`:定位城市
      • 2. 使用 `utils.InferTool`:查询天气
      • 3. 实现 `InvokableTool` 接口:天气建议
      • 小结
    • 七、为什么必须使用 ReAct Agent,而不是普通 Chain
      • 普通 Chain 的执行模式
      • ReAct Agent 的执行模式
    • 八、System Prompt:Agent 的“行为协议”
    • 九、Agent 执行效果说明
    • 十、总结与思考
      • 代码附录
    • 参考文章

一、背景:为什么我们需要“可推理”的 Agent

在很多 AI 应用的早期阶段,我们通常采用一种非常直接的方式:

用户问题 → LLM → 返回答案

比如ollama模型调用,或者直接调用模型生成接口。但只要进入稍微复杂一点的业务场景,这种方式很快就会遇到瓶颈,例如:

  • 用户问题信息不完整(没给城市,却问天气)
  • 答案需要依赖外部系统(天气 API、数据库、内部服务)
  • 需要多步决策(先判断,再查询,再组合)

此时,我们真正需要的并不是一个“聊天机器人”,而是一个 具备推理与行动能力的 AI Agent。

本文将基于 CloudWeGo Eino 框架,使用 ReAct Agent 模式,实现一个:

能够自动判断是否需要工具、并通过多步推理完成任务的天气查询 Agent


二、什么是 ReAct Agent

ReAct(Reason + Act)是一种经典的 Agent 设计模式,其核心思想是:

推理(Reason)和行动(Act)交替进行

一个典型的 ReAct Agent 执行流程如下:

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)
    未经允许不得转载:171主机测评 » 极简 AI Agent Demo:一个文件,跑完 Thought → Act → Observation
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址