ADK (Agent Development Kit,智能体开发套件)是 Eino 提供的一套标准化 Agent 开发抽象层,不管你写什么类型智能体(单 Agent、多 Agent 协作、工具调用、循环 Agent),全都统一实现一套 TypedAgent 接口,框架就能统一调度、流式消费、多智能体互通。
所有代码基于 Go 泛型设计,用泛型区分两种消息格式:传统 *schema.Message / 新版结构化 *schema.AgenticMessage。

一、最顶层核心:TypedAgent 接口

type TypedAgent[M MessageType] interface {
    Name(ctx context.Context) string
    Description(ctx context.Context) string
    Run(ctx context.Context, input *TypedAgentInput[M], options ...AgentRunOption) *AsyncIterator[*TypedAgentEvent[M]]
}
type Agent = TypedAgent[*schema.Message] // 日常99%场景用这个别名

三个方法分工极其清晰,类比成「员工」更好理解:

  1. Name():员工姓名,全局唯一标识,多 Agent 调度时用来区分是谁在干活
  2. Description():员工简历 / 能力介绍
    • 框架自动读取;
    • 多 Agent 协作时,别的 Agent 靠这段描述知道能不能把任务交给它;
  3. Run():干活的入口函数,最核心
    • 入参:input 用户对话历史、是否开流式输出;options 本次运行临时配置;
    • 返回:AsyncIterator 异步事件流(源源不断产出执行过程事件)。

MessageType 泛型约束(两种消息模式二选一)


type MessageType interface {
    *schema.Message | *schema.AgenticMessage
}
  • *schema.Message:传统对话消息,文本、工具调用、普通聊天,完整兼容所有 Eino 组件(ChatModel、工具、记忆),日常开发首选,别名 Agent 就是它;
  • *schema.AgenticMessage:v0.9 新结构,结构化内容块,适合复杂多 Agent 分层、强结构化输出场景,偏高级用法。
    文档里所有带 TypedXXX[M] 的结构体,都有不带 Typed 的简写别名,不用每次写长泛型:
泛型完整类型 日常简写别名
TypedAgent[*schema.Message] Agent
TypedAgentInput AgentInput
TypedAgentEvent AgentEvent
TypedAgentOutput AgentOutput
TypedMessageVariant MessageVariant

二、Run 入参:TypedAgentInput / AgentInput(传给 Agent 的任务信息)

type TypedAgentInput[M MessageType] struct {
    Messages       []M
    EnableStreaming bool
}
  1. Messages []M
    对话上下文,等价大模型 Chat 请求:包含用户提问、历史聊天、工具返回结果、系统提示词。Agent 读取这段对话做推理。
  2. EnableStreaming bool
    流式开关:
    • true:尽量分段输出(打字机效果,逐字返回);
    • false:全部推理完一次性返回完整结果;
      不支持流式的 Agent(比如纯工具执行、静态决策 Agent)会自动忽略这个字段。

三、Run 返回:AsyncIterator 异步事件迭代器(流式结果载体)

3.1 作用

Agent.Run 不会阻塞主线程,内部开 goroutine 执行逻辑,一边跑一边不断发送事件 Event,外部通过迭代器循环读取实时消息、工具动作、中断、报错。

3.2 标准使用模板(消费 Agent 输出)

运行
// 启动Agent,拿到事件迭代器
iter := agent.Run(ctx, input)
// 循环读取所有运行事件
for {
    event, ok := iter.Next()
    if !ok {
        // 迭代器关闭,Agent执行完毕,退出循环
        break
    }
    // 处理每一步事件:打印文字、捕获工具调用、监听中断、处理报错
}

iter.Next() 是阻塞调用:没有新事件就卡住,直到有输出 / 执行结束。

3.3 Agent 内部如何生成迭代器?(自定义 Agent 必写模板)

这个迭代器是自定义的

func (m *MyAgent) Run(ctx context.Context, input *adk.AgentInput, opts ...adk.AgentRunOption) *adk.AsyncIterator[*adk.AgentEvent] {
    // 创建一对:迭代器(对外返回) + 生成器(内部发事件)
    iter, gen := adk.NewAsyncIteratorPair[*adk.AgentEvent]()
    go func() {
        defer gen.Close() // Agent逻辑结束自动关闭流
        // 业务逻辑:大模型推理、循环调用工具、多Agent跳转
        // 产出事件发送给迭代器
        gen.Send(&adk.AgentEvent{/*填充事件内容*/})
    }()
    return iter
}

简单理解:gen 是「生产者」往里面塞事件,iter 是「消费者」外部循环读取,goroutine 实现异步不阻塞。

四、核心产出物:TypedAgentEvent / AgentEvent(运行全流程事件)

Run 流里每一条数据都是 Event,记录这一步发生了什么:

type TypedAgentEvent[M MessageType] struct {
    AgentName string                // 当前执行的Agent名字
    RunPath   []RunStep            // 执行链路(记录走了哪些Agent、步骤,用于日志/追踪)
    Output    *TypedAgentOutput[M] // 本轮输出内容(文字/结构化消息)
    Action    *AgentAction         // 协作控制信号:中断、跳转、退出循环
    Err       error                // 运行报错
}

拆分两个重点子结构体:TypedAgentOutput、AgentAction

4.1 TypedAgentOutput:本次 Agent 输出内容

type TypedAgentOutput[M MessageType] struct {
    MessageOutput    *TypedMessageVariant[M] // 标准对话消息(文字、工具返回)
    CustomizedOutput any                     // 自定义业务输出,任意类型结构体
}
  • MessageOutput:标准聊天输出,最常用;
  • CustomizedOutput:扩展字段,你可以塞自己业务数据(比如订单 ID、检索文档),框架不干涉。

TypedMessageVariant:统一兼容「流式 / 一次性」消息

type TypedMessageVariant[M MessageType] struct {
    IsStreaming   bool
    Message       M                      // 非流式完整消息
    MessageStream *schema.StreamReader[M] // 流式分段数据流
    Role          schema.RoleType        // 传统消息角色(assistant/tool)
    AgenticRole   schema.AgenticRoleType // 结构化消息专用角色
    ToolName      string
}

核心开关 IsStreaming:

  1. IsStreaming = false:一次性结果,直接读 Message 完整对话;
  2. IsStreaming = true:流式打字机效果,从 MessageStream 循环读取分段字符;
    Role / AgenticRole 互斥:
  3. 用 *schema.Message(默认 Agent)只看 Role、ToolName;
  4. 用 *schema.AgenticMessage 结构化消息只看 AgenticRole。

4.2 AgentAction:多 Agent 协作控制信号(调度指令)

决定Agent的行为动作的参数

Event 里的 Action 是用来控制整个智能体系统流转的,不需要返回消息,只发控制指令:

type AgentAction struct {
    Exit            bool                  // 整个多Agent系统直接结束
    Interrupted     *InterruptInfo        // 暂停运行,保存状态,后续可以恢复Resume
    TransferToAgent *TransferToAgentAction // 废弃方案:直接转让任务给其他Agent(不推荐)
    BreakLoop       *BreakLoopAction      // 终止LoopAgent循环(循环思考智能体)
    CustomizedAction any                   // 自定义控制信号
}

逐个解释场景:

  1. Exit = true:任务彻底完成,所有 Agent 全部停止;
  2. Interrupted:中途暂停(比如需要用户补充信息、人机交互),保存现场,外部拿到数据后可以恢复执行;
  3. BreakLoop:专门给循环 Agent(思考 - 工具调用循环)用,满足条件跳出循环;
  4. TransferToAgent(不推荐):旧方案硬编码跳转 Agent,官方推荐新方案:把其他 Agent 包装成工具 AgentAsTool,更规范、兼容框架调度;
  5. CustomizedAction:自定义控制逻辑,比如标记需要检索知识库、发起审批。

五、AgentRunOption:单次 Run 运行的临时配置

作用:每次调用 agent.Run() 可以传入可变参数,只对本次执行生效,不污染 Agent 全局配置。

5.1 框架内置通用 Option

  1. WithSessionValues(map[string]any)
    多 Agent 共享上下文 KV 存储,所有 Agent 在同一次任务里都能读写这份数据(全局会话缓存);
  2. WithCallbacks(…)
    注册回调钩子,监听 Agent 生命周期事件(开始执行、输出消息、工具调用、报错);
  3. WithCancel()
    开启取消能力,ctx cancel 时可以中断 Agent 运行,配套 Loop 循环、中断恢复逻辑。

5.2 自定义业务 Option(重点实操)

框架支持给你的自定义 Agent 增加专属配置,示例:

运行
// 1. 定义自定义配置结构体
type myOptions struct {
    modelName string
}
// 2. 包装成标准AgentRunOption构造函数
func WithModelName(name string) adk.AgentRunOption {
    return adk.WrapImplSpecificOptFn(func(t *myOptions) {
        t.modelName = name
    })
}

在 Agent.Run 内部读取自定义配置:

运行
func (m *MyAgent) Run(ctx context.Context, input *adk.AgentInput, opts ...adk.AgentRunOption) *adk.AsyncIterator[*adk.AgentEvent] {
    // 从opts列表中提取自定义配置
    o := adk.GetImplSpecificOptions(&myOptions{}, opts...)
    fmt.Println(o.modelName) // 使用本次传入的模型名称
}

5.3 DesignateAgent:给指定 Agent 单独生效的 Option

同一个多 Agent 系统里,多个 Agent 共享一组 opts 时,可以限定某条配置只给特定 Agent 生效:

运行
// 这个SessionKV配置只对name="agent_1"的Agent生效
opt := adk.WithSessionValues(map[string]any{"key": "val"}).DesignateAgent("agent_1")

六、全局语言设置 adk.SetLanguage

运行
adk.SetLanguage(adk.LanguageChinese) // 默认LanguageEnglish英文
  1. 作用:修改 Eino ADK内置组件自带提示词的语言,比如文件检索、总结、内置工具、ChatModelAgent 这些框架自带 Agent 的系统提示;
  2. 局限:只管框架内置提示词,你自己写的自定义 Instruction、提示词不会自动翻译,需要自己处理中英文;
  3. 使用时机:程序 main 初始化阶段一次性设置,全局生效。

七、整体的一套完整流程大致梳理

下面是一个大致的流程,并非详细:

  1. 程序启动 main:adk.SetLanguage(adk.LanguageChinese) 设置中文;
  2. 自定义智能体 MyAgent,实现 adk.Agent(TypedAgent [*schema.Message])接口;
    • 实现 Name、Description;
    • 实现 Run 方法,内部创建 AsyncIterator,goroutine 执行业务;
  3. 发起调用,组装输入:
运行
input := &adk.AgentInput{
    Messages: []*schema.Message{用户提问+历史对话},
    EnableStreaming: true,
}
// 传入通用+自定义配置
opts := []adk.AgentRunOption{
    adk.WithSessionValues(map[string]any{"user_id": "123"}),
    WithModelName("gpt-4o"),
}
// 启动Agent,拿到异步事件流
iter := myAgent.Run(ctx, input, opts...)
  1. 循环消费事件流:
    • 收到 Event.Err:捕获异常;
    • Event.Output.MessageOutput:读取大模型输出文字(流式 / 完整);
    • Event.Action:判断是否中断、退出、跳出循环、切换 Agent;
  2. 全部事件读取完毕,迭代器关闭,本次 Agent 执行结束。

总的来说,上述内容讲的是Eino ADK 如何用一套统一 Go 泛型接口,标准化所有 AI 智能体的定义、入参、异步流式输出、多智能体协作控制、运行时配置,是开发 Go 版 LLM Agent 的底层基础规范。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐