【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 的时候只要传 UserMessageSystemMessage 自动加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它预留了这些扩展点。