【Eino 框架入门】用 Agent 实现多轮对话
【Eino 框架入门】用 Agent 实现多轮对话
上一篇用 ChatModel 调大模型,每次都是新对话,模型记不住你说过什么。这篇用 Agent + Runner 实现真正的多轮对话。
跟上一篇的区别
| ChatModel (ch01) | Agent + Runner (ch02) | |
|---|---|---|
| 定位 | 底层组件 | 上层封装 |
| 对话 | 每次独立 | 多轮连续 |
| 输出 | StreamReader | 事件流 |
打个比方:ChatModel 像直接发 HTTP 请求,Agent 像用一个封装好的 SDK,帮你处理了 system prompt、输出格式这些事。
核心代码
// 1. 创建 Agent(封装 ChatModel) agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ Instruction: "你是个助手", // 自动变成 system prompt Model: cm, }) // 2. 创建 Runner runner := adk.NewRunner(ctx, adk.RunnerConfig{ Agent: agent, EnableStreaming: true, }) // 3. 多轮对话 history := []*schema.Message{} for { history = append(history, schema.UserMessage(input)) events := runner.Run(ctx, history) reply := collectReply(events) history = append(history, schema.AssistantMessage(reply, nil)) }
Agent 封装了什么
Agent 就是把 ChatModel 包了一层,帮你做几件事:
自动加 system prompt。Instruction 字段会自动变成 SystemMessage,不用自己构造。
统一输出格式。不管流式还是非流式,都走事件流,处理逻辑统一。
预留扩展点。以后加工具(Tool)、中间件,接口不用改。
// 这两段代码等价: // ch01 的写法 messages := []*schema.Message{ schema.SystemMessage("你是个助手"), schema.UserMessage("你好"), } stream, _ := cm.Stream(ctx, messages) // ch02 的写法 agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ Instruction: "你是个助手", Model: cm, }) // Run 的时候只要传 UserMessage,SystemMessage 自动加
Runner 是什么
Runner 是 Agent 的执行器。你给它 Agent 和输入,它跑起来,返回事件流。
为什么要 Runner 而不是直接调 Agent?因为 Runner 管了生命周期、状态、事件分发这些事,你不用关心。
runner := adk.NewRunner(ctx, adk.RunnerConfig{ Agent: agent, EnableStreaming: true, // 启用流式输出 }) events := runner.Run(ctx, history) // 返回事件流
多轮对话的本质
Agent 本身不存历史。多轮对话的关键是 history,你每次 Run() 时把完整历史传进去:
// 第一轮 history = [UserMessage("你好")] // 模型回复 "你好!" // 第二轮 history = [ UserMessage("你好"), AssistantMessage("你好!", nil), UserMessage("我刚才说了什么?"), ] // 模型能看到完整历史,回复 "你说了你好"
所以多轮对话的本质就是:每次调用都把历史带上。history 越来越长,模型看到的上下文就越来越多。
事件流怎么读
Runner 返回的是事件流,不是直接的文本。事件流就像一个管道,Agent 干完一件事就发个事件过来。
events := runner.Run(ctx, history) for { event, ok := events.Next() if !ok { break // 流结束了 } if event.Err != nil { // 出错了 } mv := event.Output.MessageOutput if mv.Role != schema.Assistant { continue // 跳过非 assistant 消息 } if mv.IsStreaming { // 流式:逐帧读取 for { frame, err := mv.MessageStream.Recv() if errors.Is(err, io.EOF) { break } fmt.Print(frame.Content) } } else { // 非流式:直接读 fmt.Print(mv.Message.Content) } }
为什么要事件流:以后加了工具调用,你也能从同一个流里收到工具事件,不用改处理逻辑。一个流,所有输出统一处理。
AgentEvent 里有什么
AgentEvent ├── Err // 错误 └── Output └── MessageOutput ├── Role // Assistant / User / System / Tool ├── IsStreaming // 是否流式 ├── Message // 非流式时的完整消息 └── MessageStream // 流式时的 StreamReader
以后还可能加 ToolCall、Action 等字段,处理工具调用和控制指令。
什么时候用 Agent
简单对话用 ChatModel 够了。但如果你后续要:
- 加工具调用(让模型能查数据库、调 API)
- 加中断恢复(长时间任务中间停掉,下次继续)
- 加监控追踪(看模型调了几次、花了多少钱)
就用 Agent,它预留了这些扩展点。
更多推荐



所有评论(0)