Eino ADK——Agent 的完整生命周期:从创建到中断恢复(第98篇-E84)

作者:leeyi日期:2026/8/29

上一篇 讲了数据库迁移怎么不翻车。但 Agent 本身是怎么跑起来的?创建、执行、工具调用、中断、恢复——这些生命周期节点 Eino ADK 怎么管?

这篇拆解 Eino ADK(Agent Development Kit)的 Agent 完整生命周期。从 NewChatModelAgentRunner.Run,从 ReAct 循环到 Interrupt/Resume。源码在 eino/adk/ 下,核心文件四个:interface.gochatmodel.gorunner.goflow.go

为什么需要 ADK

直接用 Eino 的 compose.Graph 也能跑 Agent,但每个 Agent 都要手写 Graph、管消息历史、处理工具调用、做错误恢复。ADK 把这些封装成可复用的 Agent 抽象,一行 NewChatModelAgent + 一行 runner.Query 就能跑。

(一)Agent 是什么:TypedAgent 接口

adk/interface.go:453-464 定义了 Agent 的最小契约:

1type TypedAgent[M MessageType] interface {
2    Name(ctx context.Context) string
3    Description(ctx context.Context) string
4    Run(ctx context.Context, input *TypedAgentInput[M], options ...AgentRunOption) *AsyncIterator[*TypedAgentEvent[M]]
5}
6

三个方法:

  • Name:Agent 名字,用于日志、回调、多 Agent 编排
  • Description:Agent 描述,其他 Agent 通过它判断该不该 transfer
  • Run:执行入口,返回 AsyncIterator——事件流,不是一次性结果

AsyncIterator 是关键设计。Agent 不是"输入→输出"的黑盒,而是"输入→事件流"的管道。每个事件可以是:

  • 模型回复(Output.Message
  • 工具调用/结果(Output.Role == Tool
  • 中断请求(Action.Interrupted
  • 转移请求(Action.TransferToAgent
  • 退出(Action.Exit
  • 错误(Err

事件结构定义在 adk/interface.go:419-435

1type TypedAgentEvent[M MessageType] struct {
2    AgentName string
3    RunPath   []RunStep
4    Output    *TypedAgentOutput[M]
5    Action    *AgentAction
6    Err       error
7}
8

(二)ChatModelAgent:最常用的 Agent 实现

ChatModelAgent 是 ADK 的默认实现,封装了 ReAct 循环。创建方式在 adk/chatmodel.go:502-504

1func NewChatModelAgent(ctx context.Context, config *ChatModelAgentConfig) (*ChatModelAgent, error)
2

配置项在 adk/chatmodel.go:260-369,核心字段:

字段作用默认值
NameAgent 名称
DescriptionAgent 描述
Instruction系统提示词
Model聊天模型必填
ToolsConfig工具配置
MaxIterations最大迭代次数20
GenModelInput输入转换函数默认拼接 instruction + messages
Middlewares中间件(已废弃)
Handlers新版中间件

最简创建:

1agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
2    Name:  "MyAgent",
3    Model: myModel,
4})
5

(三)ReAct 循环:Agent 怎么"思考→行动→再思考"

ChatModelAgent 的核心是 ReAct 循环。状态定义在 adk/react.go:35-57

1type typedState[M MessageType] struct {
2    Messages              []M
3    ToolInfos             []*schema.ToolInfo
4    HasReturnDirectly     bool
5    ReturnDirectlyToolCallID string
6    AgentName             string
7    RemainingIterations   int
8    // ...
9}
10

执行流程:

  1. 构建初始消息:system prompt(Instruction)+ 用户输入
  2. 调用 ChatModel:传入消息历史 + 工具列表
  3. 检查响应
    • 无工具调用 → 最终回复,返回事件流
    • 有工具调用 → 执行工具,将结果追加到消息历史
  4. 循环:回到步骤 2,直到无工具调用或超过 MaxIterations

超过 MaxIterations 时返回 ErrExceedMaxIterationsadk/react.go:33):

1var ErrExceedMaxIterations = errors.New("exceeds max iterations")
2

(四)Runner:Agent 的执行器

Runner 是 Agent 的执行器,管理 checkpoint、streaming 模式、中断恢复。定义在 adk/runner.go:50-59

1type TypedRunner[M MessageType] struct {
2    a               TypedAgent[M]
3    enableStreaming bool
4    store           CheckPointStore
5}
6

创建和运行在 adk/runner.go:94-115

1// 创建
2func NewTypedRunner[M MessageType](conf TypedRunnerConfig[M]) *TypedRunner[M]
3
4// 执行
5func (r *TypedRunner[M]) Run(ctx context.Context, messages []M, opts ...AgentRunOption) *AsyncIterator[*TypedAgentEvent[M]]
6
7// 便捷方法:字符串查询
8func (r *TypedRunner[M]) Query(ctx context.Context, query string, opts ...AgentRunOption) *AsyncIterator[*TypedAgentEvent[M]]
9

QueryRun 的语法糖,内部把字符串包装成一条用户消息后调用 Run

消费事件流:

1iter := runner.Query(ctx, "今天天气怎么样?")
2for {
3    event, ok := iter.Next()
4    if !ok {
5        break
6    }
7    if event.Err != nil {
8        // 处理错误
9    }
10    if event.Output != nil {
11        // 处理输出
12    }
13}
14

(五)Interrupt / Resume:Agent 的中断与恢复

Agent 不是每次都能一口气跑完。需要人工确认、等待外部输入、审批——这些场景需要中断机制。

中断:Agent 返回 Action.Interrupted = true 的事件,暂停执行。

恢复:调用 Runner.ResumeRunner.ResumeWithParams,携带恢复数据继续。

中断信息定义在 adk/interrupt.go:33-55

1type ResumeInfo struct {
2    EnableStreaming bool
3    *InterruptInfo
4    WasInterrupted bool
5    InterruptState any
6    IsResumeTarget bool
7    ResumeData     any
8}
9
10type InterruptInfo struct {
11    Data             any
12    InterruptContexts []*InterruptCtx
13}
14

Runner 的 Resume 在 adk/runner.go:124-149

1// 简单恢复:所有中断点都恢复
2func (r *TypedRunner[M]) Resume(ctx context.Context, checkPointID string, opts ...AgentRunOption) (*AsyncIterator[*TypedAgentEvent[M]], error)
3
4// 精确恢复:指定恢复目标和数据
5func (r *TypedRunner[M]) ResumeWithParams(ctx context.Context, checkPointID string, params *ResumeParams, opts ...AgentRunOption) (*AsyncIterator[*TypedAgentEvent[M]], error)
6

流程:

  1. Agent 执行中调用 TypedInterrupt 返回中断事件
  2. Runner 检测到 Action.Interrupted,保存 checkpoint
  3. 外部系统处理中断(人工审批等)
  4. 调用 Resume/ResumeWithParams 携带数据继续
  5. Agent 从 checkpoint 恢复状态,继续执行

(六)FlowAgent:多 Agent 编排

FlowAgent 是 ADK 的多 Agent 编排层。定义在 adk/flow.go:42-52

1type flowAgent struct {
2    Agent
3    subAgents                []*flowAgent
4    parentAgent              *flowAgent
5    disallowTransferToParent bool
6    historyRewriter          HistoryRewriter
7    checkPointStore          compose.CheckPointStore
8}
9

关键能力:

  • 子 Agent 注册SetSubAgents 把 Agent 注入到编排树
  • Transfer:Agent 可以把任务转交给子 Agent 或父 Agent
  • History Rewriting:Transfer 时重写消息历史,把上一个 Agent 的回复改写成用户消息
  • Checkpoint:支持中断恢复的持久化

注意:源码多处标注"NOT RECOMMENDED",建议用 AgentToolDeepAgent 替代 Transfer 模式。Agent 间全量上下文共享在实践中效果不如预期。

(七)生命周期状态机

ChatModelAgent 内部有 frozen 标记(sync.Once 保证只初始化一次)和懒加载机制。但对外暴露的生命周期可以简化为:

1Created  Running  Completed
2                   Interrupted  Running  Completed
3                   Error
4
  • CreatedNewChatModelAgent 返回,未冻结
  • Running:首次 Run 调用触发 sync.Once 初始化,进入执行
  • Completed:模型返回无工具调用的最终回复
  • Interrupted:Agent 发出中断事件,等待外部输入
  • Error:超过 MaxIterations、模型错误、工具错误

(八)事件流:AsyncIterator 的设计

ADK 没有用 channel,而是用 AsyncIterator 模式。好处:

  • 不依赖 goroutine 通信
  • 消费端可以同步迭代
  • 支持 Next() 阻塞等待

消费模式:

1iter := agent.Run(ctx, input)
2for {
3    event, ok := iter.Next()
4    if !ok {
5        break
6    }
7    switch {
8    case event.Err != nil:
9        // 错误
10    case event.Action != nil && event.Action.Interrupted:
11        // 中断
12    case event.Output != nil:
13        // 输出
14    }
15}
16

小结

Eino ADK 的 Agent 生命周期用四个核心概念覆盖:

  1. TypedAgent 接口:Name + Description + Run → AsyncIterator
  2. ChatModelAgent:封装 ReAct 循环,工具调用,迭代限制
  3. Runner:管理执行、checkpoint、streaming、中断恢复
  4. FlowAgent:多 Agent 编排、transfer、history rewriting

结果是:一行 NewChatModelAgent + 一行 runner.Query,Agent 就能跑起来。中断恢复、事件流、多 Agent 编排——ADK 全管了。

下一篇(E99)讲两个"周边件":ACP 协议怎么把 Eino Agent 桥接到外部编辑器,devops 模块怎么把编译后的 Graph 变成可视化画布。


Eino ADK——Agent 的完整生命周期:从创建到中断恢复(第98篇-E84)》 是转载文章,点击查看原文


相关推荐


Rust 泛型 Generics详解:编写可复用且类型安全的代码
程序员爱钓鱼2026/8/21

《Rust编程实战》系列第47篇 上一篇文章中,我们学习了Rust的Trait Object,了解了dyn Trait、Box<dyn Trait>以及动态分发。本篇继续学习Rust类型系统中另一项非常核心的能力: Generics 中文通常称为“泛型”。 泛型解决的问题可以简单理解为: 在不知道具体类型的情况下,编写可以复用于多种类型的代码。 假设我们需要分别查找i32、f64中的最大值,如果不用泛型,可能会写: fn max_i32(a: i32, b: i32) -> i32 {


数字孪生3D可视化有哪些?主流实时渲染平台深度测评
anxiao_m2026/8/7

随着产业数字化推进,数字孪生3D可视化逐步落地智能制造、智慧城市、园区管控等场景。不少项目负责人在选型阶段产生困惑:市面上可用的数字孪生3D可视化方案分为哪些类型,搭载实时渲染能力的服务商又该如何筛选。 本文结合行业通用评估维度,梳理主流解决方案,对比各方案适配场景,帮助需求方避开选型误区,找到匹配自身项目的可视化底座。 一、数字孪生3D可视化方案评测标准 本次榜单排序依据五项核心维度综合打分,每项标准具备明确选型参考价值: 实时渲染性能:决定大体量三维模型能否流畅交互,是区分静态沙


玻尔兹曼机光谱数据处理实战:细菌拉曼光谱上的RBM少标签分类
nnerddboy2026/7/29

本文是一篇“光谱数据处理 + 受限玻尔兹曼机(Restricted Boltzmann Machine, RBM)”的实战讲解稿。它不重新运行你的脚本,而是基于你提供的真实结果文件、脚本和 5 张图,把整条链路从零讲清楚:数据是什么、为什么要这样预处理、RBM 到底学到了什么、少标签实验为什么这样设计、每张图应该怎么读、结果说明了什么、如果自己动手复现要注意什么。完整代码在文末 0.前言 这个案例解决的是一个非常典型、也非常实际的问题:光谱很多,标签很少。 你手里的数据是真实单细胞


【线性代数】图形学基础:齐次坐标与矩阵变换
h_a_o777oah2026/7/21

计算机图形学涉及很多知识,比如线性代数和微积分等等。在学习图形学知识前,我们应该要对这些基础知识有一个了解。下面介绍一下相关的基础线性代数知识。 首先我们要知道,线性代数中的矩阵是可以变换坐标系里点的位置的。而比如  和  分别表示二维或者三维空间中点或者向量的坐标,也可以写成  或者  。如果有对于矩阵乘法不理解的,可以查看我此前的一个矩阵快速幂博客,其中有一段是对于矩阵乘法的解析。链接:【算法详解】矩阵快速幂:如何将线性方程组映射为转移矩阵解决线性递推-CSDN博客 齐次坐标 我们知道


LeetCode 150. 逆波兰表达式求值
Best_Jerry2026/7/13

leetcode.cn/problems/ev… programmercarl.com/0150.%E9%80… 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意: 有效的算符为 '+'、'-'、'*' 和 '/' 。 每个操作数(运算对象)都可以是一个整数或者另一个表达式。 两个整数之间的除法总是 向零截断 。 表达式中不含除零运算。 输入是一个根据逆波兰表示法表示的算术表达式。 答案及所有中间计算结果可


【系统架构设计师】论文篇
忻州人在郑州2026/7/5

文章目录 前言总体方向浅谈论文要求及准备方法如何找项目具体论文各部分准备通用摘要模板通用项目背景与本人职责通用点题句中间段1. 论微服务架构及其应用2. 论云原生架构及其应用3. 论大数据架构及其应用4. 论系统架构评估5. 论软件测试方法及应用6. 论需求分析方法及应用7. 论软件开发模型及应用8. 负载均衡9. 事件驱动架构 万能句1. 需求与变更控制2. 敏捷开发3. 测试保障4. 运维治理 通用结尾 参考资料 前言 ​ 本系列文章为软考系统架构设计师笔记


Flutter版本选择指南:3.44系列继续观望 | 2026年6月
程序员老刘2026/6/27

大家好,我是老刘 每次Flutter发新版本,群里总有人问:"老刘,3.44.3出来了,我能升了吗?" 我的回答永远不变:再等等。 不是我不信任Google的修复速度。事实上,3.44系列这一个月发三个补丁,节奏还算克制。但是,补丁版本的稳定不等于生产环境的可靠,尤其是3.44这一版动了SwiftPM和Android底层架构,第三方插件的适配进度至今没有一个明确的说法。 今天这篇,我把6月所有版本状态、修复内容和选择策略一次性讲清楚,帮你做出不后悔的决定。 一、6月Flutter大事件 Flu


PyCharm 中调用项目内文件夹的 Python 文件方法
6v6-博客网2026/6/18

PyCharm 中调用项目内文件夹的 Python 文件方法 在 PyCharm 中调用另一个文件夹中的 .py 文件,本质上是解决 Python 的模块导入路径问题。默认情况下,Python 只会搜索当前目录和系统库,直接导入子文件夹里的模块会报 ModuleNotFoundError。以下提供三种最有效的解决方案。 方法一:将文件夹标记为「源根」(最推荐) 这是 PyCharm 独有的便捷功能,无需修改代码即可让 IDE 识别路径。 在项目侧边栏中,右键点击目标文件夹(比如 utils)。选


workbuddy ,node.js 每次会在 项目目录上安装 node_modules,能不能一次安装多次使用,为什么 npm 不把包装在全局
zhangfeng11332026/6/10

workbuddy ,node.js 每次会在 项目目录上安装 \node_modules ,而不是把他放到node.js的安装目录 或者系统的path 中导致 ,类似的功能 都要载入 安装 程序包 很慢 你的问题核心是:npm 默认把包安装在项目本地 node_modules,每次新建项目都要重新 npm install,耗时且冗余。这是 npm 的设计哲学问题,我来解释清楚并提供解决方案。 为什么 npm 不把包装在全局? npm 的设计原则是 依赖隔离: 方式优点缺点项目本地安装(


数据同步的几种姿势
小跟班_mya2026/6/3

先搞清楚一件事:为什么要有 ES 数据同步? 想象一下,你开了一家图书馆: MySQL 是图书管理员,负责把书一本本摆好、登记在册(增删改查、事务保证)Elasticsearch 是图书检索系统,用户输入"三体",0.01 秒就能告诉你第几排第几架(全文检索、聚合分析) 问题来了:管理员刚上架了一本新书,检索系统怎么立刻知道这本书的存在?这就是数据同步要解决的事。 方案一:同步双写——"一边记账一边贴标签" 场景 你开了个电商网站,用户下单后,订单既要存到 MySQL(方便财务对账

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读