5 分钟跑通你的第一个 AI Agent
系列「企业级 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 倍以上,特别适合调试。
第一步:初始化项目
mkdirmy-first-agent&&cdmy-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@latesteino是核心框架,eino-ext里装了各个模型的驱动,这里用openai包驱动 DeepSeek(两者 API 格式相同)。
第二步:写一个 Tool
Tool 是 Agent 能调用的"工具"——类似函数,但 Agent 会根据任务自己决定要不要调它、怎么调。
新建main.go:
packagemainimport("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 描述typeWeatherParamsstruct{Citystring`json:"city" jsonschema_description:"城市名,如 北京、上海"`}// 工具的执行逻辑:返回 (结果字符串, error)funcgetWeather(_context.Context,p*WeatherParams)(string,error){// 这里可以调真实天气 API,演示先硬编码returnfmt.Sprintf("%s 今天晴,气温 25°C,适合出行。",p.City),nil}工具定义只需要三样东西:输入结构体、执行函数、一个名字和描述。
第三步:创建 ChatModel
funcnewChatModel(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"),// 从环境变量读,别写死在代码里})iferr!=nil{panic(err)}returncm}openai.NewChatModel本来是给 GPT 用的,但因为 DeepSeek 的接口格式一样,换个BaseURL就能用。这就是"接口兼容"带来的好处。
第四步:把 ChatModel + Tool 组成 Agent
funcmain(){ctx:=context.Background()// 1. 把函数包成 ToolweatherTool,err:=utils.InferTool("get_weather",// 工具名(Agent 会看到这个名字)"查询指定城市的天气",// 工具描述(Agent 用它决定什么时候调)getWeather,)iferr!=nil{panic(err)}// 2. 创建 ChatModelchatModel:=newChatModel(ctx)// 3. 用 react.NewAgent 把两者组装成一个 ReAct Agentagent,err:=react.NewAgent(ctx,&react.AgentConfig{ToolCallingModel:chatModel,ToolsConfig:compose.ToolsNodeConfig{Tools:[]tool.BaseTool{weatherTool},},})iferr!=nil{panic(err)}// 4. 发一条消息,用 Stream 得到流式回复sr,err:=agent.Stream(ctx,[]*schema.Message{{Role:schema.User,Content:"北京今天天气怎么样?适合去颐和园吗?"},})iferr!=nil{panic(err)}defersr.Close()// 5. 逐 token 打印(打字机效果)for{msg,err:=sr.Recv()iferr==io.EOF{break}iferr!=nil{panic(err)}fmt.Print(msg.Content)// 不换行,token 一个个蹦出来}fmt.Println()}运行
exportDEEPSEEK_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:
- 用反射读取
WeatherParams的字段和jsonschema_descriptiontag,生成 JSON Schema - 把这个 Schema 告诉 LLM:“这个工具接受
{city: string}参数” - 当 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里,你只需要:
openai.NewChatModel接上模型utils.InferTool包一个函数成工具react.NewAgent组装起来agent.Stream发消息、收回复
下一篇讲 Go 项目怎么组织——从脚本式的单文件,到 DDD 4 层结构,什么时候该分层、什么时候过度设计。
代码来源:eino-examples quickstart