Eino学习笔记 —— Agentic 进阶
Eino学习笔记 —— Agentic 高级功能
本笔记通过ai辅助生成,但对于代码阅读顺序应当没有问题(均使用eino官方的例子进行,个人认为官方的教程感觉有点太难懂了)
Eino Agentic 高级功能指南
📁 代码来源:
adk/agentic/research_assistant/— AgenticModel + AgenticMessage 全功能研究助手adk/agentic/retry_max_output_tokens/— 输出截断自动重试📖 前置知识:本指南假设你已经掌握 入门笔记 中一到五节的内容。
速览:ChatModel → AgenticModel 类型映射表(先看这里!)
在深入细节之前,先把核心映射记住——Agentic 就是所有类型名字加 Typed、所有 Message 换成 AgenticMessage:
| 普通版(Chat Completions) | Agentic 版(Responses API) |
|---|---|
openai.NewChatModel() | agenticopenai.New() / agenticark.New() |
schema.UserMessage("...") | schema.UserAgenticMessage("...") |
adk.NewChatModelAgent(...) | adk.NewTypedChatModelAgent[*schema.AgenticMessage](...) |
adk.NewRunner(...) | adk.NewTypedRunner[*schema.AgenticMessage](...) |
event.Output.MessageOutput.GetMessage() | adk.TypedGetMessage(event) |
adk.ChatModelAgentMiddleware | adk.TypedChatModelAgentMiddleware[*schema.AgenticMessage] |
adk.ModelRetryConfig | adk.TypedModelRetryConfig[*schema.AgenticMessage] |
filesystem.New(...) | filesystem.NewTyped[*schema.AgenticMessage](...) |
🧠 一句话总结:Agentic 系列是 Eino 为 Responses API 设计的完整封装——用
AgenticModel替代ChatModel,用AgenticMessage替代Message,其余概念(Agent / Runner / Middleware / Tool)的用法保持一致,但全部使用 Typed 泛型变体。
背景:Chat Completions API → Responses API
在 入门笔记 中,所有代码都基于 Chat Completions API(ChatModel + *schema.Message)。这是 OpenAI 2022 年推出的 API 形态。
但从 2025 年起,主流模型厂商开始推 Responses API,这是新一代 API 形态。两者的核心区别:
| 维度 | Chat Completions API | Responses API |
|---|---|---|
| 消息类型 | *schema.Message(单一角色+内容) | *schema.AgenticMessage(含 ContentBlocks) |
| 模型接口 | einoModel.ChatModel | einoModel.AgenticModel |
| Agent 类型 | adk.NewChatModelAgent | adk.NewTypedChatModelAgent[*schema.AgenticMessage] |
| Runner 类型 | adk.NewRunner | adk.NewTypedRunner[*schema.AgenticMessage] |
| 一次调用返回 | 一条文本回复 | 多个有序结构化事件(推理→工具调用→再推理→文本) |
| 服务端工具 | ❌ 全在客户端执行 | ✅ 模型厂商侧直接执行(如 web_search) |
| 输出截断检测 | 无标准信号 | status=incomplete + reason=max_output_tokens |
一、核心概念:AgenticMessage 与 ContentBlock
1.1 为什么需要 AgenticMessage?
在 Chat Completions API 中,一次模型调用只返回一条消息。Responses API 在一次调用中返回所有结构化的中间步骤。要承载这种”多事件”的返回,原来的 *schema.Message(只有一个 Role + Content 字符串)不够用了:
ChatModel 返回: ┌──────────────────────────┐ │ Role: Assistant │ │ Content: "答案是42" │ └──────────────────────────┘ 只有一条扁平消息
AgenticModel ┌──────────────────────────────────────────────┐返回: │ Role: Assistant │ │ ContentBlocks: [ │ │ [0] type: reasoning │ │ text: "我需要先搜索..." │ │ [1] type: server_tool_call │ │ name: web_search │ │ [2] type: reasoning │ │ text: "搜索结果显示..." │ │ [3] type: text │ │ text: "根据分析,答案是42" │ │ ] │ └──────────────────────────────────────────────┘ 一条消息里装了多个有序的结构化 Block1.2 ContentBlock 类型速查
每个 Block 有一个 Type 字段表示类别:
| Block 类型 | 含义 | 谁产生 | 出现在哪条消息里 |
|---|---|---|---|
reasoning | 模型的推理/思考过程 | 模型 | Assistant 消息 |
text | 普通文本输出 | 模型 | Assistant 消息 |
server_tool_call | 调用服务端工具(如 web_search) | 模型 | Assistant 消息 |
function_tool_call | 调用客户端本地工具 | 模型 | Assistant 消息 |
function_tool_result | 本地工具执行结果 | 框架 | User 消息(反馈给模型) |
thinking | 深度思考内容 | 模型 | Assistant 消息 |
🧠 关键理解:
server_tool_call在模型厂商的服务器上执行(你的代码看不到执行过程),function_tool_call返回给你的 Agent 本地执行。
二、AgenticModel:连接 Responses API
2.1 两种厂商接入
import ( einoModel "github.com/cloudwego/eino/components/model" "github.com/cloudwego/eino-ext/components/model/agenticark" // 火山方舟 ARK "github.com/cloudwego/eino-ext/components/model/agenticopenai" // OpenAI)
// ARKmodel, err := agenticark.New(ctx, &agenticark.Config{ APIKey: os.Getenv("ARK_API_KEY"), Model: os.Getenv("ARK_MODEL_ID"), BaseURL: os.Getenv("ARK_BASE_URL"),})
// OpenAImodel, err := agenticopenai.New(ctx, &agenticopenai.Config{ APIKey: os.Getenv("OPENAI_API_KEY"), Model: os.Getenv("OPENAI_MODEL_ID"), BaseURL: os.Getenv("OPENAI_BASE_URL"),})2.2 服务端工具:Web Search
不需要自己写搜索 Tool——直接声明即可,搜索在模型厂商侧完成:
// ARK 服务端 web_searchagenticark.WithServerTools([]*agenticark.ServerToolConfig{ {WebSearch: &arkResponses.ToolWebSearch{ Type: arkResponses.ToolType_web_search, Limit: ptrOf[int64](6), }},})
// OpenAI 服务端 web_searchagenticopenai.WithServerTools([]*agenticopenai.ServerToolConfig{ {WebSearch: &openaiResponses.WebSearchToolParam{ Type: openaiResponses.WebSearchToolTypeWebSearch, }},})💡 服务端 vs 本地工具:服务端工具的调用和执行都在模型厂商侧,你的代码只会在
content_blocks中看到一个server_tool_callblock,不会有对应的function_tool_result——结果被模型直接在内部消费了。
三、Typed 泛型体系
3.1 完整组装示例(与 入门笔记 §3 对照)
和 入门笔记 第三节的 Agent 组装步骤一模一样,只是类型都换成了 Typed + AgenticMessage:
func newResearchAssistant(ctx context.Context) (adk.TypedAgent[*schema.AgenticMessage], error) { // ─── 第1步:创建 AgenticModel ─── agenticModel, _ := newAgenticModel(ctx)
// ─── 第2步:创建本地工具(和之前一模一样,InferTool)─── tools, _ := buildTools()
// ─── 第3步:创建 TypedChatModelAgent ─── agent, err := adk.NewTypedChatModelAgent[*schema.AgenticMessage](ctx, &adk.TypedChatModelAgentConfig[*schema.AgenticMessage]{ Name: "AgenticResearchAssistant", Instruction: "...", Model: agenticModel, // ← AgenticModel ToolsConfig: adk.ToolsConfig{ // ← 和 ChatModel 版本一样! ToolsNodeConfig: compose.ToolsNodeConfig{ Tools: tools, }, }, MaxIterations: 8, }) return agent, err}其余逻辑(Middleware 等)和 ChatModel 版本完全一样,只需加 Typed 前缀
3.2 TypedRunner 的使用方式
// 创建(和之前一样的模式)runner := adk.NewTypedRunner[*schema.AgenticMessage](adk.TypedRunnerConfig[*schema.AgenticMessage]{ Agent: agent, EnableStreaming: true,})
// 构造输入(用 UserAgenticMessage 替代 UserMessage)input := schema.UserAgenticMessage("请写一份研究报告...")
// 运行iter := runner.Run(ctx, []*schema.AgenticMessage{input})
// 消费事件 —— ⚠️ 关键变化:用 TypedGetMessagefor { event, ok := iter.Next() if !ok { break } msg, _, err := adk.TypedGetMessage(event) // ← 不是 GetMessage() if msg != nil { fmt.Print(msg.String()) }}🧠 与 入门笔记 的对比:
// 旧:ChatModel 路径msg, err := event.Output.MessageOutput.GetMessage()// 新:AgenticModel 路径msg, typedagentevent, err := adk.TypedGetMessage(event)
四、ModelRetryConfig:输出截断自动重试
4.1 问题场景
Responses API 中对每次调用有 max_output_tokens 限制。设置太小时输出被截断:
status=incompleteincomplete_details.reason=max_output_tokens4.2 解决方案
Eino ADK 提供 TypedModelRetryConfig,自动丢弃不完整输出、调大 budget、重新调用:
var retryMaxTokens = []int{4096, 8192, 16384}
ModelRetryConfig: &adk.TypedModelRetryConfig[*schema.AgenticMessage]{ MaxRetries: len(retryMaxTokens), ShouldRetry: func(ctx context.Context, retryCtx *adk.TypedRetryContext[*schema.AgenticMessage]) *adk.TypedRetryDecision[*schema.AgenticMessage] { if !isMaxOutputTokensIncomplete(retryCtx.OutputMessage) { return nil // 不需要重试 } nextMaxTokens := retryMaxTokens[retryCtx.RetryAttempt-1] return &adk.TypedRetryDecision[*schema.AgenticMessage]{ Retry: true, AdditionalOptions: []einoModel.Option{einoModel.WithMaxTokens(nextMaxTokens)}, Backoff: 100 * time.Millisecond, } },},4.3 与 入门笔记 §4.4 节 ModelRetryConfig 的对比
| 维度 | ChatModel 版 | Agentic 版 |
|---|---|---|
| 类型 | adk.ModelRetryConfig | adk.TypedModelRetryConfig[*schema.AgenticMessage] |
| 检测依据 | err(HTTP 错误) | OutputMessage.ResponseMeta(响应状态) |
| 重试方式 | 原样重试 | 调大 max_output_tokens 后重试 |
| 适用场景 | 限流、网络错误 | 输出截断 |
五、Agentic 全景速查
5.1 新增能力
| 能力 | ChatModel 有吗 | AgenticModel 如何实现 |
|---|---|---|
| 推理过程可见 | ❌ | reasoning ContentBlock |
| 服务端工具 | ❌ | server_tool_call + WithServerTools |
| 输出截断检测 | 间接(finish_reason) | 直接:status=incomplete |
| 一次调用多事件 | ❌ 需多次往返 | ✅ 单次调用返回多个 ContentBlock |
| 深度思考 | ❌ | thinking ContentBlock + WithReasoning |
5.2 常用 import 路径
// AgenticModel 实现"github.com/cloudwego/eino-ext/components/model/agenticopenai" // OpenAI Responses API"github.com/cloudwego/eino-ext/components/model/agenticark" // 火山方舟 ARK
// 核心接口"github.com/cloudwego/eino/components/model" // AgenticModel 接口"github.com/cloudwego/eino/schema" // AgenticMessage, ContentBlock"github.com/cloudwego/eino/adk" // TypedChatModelAgent, TypedRunner, TypedModelRetryConfig
// 中间件(Typed 版)"github.com/cloudwego/eino/adk/middlewares/filesystem" // filesystem.NewTyped
// 后端"github.com/cloudwego/eino-ext/adk/backend/local" // localbackend.NewBackend5.3 总结:什么时候该用 Agentic?
你的场景 → 用什么─────────────────────────────────────────────────────────简单问答、一次性调用 → ChatModel([入门笔记](./note) §2)多轮对话 + 本地工具 + 中断恢复 → ChatModelAgent([入门笔记](./note) §3)需要服务端搜索(web_search) → AgenticModel ✅需要看到模型的推理过程 → AgenticModel ✅需要处理输出截断自动重试 → AgenticModel ✅需要一次调用中混合推理+工具+文本 → AgenticModel ✅现有 ChatModelAgent 代码跑得好好的 → 不用迁移,除非需要上述特性🧠 渐进式采用:Agentic 并不是要替代 ChatModel——它们是两套并行的体系。当需要高级特性时,迁移路径也很清晰:把类型从
ChatModel/Message换成AgenticModel/AgenticMessage,其余逻辑基本不变。
📇 本篇速记卡片
见到 Typed 就用 AgenticMessage: NewChatModelAgent → NewTypedChatModelAgent[*AgenticMessage] NewRunner → NewTypedRunner[*AgenticMessage] GetMessage() → TypedGetMessage() ModelRetryConfig → TypedModelRetryConfig filesystem.New → filesystem.NewTyped[*AgenticMessage]
ContentBlock 六种类型:reasoning / text / server_tool_call function_tool_call / function_tool_result / thinking
新增能力: reasoning Block(推理可见)、server_tool_call(服务端工具) web_search(不用自己写搜索Tool)、输出截断检测+重试📂 源码仓库:github.com/cloudwego/eino-examples↗
📖 继续阅读:
- 入门笔记 — Eino ADK 从 Hello World 到 Compose 编排
- Agentic 进阶 — Responses API / AgenticMessage / Typed 泛型 ← 你在这里
- 附录一:Flow 流程模块 — ReAct Agent / Multi-Agent / 状态图
- 附录二:Components 组件模块 — A/B 路由 / HTTP 日志 / 检索增强 / 文档解析
- 附录三:Lambda 与调试工具 — Lambda 写法 / Devops / Mermaid 可视化