🔧5 分钟跑通你的第一个 AI Agent

0 阅读4分钟

系列「企业级 AI Agent 实现拆解」E31 篇,Part 9 起步篇第一章。前面 30 篇拆解了 Eino 和 DeepFlux 的内部机制。从这里开始换个姿态:从零动手,把 Agent 真的跑起来。

读完这篇你会得到

  • 一个能跑的 Go Agent 项目:30 行核心代码
  • ChatModel + Tool + ReAct 三者怎么串在一起
  • 用 DeepSeek(国内可用、最便宜)作为模型入口
  • 看懂终端输出:Agent 在"想"什么

准备工作

你需要:

  • Go 1.21 及以上(go version 验证)
  • DeepSeek API Key:去 platform.deepseek.com 注册,新用户有免费额度
  • 一个能联网的终端

为什么选 DeepSeek? 它的 API 兼容 OpenAI 格式,只需换一个 BaseURL,价格比 GPT-4o 便宜 10 倍以上,特别适合调试。


第一步:初始化项目

mkdir my-first-agent && cd my-first-agent
go mod init my-first-agent

安装依赖:

go get github.com/cloudwego/eino@latest
go get github.com/cloudwego/eino-ext/components/model/openai@latest

eino 是核心框架,eino-ext 里装了各个模型的驱动,这里用 openai 包驱动 DeepSeek(两者 API 格式相同)。


第二步:写一个 Tool

Tool 是 Agent 能调用的"工具"——类似函数,但 Agent 会根据任务自己决定要不要调它、怎么调。

新建 main.go

package main

import (
    "context"
    "fmt"
    "io"
    "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"
)

// 工具的输入参数:用 struct 定义,字段注释变成 JSON Schema 描述
type WeatherParams struct {
    City string `json:"city" jsonschema_description:"城市名,如 北京、上海"`
}

// 工具的执行逻辑:返回 (结果字符串, error)
func getWeather(_ context.Context, p *WeatherParams) (string, error) {
    // 这里可以调真实天气 API,演示先硬编码
    return fmt.Sprintf("%s 今天晴,气温 25°C,适合出行。", p.City), nil
}

工具定义只需要三样东西:输入结构体、执行函数、一个名字和描述。


第三步:创建 ChatModel

func newChatModel(ctx context.Context) *openai.ChatModel {
    cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        BaseURL: "https://api.deepseek.com",       // DeepSeek 的 OpenAI 兼容地址
        Model:   "deepseek-chat",
        APIKey:  os.Getenv("DEEPSEEK_API_KEY"),    // 从环境变量读,别写死在代码里
    })
    if err != nil {
        panic(err)
    }
    return cm
}

openai.NewChatModel 本来是给 GPT 用的,但因为 DeepSeek 的接口格式一样,换个 BaseURL 就能用。这就是"接口兼容"带来的好处。


第四步:把 ChatModel + Tool 组成 Agent

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

    // 1. 把函数包成 Tool
    weatherTool, err := utils.InferTool(
        "get_weather",          // 工具名(Agent 会看到这个名字)
        "查询指定城市的天气",    // 工具描述(Agent 用它决定什么时候调)
        getWeather,
    )
    if err != nil {
        panic(err)
    }

    // 2. 创建 ChatModel
    chatModel := newChatModel(ctx)

    // 3. 用 react.NewAgent 把两者组装成一个 ReAct Agent
    agent, err := react.NewAgent(ctx, &react.AgentConfig{
        ToolCallingModel: chatModel,
        ToolsConfig: compose.ToolsNodeConfig{
            Tools: []tool.BaseTool{weatherTool},
        },
    })
    if err != nil {
        panic(err)
    }

    // 4. 发一条消息,用 Stream 得到流式回复
    sr, err := agent.Stream(ctx, []*schema.Message{
        {Role: schema.User, Content: "北京今天天气怎么样?适合去颐和园吗?"},
    })
    if err != nil {
        panic(err)
    }
    defer sr.Close()

    // 5. 逐 token 打印(打字机效果)
    for {
        msg, err := sr.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            panic(err)
        }
        fmt.Print(msg.Content) // 不换行,token 一个个蹦出来
    }
    fmt.Println()
}

运行

export DEEPSEEK_API_KEY="你的 key"
go run main.go

你会看到类似这样的输出:

北京今天晴,气温 25°C,适合出行。颐和园户外游览非常适合,
建议上午前往,避开正午高温。记得带遮阳帽和防晒霜……

发生了什么?

这 30 行代码背后,Agent 完成了一次完整的 ReAct 循环:

用户消息
    ↓
[思考] LLM 判断:需要查天气才能回答
    ↓
[行动] 调用 get_weather(city="北京")
    ↓
[观察] 工具返回:北京今天晴,25°C
    ↓
[思考] LLM 结合观察生成最终回复
    ↓
流式输出给你

react.NewAgent 把这个循环封装好了。你给它 ChatModel 和 Tool,它处理"什么时候调工具、调什么、把结果填回上下文再想一轮"这些细节。


utils.InferTool 做了什么

你写的是一个普通 Go 函数 func(context.Context, *WeatherParams) (string, error)utils.InferTool 把它变成 Agent 认识的 tool.InvokableTool

  1. 用反射读取 WeatherParams 的字段和 jsonschema_description tag,生成 JSON Schema
  2. 把这个 Schema 告诉 LLM:"这个工具接受 {city: string} 参数"
  3. 当 LLM 决定调工具时,把 LLM 返回的 JSON 参数反序列化成 *WeatherParams,调你的函数

你只需要关心业务逻辑,不需要手写 JSON Schema。


三行改动就能换模型

想换成 GPT-4o?

BaseURL: "",                    // 删掉 BaseURL,用默认
Model:   "gpt-4o",
APIKey:  os.Getenv("OPENAI_API_KEY"),

想换成 Claude?后面会专门讲。想本地跑 Llama?E42 会讲 Ollama 接入。模型切换不影响工具和 Agent 逻辑。


完整代码清单

my-first-agent/
├── go.mod
└── main.go   ← 全部 70 行(含注释)

不需要 YAML,不需要配置框架,不需要数据库。这就是 Eino 的最小可运行形态。


小结

一个可用的 AI Agent = ChatModel(大脑) + Tool(手) + ReAct 循环(神经)

Eino 把 ReAct 循环封在 react.NewAgent 里,你只需要:

  1. openai.NewChatModel 接上模型
  2. utils.InferTool 包一个函数成工具
  3. react.NewAgent 组装起来
  4. agent.Stream 发消息、收回复

下一篇讲 Go 项目怎么组织——从脚本式的单文件,到 DDD 4 层结构,什么时候该分层、什么时候过度设计。


代码来源:eino-examples quickstart