mcpField
E
eino-agent

eino-agent

github.com/cloudwegoAgent 工作流828336 次安装1 周前更新
代码仓库

关于 eino-agent

eino-agent 是面向 Go 开发者的 Eino Agent 开发套件,提供多种 Agent 类型、运行器和中间件。它还支持可控取消、模型重试与故障切换,以及人工介入后的暂停和恢复。

主要功能

  1. 01ChatModelAgent、DeepAgent 与自定义 Agent
  2. 02TurnLoop 多轮事件循环
  3. 03Runner 检查点与事件流管理
  4. 04安全取消、重试和模型故障切换
  5. 05中间件扩展与 Agent 转工具

适用场景

  1. 01构建可调用工具的多轮 Agent
  2. 02处理需要规划、文件操作或子 Agent 的任务
  3. 03在交互应用中排队输入并抢占当前轮次
  4. 04在审批或澄清后恢复 Agent 执行

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cloudwego/eino-ext --skill eino-agent

安装后在 Claude Code 中输入/eino-agent确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

reference/
agent-as-tool.md
chat-model-agent.md
deep-agents.md
filesystem.md
human-in-the-loop.md
middleware.md
runner-and-events.md

详细介绍

Eino ADK 概览

导入:github.com/cloudwego/eino/adk

Agent Development Kit(ADK)为在 Go 中构建 Agent 提供了一个框架。ADK 使用 MessageType 进行泛型参数化,以同时支持经典的 *schema.Message 和新的 *schema.AgenticMessage。新用法建议优先使用 *schema.AgenticMessage。

type MessageType interface {
    *schema.Message | *schema.AgenticMessage
}

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]]
}

// Convenience aliases for classic message type
type Agent = TypedAgent[*schema.Message]

Agent 类型

类型描述决策方式
ChatModelAgentReAct 模式:大模型推理、调用工具,并循环执行直至完成动态(大模型)
DeepAgent预构建 Agent,具备规划、文件系统和子 Agent 功能动态(大模型)
TurnLoop基于推送的事件循环,用于多轮执行,支持抢占和生命周期管理运行时
自定义 Agent直接实现 TypedAgent 接口自定义

ChatModelAgent 快速入门

import (
    "context"
    "fmt"
    "log"

    "github.com/cloudwego/eino-ext/components/model/openai"
    "github.com/cloudwego/eino/adk"
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/components/tool/utils"
    "github.com/cloudwego/eino/compose"
)

func main() {
    ctx := context.Background()

    // 1. Create a tool
    searchTool, _ := utils.InferTool("search_book", "Search books by genre",
        func(ctx context.Context, input *struct {
            Genre string `json:"genre" jsonschema_description:"Book genre"`
        }) (string, error) {
            return `{"books": ["The Great Gatsby"]}`, nil
        })

    // 2. Create model (BaseModel[M], not ToolCallingChatModel)
    cm, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey: "your-key", Model: "gpt-4o",
    })

    // 3. Create agent
    agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
        Name:        "BookRecommender",
        Description: "Recommends books",
        Instruction: "You recommend books using the search_book tool.",
        Model:       cm,
        ToolsConfig: adk.ToolsConfig{
            ToolsNodeConfig: compose.ToolsNodeConfig{
                Tools: []tool.BaseTool{searchTool},
            },
        },
    })

    // 4. Run with Runner
    runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
    iter := runner.Query(ctx, "recommend a fiction book")
    for {
        event, ok := iter.Next()
        if !ok {
            break
        }
        if event.Err != nil {
            log.Fatal(event.Err)
        }
        if event.Output != nil && event.Output.MessageOutput != nil {
            msg, _ := event.Output.MessageOutput.GetMessage()
            fmt.Printf("Agent[%s]: %v\n", event.AgentName, msg)
        }
    }
}

取消机制

取消机制可安全、可控地终止 Agent 执行。

// Create a cancel function alongside the run
cancelOpt, cancelFn := adk.WithCancel()
iter := runner.Query(ctx, "do something", cancelOpt)

// ... iterate events ...

// Cancel at a safe point (cancelFn is non-blocking, Wait blocks until complete)
handle, ok := cancelFn(adk.WithAgentCancelMode(adk.CancelAfterChatModel))
if ok {
    handle.Wait()
}

CancelMode(位掩码):

模式行为
CancelImmediate (0)立即中止,流随即终止
CancelAfterChatModel等待当前模型调用完成
CancelAfterToolCalls等待当前工具调用完成

取消选项:

  • WithAgentCancelMode(mode) -- 设置安全点
  • WithAgentCancelTimeout(d) -- 若未能在时限内到达安全点,则升级为立即取消
  • WithRecursive() -- 将取消操作传播至嵌套的 AgentTool Agent

取消会在事件流中产生一个 CancelError,其中包含可供后续恢复的检查点数据。

模型重试

基于输出的重试,可全面控制重试决策。

agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    // ...
    ModelRetryConfig: &adk.ModelRetryConfig{
        MaxRetries: 3,
        ShouldRetry: func(ctx context.Context, retryCtx *adk.RetryContext) *adk.RetryDecision {
            // Retry based on output content (e.g., empty response, bad finish reason)
            if retryCtx.Err != nil {
                return &adk.RetryDecision{Retry: true, Backoff: time.Second}
            }
            if retryCtx.OutputMessage == nil || retryCtx.OutputMessage.Content == "" {
                return &adk.RetryDecision{Retry: true, Backoff: time.Second}
            }
            return &adk.RetryDecision{Retry: false}
        },
    },
})

RetryContext 提供:RetryAttempt、InputMessages、OutputMessage(完整拼接后的响应)、Err。

RetryDecision 控制:Retry、ModifiedInputMessages、AdditionalOptions、Backoff、RejectReason。

在流式处理中,流中会发出 WillRetryError,以表示正在进行重试。

模型故障转移

当主模型失败或生成不满意的输出时,动态切换模型。

agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    // ...
    ModelFailoverConfig: &adk.ModelFailoverConfig[*schema.Message]{
        MaxRetries: 2,
        ShouldFailover: func(ctx context.Context, outputMessage *schema.Message, outputErr error) bool {
            return outputErr != nil // failover on any error
        },
        GetFailoverModel: func(ctx context.Context, failoverCtx *adk.FailoverContext[*schema.Message]) (
            model.BaseModel[*schema.Message], []*schema.Message, error) {
            // Return a different model and optionally modified input
            return backupModel, failoverCtx.InputMessages, nil
        },
    },
})

故障转移与重试会相互作用:如果同时配置两者,且重试也已耗尽,则 FailoverContext.LastErr 将是一个 *RetryExhaustedError。

TurnLoop

基于推送的事件循环,用于多轮 Agent 执行,支持抢占、空闲超时和优雅关闭。

import "github.com/cloudwego/eino/adk"

loop := adk.NewTurnLoop(adk.TurnLoopConfig[string, *schema.Message]{
    GenInput: func(ctx context.Context, loop *adk.TurnLoop[string, *schema.Message], items []string) (*adk.GenInputResult[string, *schema.Message], error) {
        // Convert pushed items into agent input
        combined := strings.Join(items, "\n")
        return &adk.GenInputResult[string, *schema.Message]{
            RunCtx:   ctx,
            Input:    &adk.TypedAgentInput[*schema.Message]{Messages: []*schema.Message{schema.UserMessage(combined)}},
            Consumed: items,
        }, nil
    },
    PrepareAgent: func(ctx context.Context, loop *adk.TurnLoop[string, *schema.Message], consumed []string) (adk.Agent, error) {
        return myAgent, nil
    },
    OnAgentEvents: func(ctx context.Context, tc *adk.TurnContext[string, *schema.Message], events *adk.AsyncIterator[*adk.AgentEvent]) error {
        for {
            event, ok := events.Next()
            if !ok {
                break
            }
            if event.Err != nil {
                return event.Err
            }
            // Process events (e.g., send to client)
        }
        return nil
    },
})

// Start the loop
loop.Run(ctx)

// Push items (non-blocking, returns (ok bool, resolved <-chan struct{}))
loop.Push("user message 1")

// Preempt current turn with new input
loop.Push("urgent message", adk.WithPreempt[string, *schema.Message](adk.AfterChatModel))

// Stop gracefully
loop.Stop(adk.WithGraceful())

// Wait for exit and get final state
exitState := loop.Wait()

关键概念:

  • Push() 将条目加入队列;循环会对条目进行批处理,并通过 GenInput 处理
  • 抢占:在安全点取消当前轮次,并使用待处理条目启动新一轮
  • 空闲超时:UntilIdleFor(d) 在持续 d 时间没有条目后自动停止
  • 优雅关闭:WithGraceful()、WithGracefulTimeout(d)、WithImmediate()
  • 退出状态:TurnLoopExitState 包含 ExitReason、UnhandledItems、InterruptedItems

Runner

Runner 管理 Agent 生命周期、上下文传递以及中断和恢复:

runner := adk.NewRunner(ctx, adk.RunnerConfig{
    Agent:           myAgent,
    EnableStreaming:  true,
    CheckPointStore: myStore,  // for interrupt/resume
})

// Query (convenience for single user message)
iter := runner.Query(ctx, "hello")

// Run (full control over input messages)
iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("hello")})

中间件系统

中间件扩展 ChatModelAgent 的行为。通过 Handlers 字段进行配置:

agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    // ...
    Handlers: []adk.ChatModelAgentMiddleware{fsMiddleware, summarizationMW},
})

八种内置中间件类型(详情请参阅 reference/middleware.md):

中间件包用途
FileSystemadk/middlewares/filesystem文件操作(读取/写入/编辑/文件匹配/文本搜索)和 Shell
ToolSearchadk/middlewares/dynamictool/toolsearch通过正则搜索动态选择工具
ToolReductionadk/middlewares/reduction截断/清除大型工具结果
Summarizationadk/middlewares/summarization压缩冗长的对话历史
PlanTaskadk/middlewares/plantask创建任务并跟踪进度
Skilladk/middlewares/skill基于技能的渐进式信息披露
PatchToolCallsadk/middlewares/patchtoolcalls修复历史记录中未完成的工具调用
Agents.mdadk/middlewares/agentsmd将 Agents.md 指令注入模型输入

AgentAsTool

将任意 Agent 封装为工具,供其他 Agent 使用:

subAgent := createMySubAgent()
agentTool := adk.NewAgentTool(ctx, subAgent)

parentAgent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    ToolsConfig: adk.ToolsConfig{
        ToolsNodeConfig: compose.ToolsNodeConfig{
            Tools: []tool.BaseTool{agentTool},
        },
    },
})

Human-in-the-Loop

ChatModelAgent 支持为人工审批、澄清和反馈进行中断和恢复。详情请参阅 reference/human-in-the-loop.md。

关键模式:工具返回 compose.Interrupt(ctx, info) 以暂停,然后调用 runner.ResumeWithParams(ctx, checkpointID, params) 继续执行。

给 Agent 的说明

  • 大多数情况下,默认使用 ChatModelAgent(包含工具的单个 Agent)
  • 使用 Runner 执行 Agent——在生产环境中切勿直接调用 agent.Run()
  • 中间件顺序很重要:先使用 PatchToolCalls,然后是 Reduction,最后是 Summarization
  • 需要内置规划、文件系统和子 Agent 时,使用 DeepAgent(adk/prebuilt/deep)
  • 子 Agent 需要隔离上下文(不共享历史记录)时,使用 AgentAsTool 或 DeepAgents 的 SubAgents
  • 构建需要抢占、空闲管理或基于推送的输入的交互式应用时,使用 TurnLoop
  • Model 字段接受 model.BaseModel[M]——对于经典路径,可使用任意 BaseChatModel;对于 Agentic 路径,使用 AgenticModel
  • Cancel、Retry 和 Failover 可以组合使用;Failover 会封装 Retry
  • WithCancel() 是运行选项,而不是配置选项——每次运行都应创建一个新的实例

参考文件

需要详细的 API、示例和高级用法时,按需阅读以下文件:

评论

支持 Markdown