diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent Runner.md b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent Runner.md new file mode 100644 index 00000000000..955e3c36f39 --- /dev/null +++ b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent Runner.md @@ -0,0 +1,103 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Agent 扩展' +weight: 0 +--- + +# Agent Runner + +Runner 是 Eino ADK 中负责执行 Agent 的核心引擎。它的主要作用是管理和控制 Agent 的整个生命周期,如处理多 Agent 协作,保存传递上下文等,interrupt、callback 等切面能力也均依赖 Runner 实现。任何 Agent 都应通过 Runner 来运行。 + +# Interrupt & Resume + +该功能允许一个正在运行的 Agent 主动中断其执行,保存当前状态,并在稍后从中断点恢复执行。这对于处理需要外部输入、长时间等待或可暂停的任务流非常有用。 + +## Interrupted Action + +在 Agent 的执行过程中,可以通过产生包含 Interrupted Action 的 AgentEvent 来主动中断 Runner 的运行: + +```go +// github.com/cloudwego/eino/adk/interface.go +type AgentAction struct { + // other actions + Interrupted *InterruptInfo + // other actions +} + +// github.com/cloudwego/eino/adk/interrupt.go +type InterruptInfo struct { + Data any +} +``` + +当中断发生时,可以通过 InterruptInfo 结构体附带自定义的中断信息。此信息: + +1. 会被传递给调用者,可以通过该信息向调用者说明中断原因等 +2. 如果后续需要恢复 Agent 运行,InterruptInfo 会在恢复时重新传递给中断的 Agent,Agent 可以依据该信息恢复运行 + +## 状态持久化 (Checkpoint) + +当 Runner 捕获到这个带有 Interrupted Action 的 Event 时,会立即终止当前的执行流程。 如果: + +1. Runner 中设置了 CheckPointStore + +```go +// github.com/cloudwego/eino/adk/runner.go +type RunnerConfig struct { + // other fields + CheckPointStore CheckPointStore +} + +// github.com/cloudwego/eino/adk/interrupt.go +type CheckPointStore interface { + Set(ctx context.Context, key string, value []byte) error + Get(ctx context.Context, key string) ([]byte, bool, error) +} +``` + +1. 调用 Runner 时通过 AgentRunOption WithCheckPointID 传入 CheckPointID + +```go +// github.com/cloudwego/eino/adk/interrupt.go +func WithCheckPointID(id string) _AgentRunOption_ +``` + +Runner 在终止运行后会将当前运行状态(原始输入、对话历史等)以及 Agent 抛出的 InterruptInfo 以 CheckPointID 为 key 持久化到 CheckPointStore 中。 + +> 💡 +> 为了保存 interface 中数据的原本类型,Eino ADK 使用 gob([https://pkg.go.dev/encoding/gob](https://pkg.go.dev/encoding/gob))序列化运行状态。因此在使用自定义类型时需要提前使用 gob.Register 或 gob.RegisterName 注册类型(更推荐后者,前者使用路径加类型名作为默认名字,因此类型的位置和名字均不能发生变更)。Eino 会自动注册框架内置的类型。 + +## Resume + +运行中断,调用 Runner 的 Resume 接口传入中断时的 CheckPointID 可以恢复运行: + +```go +// github.com/cloudwego/eino/adk/runner.go +func (r *Runner) Resume(ctx context.Context, checkPointID string, opts ...AgentRunOption) (*AsyncIterator[*AgentEvent], error) +``` + +恢复 Agent 运行需要发生中断的 Agent 实现了 ResumableAgent 实现了 ResumableAgent 接口, Runner 从 CheckPointerStore 读取运行状态并恢复运行,其中 InterruptInfo 和上次运行配置的 EnableStreaming 会作为输入提供给 Agent: + +```go +// github.com/cloudwego/eino/adk/interface.go +type ResumableAgent interface { + Agent + + Resume(ctx context.Context, info *ResumeInfo, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] +} + +// github.com/cloudwego/eino/adk/interrupt.go +type ResumeInfo struct { + EnableStreaming bool + *_InterruptInfo_ +} +``` + +Resume 如果向 Agent 传入新信息,可以定义 AgentRunOption,在调用 Runner.Resume 时传入。 + +# Callback + +TODO diff --git "a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260.md" "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260.md" new file mode 100644 index 00000000000..8e8dec916da --- /dev/null +++ "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260.md" @@ -0,0 +1,12 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Agent 实现' +weight: 0 +--- + +用户可以通过实现 Agent 接口自定义 Agent。自定义 Agent 建议严格遵守上述规则,在应用、迭代、合作中可以带来便利。 + +简单自定义 Agent 可以参考: github.com/cloudwego/eino-examples/adk/intro/custom/myagent.go diff --git "a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: ChatModelAgent.md" "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: ChatModelAgent.md" new file mode 100644 index 00000000000..f2494842a9b --- /dev/null +++ "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: ChatModelAgent.md" @@ -0,0 +1,493 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: ChatModelAgent' +weight: 0 +--- + +ChatModelAgent 是 Eino ADK 中的一个核心预构建 的 Agent,它封装了与大语言模型(LLM)进行交互、并支持使用工具来完成任务的复杂逻辑。 + +下面,我们将创建一个图书推荐 Agent,演示如何配置和使用 ChatModelAgent 。这个 Agent 将能够根据用户的输入推荐相关图书。 + +- 工具定义 + +对图书推荐 Agent,需要一个根据能够根据用户要求(题材、评分等)检索图书的工具 `book_search` 利用 Eino 提供的工具方法可以方便地创建(可参考[如何创建一个 tool ?](/zh/docs/eino/core_modules/components/tools_node_guide/如何创建一个 tool ?)): + +```go +import ( + "context" + "log" + + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/components/tool/utils" +) + +type BookSearchInput struct { + Genre string `json:"genre" jsonschema:"description=Preferred book genre,enum=fiction,enum=sci-fi,enum=mystery,enum=biography,enum=business"` + MaxPages int `json:"max_pages" jsonschema:"description=Maximum page length (0 for no limit)"` + MinRating int `json:"min_rating" jsonschema:"description=Minimum user rating (0-5 scale)"` +} + +type BookSearchOutput struct { + Books []string +} + +func NewBookRecommender() tool.InvokableTool { + bookSearchTool, err := utils.InferTool("search_book", "Search books based on user preferences", func(ctx context.Context, input *BookSearchInput) (output *BookSearchOutput, err error) { + // search code + // ... + return &BookSearchOutput{Books: []string{"God's blessing on this wonderful world!"}}, nil + }) + if err != nil { + log.Fatalf("failed to create search book tool: %v", err) + } + return bookSearchTool +} +``` + +- 创建 ChatModel + +为 ChatModelAgent 创建 ChatModel,Eino 提供了多种 ChatModel 封装(如 openai、gemini、doubao 等,详见 [Eino: ChatModel 使用说明](/zh/docs/eino/core_modules/components/chat_model_guide)),这里以 openai ChatModel 为例: + +```go +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/components/model" +) + +func NewChatModel() model.ToolCallingChatModel { + ctx := context.Background() + apiKey := os.Getenv("OPENAI_API_KEY") + openaiModel := os.Getenv("OPENAI_MODEL") + + cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{ + APIKey: apiKey, + Model: openaiModel, + }) + if err != nil { + log.Fatal(fmt.Errorf("failed to create chatmodel: %w", err)) + } + return cm +} +``` + +- 创建 ChatModelAgent + +除了配置 ChatModel 和工具外,还需要配置描述 Agent 功能用途的 Name 和 Description,以及指示 ChatModel 的 Instruction,Instruction 最终会作为 system message 被传递给 ChatModel。 + +```go +import ( + "context" + "fmt" + "log" + + "github.com/cloudwego/eino/adk" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/compose" +) + +func NewBookRecommendAgent() adk.Agent { + ctx := context.Background() + + a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ + Name: "BookRecommender", + Description: "An agent that can recommend books", + Instruction: `You are an expert book recommender. Based on the user's request, use the "search_book" tool to find relevant books. Finally, present the results to the user.`, + Model: NewChatModel(), + ToolsConfig: adk.ToolsConfig{ + ToolsNodeConfig: compose.ToolsNodeConfig{ + Tools: []tool.BaseTool{NewBookRecommender()}, + }, + }, + }) + if err != nil { + log.Fatal(fmt.Errorf("failed to create chatmodel: %w", err)) + } + + return a +} +``` + +通过 Runner 运行: + +```go +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudwego/eino/adk" + + "github.com/cloudwego/eino-examples/adk/intro/chatmodel/subagents" +) + +func main() { + ctx := context.Background() + a := subagents.NewBookRecommendAgent() + runner := adk.NewRunner(ctx, adk.RunnerConfig{ + Agent: a, + }) + iter := runner.Query(ctx, "recommend a fiction book to me") + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + log.Fatal(event.Err) + } + msg, err := event.Output.MessageOutput.GetMessage() + if err != nil { + log.Fatal(err) + } + fmt.Printf("\nmessage:\n%v\n======", msg) + } +} +``` + +示例结果: + +> message: +> assistant: +> tool_calls: +> {Index: ID:call_o2It087hoqj8L7atzr70EnfG Type:function Function:{Name:search_book Arguments:{"genre":"fiction","max_pages":0,"min_rating":0}} Extra:map[]} +> +> finish_reason: tool_calls +> usage: &{140 24 164} +> ====== +> +> message: +> tool: {"Books":["God's blessing on this wonderful world!"]} +> tool_call_id: call_o2It087hoqj8L7atzr70EnfG +> tool_call_name: search_book +> ====== +> +> message: +> assistant: I recommend the fiction book "God's blessing on this wonderful world!". It's a great choice for readers looking for an exciting story. Enjoy your reading! +> finish_reason: stop +> usage: &{185 31 216} +> ====== + +# 工具调用 + +ChatModelAgent 内使用了 [ReAct](https://react-lm.github.io/) 模式,该模式旨在通过让 ChatModel 进行显式的、一步一步的“思考”来解决复杂问题。为 ChatModelAgent 配置了工具后,它在内部的执行流程就遵循了 ReAct 模式: + +- 调用 ChatModel(Reason) +- LLM 返回工具调用请求(Action) +- ChatModelAgent 执行工具(Act) +- 它将工具结果返回给 ChatModel(Observation),然后开始新的循环,直到 ChatModel 判断不需要调用 Tool 结束。 + +![](/img/eino/react_agent_pattern.png) + +可以通过 ToolsConfig 为 ChatModelAgent 配置 Tool: + +```go +// github.com/cloudwego/eino/adk/chatmodel.go + +type ToolsConfig struct { + compose.ToolsNodeConfig + + // Names of the tools that will make agent return directly when the tool is called. + // When multiple tools are called and more than one tool is in the return directly list, only the first one will be returned. + ReturnDirectly map[string]bool +} +``` + +ToolsConfig 复用了 Eino Graph ToolsNodeConfig,详细参考:[Eino: ToolsNode&Tool 使用说明](/zh/docs/eino/core_modules/components/tools_node_guide)。额外提供了 ReturnDirectly 配置,ChatModelAgent 调用配置在 ReturnDirectly 中的 Tool 后会直接退出。 + +当没有配置工具时,ChatModelAgent 退化为一次 ChatModel 调用。 + +# GenModelInput + +ChatModelAgent 创建时可以配置 GenModelInput,Agent 被调用时会使用该方法生成 ChatModel 的初始输入: + +``` +type GenModelInput func(ctx context.Context, instruction string, input *AgentInput) ([]Message, error) +``` + +Agent 提供了默认的 GenModelInput 方法: + +1. 将 Instruction 作为 system message 加到 AgentInput.Messages 前 +2. 以 SessionValues 为 variables 渲染 1 中得到的 message list + +# OutputKey + +ChatModelAgent 创建时可以配置 OutputKey,配置后 Agent 产生的最后一个 message 会被以设置的 OutputKey 为 key 添加到 SessionValues 中。 + +# Exit + +Exit 字段支持配置一个 Tool,当 LLM 调用这个工具后并执行后,ChatModelAgent 将直接退出,效果类似 ToolReturnDirectly。Eino ADK 提供了一个 ExitTool,用户可以直接使用: + +```go +// github.com/cloudwego/eino/adk/chatmodel.go + +type ExitTool struct{} + +func (et ExitTool) Info(_ context.Context) (*schema.ToolInfo, error) { + return ToolInfoExit, nil +} + +func (et ExitTool) InvokableRun(ctx context.Context, argumentsInJSON string, _ ...tool.Option) (string, error) { + type exitParams struct { + FinalResult string `json:"final_result"` + } + + params := &exitParams{} + err := sonic.UnmarshalString(argumentsInJSON, params) + if err != nil { + return "", err + } + + err = SendToolGenAction(ctx, "exit", NewExitAction()) + if err != nil { + return "", err + } + + return params.FinalResult, nil +} +``` + +# Transfer + +ChatModelAgent 实现了 OnSubAgents 接口,使用 SetSubAgents 为 ChatModelAgent 设置父或子 Agent 后,ChatModelAgent 会增加一个 Transfer Tool,并且在 prompt 中指示 ChatModel 在需要 transfer 时调用这个 Tool 并以 transfer 目标 AgentName 作为 Tool 输入。在此工具被调用后,Agent 会产生 TransferAction 并退出。 + +# AgentTool + +ChatModelAgent 提供了工具方法,可以方便地将 Eino ADK Agent 转化为 Tool 供 ChatModelAgent 调用: + +```go +// github.com/cloudwego/eino/adk/agent_tool.go + +func NewAgentTool(_ context.Context, agent Agent, options ...AgentToolOption) tool.BaseTool +``` + +比如之前创建的 `BookRecommendAgent` 可以使用 NewAgentTool 方法转换为 Tool,并被其他 Agent 调用: + +```go +bookRecommender := NewBookRecommendAgent() +bookRecommendeTool := NewAgentTool(ctx, bookRecommender) + +// other agent +a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ + // xxx + ToolsConfig: adk.ToolsConfig{ + ToolsNodeConfig: compose.ToolsNodeConfig{ + Tools: []tool.BaseTool{bookRecommendeTool}, + }, + }, +}) +``` + +# Interrupt&Resume + +ChatModelAgent 支持 Interrupt&Resume,我们给 BookRecommendAgent 增加一个工具 `ask_for_clarification`,当用户提供的信息不足以支持推荐时,Agent 将调用这个工具向用户询问更多信息,`ask_for_clarification` 使用了 Interrupt&Resume 能力来实现向用户“询问”。 + +ChatModelAgent 使用了 Eino Graph 实现,在 agent 中可以复用 Eino Graph 的 Interrupt&Resume 能力,工具返回特殊错误使 Graph 触发中断并向外抛出自定义信息,在恢复时 Graph 会重新运行此工具: + +```go +// github.com/cloudwego/eino/adk/interrupt.go + +func NewInterruptAndRerunErr(extra any) error +``` + +另外定义 ToolOption 来在恢复时传递新输入: + +```go +import ( + "github.com/cloudwego/eino/components/tool" +) + +type askForClarificationOptions struct { + NewInput *string +} + +func WithNewInput(input string) tool.Option { + return tool.WrapImplSpecificOptFn(func(t *askForClarificationOptions) { + t.NewInput = &input + }) +} +``` + +> 💡 +> 定义 tool option 不是必须的,实践时可以根据 context、闭包等其他方式传递新输入 + +完整的 Tool 实现如下: + +```go +import ( + "context" + "log" + + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/components/tool/utils" + "github.com/cloudwego/eino/compose" +) + +type askForClarificationOptions struct { + NewInput *string +} + +func WithNewInput(input string) tool.Option { + return tool.WrapImplSpecificOptFn(func(t *askForClarificationOptions) { + t.NewInput = &input + }) +} + +type AskForClarificationInput struct { + Question string `json:"question" jsonschema:"description=The specific question you want to ask the user to get the missing information"` +} + +func NewAskForClarificationTool() tool.InvokableTool { + t, err := utils.InferOptionableTool( + "ask_for_clarification", + "Call this tool when the user's request is ambiguous or lacks the necessary information to proceed. Use it to ask a follow-up question to get the details you need, such as the book's genre, before you can use other tools effectively.", + func(ctx context.Context, input *AskForClarificationInput, opts ...tool.Option) (output string, err error) { + o := tool.GetImplSpecificOptions[askForClarificationOptions](nil, opts...) + if o.NewInput == nil { + return "", compose.NewInterruptAndRerunErr(input.Question) + } + return *o.NewInput, nil + }) + if err != nil { + log.Fatal(err) + } + return t +} +``` + +将 `ask_for_clarification` 添加到之前的 Agent 中: + +```go +func NewBookRecommendAgent() adk.Agent { + // xxx + a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ + // xxx + ToolsConfig: adk.ToolsConfig{ + ToolsNodeConfig: compose.ToolsNodeConfig{ + Tools: []tool.BaseTool{NewBookRecommender(), NewAskForClarificationTool()}, + }, + }, + }) + // xxx +} +``` + +之后在 Runner 中配置 CheckPointStore(例子中使用最简单的 InMemoryStore),并在调用 Agent 时传入 CheckPointID,用来在恢复时使用。eino Graph 在中断时,会把 Graph 的 InterruptInfo 放入 Interrupted.Data 中: + +```go +func main() { + ctx := context.Background() + a := subagents.NewBookRecommendAgent() + runner := adk.NewRunner(ctx, adk.RunnerConfig{ + Agent: a, + CheckPointStore: newInMemoryStore(), + }) + iter := runner.Query(ctx, "recommend a book to me", adk.WithCheckPointID("1")) + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + log.Fatal(event.Err) + } + if event.Action != nil && event.Action.Interrupted != nil { + fmt.Printf("\ninterrupt happened, info: %+v\n", event.Action.Interrupted.Data.(*compose.InterruptInfo).RerunNodesExtra["ToolNode"]) + continue + } + msg, err := event.Output.MessageOutput.GetMessage() + if err != nil { + log.Fatal(err) + } + fmt.Printf("\nmessage:\n%v\n======\n\n", msg) + } + + // xxxxxx +} +``` + +可以在中断看到输出: + +> message: +> assistant: +> tool_calls: +> {Index: ID:call_3HAobzkJvW3JsTmSHSBRftaG Type:function Function:{Name:ask_for_clarification Arguments:{"question":"Could you please specify the genre you're interested in and any preferences like maximum page length or minimum user rating?"}} Extra:map[]} +> +> finish_reason: tool_calls +> usage: &{219 37 256} +> ====== +> +> interrupt happened, info: &{ToolCalls:[{Index: ID:call_3HAobzkJvW3JsTmSHSBRftaG Type:function Function:{Name:ask_for_clarification Arguments:{"question":"Could you please specify the genre you're interested in and any preferences like maximum page length or minimum user rating?"}} Extra:map[]}] ExecutedTools:map[] RerunTools:[call_3HAobzkJvW3JsTmSHSBRftaG] RerunExtraMap:map[call_3HAobzkJvW3JsTmSHSBRftaG:Could you please specify the genre you're interested in and any preferences like maximum page length or minimum user rating?]} + +之后向用户询问新输入并恢复运行: + +```go +func main(){ + // xxx + scanner := bufio.NewScanner(os.Stdin) + fmt.Print("new input is:\n") + scanner.Scan() + nInput := scanner.Text() + + iter, err := runner.Resume(ctx, "1", adk.WithToolOptions([]tool.Option{chatmodel.WithNewInput(nInput)})) + if err != nil { + log.Fatal(err) + } + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + log.Fatal(event.Err) + } + msg, err := event.Output.MessageOutput.GetMessage() + if err != nil { + log.Fatal(err) + } + fmt.Printf("\nmessage:\n%v\n======\n\n", msg) + } +} +``` + +新的输出为: + +> new input is: +> recommend me a fiction book +> +> message: +> tool: recommend me a fiction book +> tool_call_id: call_3HAobzkJvW3JsTmSHSBRftaG +> tool_call_name: ask_for_clarification +> ====== +> +> message: +> assistant: +> tool_calls: +> {Index: ID:call_3fC5OqPZLls11epXMv7sZGAF Type:function Function:{Name:search_book Arguments:{"genre":"fiction","max_pages":0,"min_rating":0}} Extra:map[]} +> +> finish_reason: tool_calls +> usage: &{272 24 296} +> ====== +> +> message: +> tool: {"Books":["God's blessing on this wonderful world!"]} +> tool_call_id: call_3fC5OqPZLls11epXMv7sZGAF +> tool_call_name: search_book +> ====== +> +> message: +> assistant: I recommend the fiction book "God's Blessing on This Wonderful World!" Enjoy your reading! +> finish_reason: stop +> usage: &{317 20 337} +> ====== + +> 完整示例见:github.com/cloudwego/eino-examples/adk/intro/chatmodel diff --git "a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: Transfer SubAgents.md" "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: Transfer SubAgents.md" new file mode 100644 index 00000000000..6c19b3695c5 --- /dev/null +++ "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: Transfer SubAgents.md" @@ -0,0 +1,288 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Transfer SubAgents' +weight: 0 +--- + +Agent 运行时产生带有包含 TransferAction 的 AgentEvent 后,Eino ADK 会调用 Action 指定的 Agent,被调用的 Agent 被称为子 Agent(SubAgent)。TransferAction 可以使用 `NewTransferToAgentAction` 快速创建: + +```go +import "github.com/cloudwego/eino/adk" + +event := adk.NewTransferToAgentAction("dest agent name") +``` + +为了让 Eino ADK 在接受到 TransferAction 可以找到子 Agent 实例并运行,在运行前需要先调用 `SetSubAgents` 将可能的子 Agent 注册到 Eino ADK 中: + +```go +// github.com/cloudwego/eino/adk/flow.go +func SetSubAgents(ctx context.Context, agent Agent, subAgents []Agent) (Agent, error) +``` + +> 💡 +> Transfer 的含义是将任务**移交**给子 Agent,而不是委托或者分配,因此: +> +> 1. 区别于 ToolCall,通过 Transfer 调用子 Agent,子 Agent 运行结束后,不会再调用父 Agent 总结内容或进行下一步操作。 +> 2. 调用子 Agent 时,子 Agent 的输入仍然是原始输入,父 Agent 的输出会作为上下文供子 Agent 参考。 + +以上描述中,产生 TransferAction Agent 天然清楚自己的子 Agent 有哪些,另外一些 Agent 需要根据不同场景配置不同的子 Agent,比如 Eino ADK 提供的 ChatModelAgent,这是一个通用 Agent 模板,需要根据业务实际场景配置子 Agent。这样的 Agent 需要能动态地注册父子 Agent,Eino 定义了 `OnSubAgents` 接口,用来支持此功能: + +```go +// github.com/cloudwego/eino/adk/interface.go +type OnSubAgents interface { + OnSetSubAgents(ctx context.Context, subAgents []Agent) error + OnSetAsSubAgent(ctx context.Context, parent Agent) error + + OnDisallowTransferToParent(ctx context.Context) error +} +``` + +如果 Agent 实现了 `OnSubAgents` 接口,`SetSubAgents` 中会调用相应的方法向 Agent 注册。 + +接下来以一个多功能对话 Agent 演示 Transfer 能力,目标是搭建一个可以查询天气或者与用户对话的 Agent,Agent 结构如下: + +![](/img/eino/EdZawsBGPhbH4MbBkSVcWo6Unrg.png) + +三个 Agent 均使用 ChatModelAgent 实现: + +```go +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/adk" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/components/tool/utils" + "github.com/cloudwego/eino/compose" +) + +func newChatModel() model.ToolCallingChatModel { + cm, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{ + APIKey: os.Getenv("OPENAI_API_KEY"), + Model: os.Getenv("OPENAI_MODEL"), + }) + if err != nil { + log.Fatal(err) + } + return cm +} + +type GetWeatherInput struct { + City string `json:"city"` +} + +func NewWeatherAgent() adk.Agent { + weatherTool, err := utils.InferTool( + "get_weather", + "Gets the current weather for a specific city.", // English description + func(ctx context.Context, input *GetWeatherInput) (string, error) { + return fmt.Sprintf(`the temperature in %s is 25°C`, input.City), nil + }, + ) + if err != nil { + log.Fatal(err) + } + + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "WeatherAgent", + Description: "This agent can get the current weather for a given city.", + Instruction: "Your sole purpose is to get the current weather for a given city by using the 'get_weather' tool. After calling the tool, report the result directly to the user.", + Model: newChatModel(), + ToolsConfig: adk.ToolsConfig{ + ToolsNodeConfig: compose.ToolsNodeConfig{ + Tools: []tool.BaseTool{weatherTool}, + }, + }, + }) + if err != nil { + log.Fatal(err) + } + return a +} + +func NewChatAgent() adk.Agent { + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "ChatAgent", + Description: "A general-purpose agent for handling conversational chat.", // English description + Instruction: "You are a friendly conversational assistant. Your role is to handle general chit-chat and answer questions that are not related to any specific tool-based tasks.", + Model: newChatModel(), + }) + if err != nil { + log.Fatal(err) + } + return a +} + +func NewRouterAgent() adk.Agent { + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "RouterAgent", + Description: "A manual router that transfers tasks to other expert agents.", + Instruction: `You are an intelligent task router. Your responsibility is to analyze the user's request and delegate it to the most appropriate expert agent.If no Agent can handle the task, simply inform the user it cannot be processed.`, + Model: newChatModel(), + }) + if err != nil { + log.Fatal(err) + } + return a +} +``` + +之后使用 Eino ADK 的 Transfer 能力搭建 Multi-Agent 并运行,ChatModelAgent 实现了 OnSubAgent 接口,在 adk.SetSubAgents 方法中会使用此接口向 ChatModelAgent 注册父/子 Agent,不需要用户处理 TransferAction 生成问题: + +```go +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudwego/eino/adk" + + "github.com/cloudwego/eino-examples/adk/intro/transfer/subagents" +) + +func main() { + weatherAgent := subagents.NewWeatherAgent() + chatAgent := subagents.NewChatAgent() + routerAgent := subagents.NewRouterAgent() + + ctx := context.Background() + a, err := adk.SetSubAgents(ctx, routerAgent, []adk.Agent{chatAgent, weatherAgent}) + if err != nil { + log.Fatal(err) + } + + runner := adk.NewRunner(ctx, adk.RunnerConfig{ + Agent: a, + }) + + // query weather + println("\n\n>>>>>>>>>query weather<<<<<<<<<") + iter := runner.Query(ctx, "What's the weather in Beijing?") + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + log.Fatal(event.Err) + } + if event.Action != nil { + fmt.Printf("\nAgent[%s]: transfer to %+v\n\n======\n", event.AgentName, event.Action.TransferToAgent.DestAgentName) + } else { + fmt.Printf("\nAgent[%s]:\n%+v\n\n======\n", event.AgentName, event.Output.MessageOutput.Message) + } + } + + // failed to route + println("\n\n>>>>>>>>>failed to route<<<<<<<<<") + iter = runner.Query(ctx, "Book me a flight from New York to London tomorrow.") + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + log.Fatal(event.Err) + } + if event.Action != nil { + fmt.Printf("\nAgent[%s]: transfer to %+v\n\n======\n", event.AgentName, event.Action.TransferToAgent.DestAgentName) + } else { + fmt.Printf("\nAgent[%s]:\n%+v\n\n======\n", event.AgentName, event.Output.MessageOutput.Message) + } + } +} +``` + +得到结果: + +>>>>>>>>> query weather<<<<<<<<< +>>>>>>>>> +>>>>>>>> +>>>>>>> +>>>>>> +>>>>> +>>>> +>>> +>> +> +> Agent[RouterAgent]: +> +> assistant: +> +> tool_calls: +> +> {Index: ID:call_SKNsPwKCTdp1oHxSlAFt8sO6 Type:function Function:{Name:transfer_to_agent Arguments:{"agent_name":"WeatherAgent"}} Extra:map[]} +> +> finish_reason: tool_calls +> +> usage: &{201 17 218} +> +> ====== +> +> Agent[RouterAgent]: transfer to WeatherAgent +> +> ====== +> +> Agent[WeatherAgent]: +> +> assistant: +> +> tool_calls: +> +> {Index: ID:call_QMBdUwKj84hKDAwMMX1gOiES Type:function Function:{Name:get_weather Arguments:{"city":"Beijing"}} Extra:map[]} +> +> finish_reason: tool_calls +> +> usage: &{255 15 270} +> +> ====== +> +> Agent[WeatherAgent]: +> +> tool: the temperature in Beijing is 25°C +> +> tool_call_id: call_QMBdUwKj84hKDAwMMX1gOiES +> +> tool_call_name: get_weather +> +> ====== +> +> Agent[WeatherAgent]: +> +> assistant: The current temperature in Beijing is 25°C. +> +> finish_reason: stop +> +> usage: &{286 11 297} +> +> ====== +> +>>>>>>>>> failed to route<<<<<<<<< +>>>>>>>>> +>>>>>>>> +>>>>>>> +>>>>>> +>>>>> +>>>> +>>> +>> +> +> Agent[RouterAgent]: +> +> assistant: I'm unable to assist with booking flights. Please use a relevant travel service or booking platform to make your reservation. +> +> finish_reason: stop +> +> usage: &{206 23 229} +> +> ====== +> +> 完整示例见:github.com/cloudwego/eino-examples/ diff --git "a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: Workflow Agent.md" "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: Workflow Agent.md" new file mode 100644 index 00000000000..0005fdebe53 --- /dev/null +++ "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/Eino ADK: Workflow Agent.md" @@ -0,0 +1,204 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Workflow Agent' +weight: 0 +--- + +WorkflowAgent 支持以静态的模式运行多个 Agent。所谓“静态”,是指 Agent 之间的协作流程(如顺序、并行)是在代码中预先定义好的,而不是在运行时由 Agent 动态决定的。Eino ADK 提供了三种基础 Workflow Agent:Sequential、Parallel、Loop,他们之间可以互相嵌套以完成更复杂的任务。 + +默认情况下,Workflow 中每个 Agent 的输入由 History 章节中介绍的方式生成,可以通过 WithHistoryRewriter 自定 AgentInput 生成方式。 + +当 Agent 产生 ExitAction Event 后,Workflow Agent 会立刻退出,无论之后有没有其他需要运行的 Agent。 + +# SequentialAgent + +SequentialAgent 会按照你提供的顺序,依次执行一系列 Agent: + +![](/img/eino/sequential_agents.png) + +我们通过一个包含两个子 Agent 的 Research Agent 来介绍 SequentialAgent 的用法,其中第一个 Plan Agent 会接收一个研究主题,并生成研究计划;第二个 Write Agent 会接收研究主题与 Plan 产生研究计划(Write Agent 的输入依据 History 章节中介绍的默认方式生成,也可以通过 WithHistoryRewriter 自定义),并撰写报告。 + +首先创建两个子 Agent ,我们将两个 Agent 简化为仅包含 ChatModel,实践中可以通过为 Agent 增加 Tool 来增强 Agent 的 plan 和 write 能力: + +```go +import ( + "context" + "log" + "os" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/adk" + "github.com/cloudwego/eino/components/model" +) + +func newChatModel() model.ToolCallingChatModel { + cm, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{ + APIKey: os.Getenv("OPENAI_API_KEY"), + Model: os.Getenv("OPENAI_MODEL"), + }) + if err != nil { + log.Fatal(err) + } + return cm +} + +func NewPlanAgent() adk.Agent { + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "PlannerAgent", + Description: "Generates a research plan based on a topic.", + Instruction: ` +You are an expert research planner. +Your goal is to create a comprehensive, step-by-step research plan for a given topic. +The plan should be logical, clear, and easy to follow. +The user will provide the research topic. Your output must ONLY be the research plan itself, without any conversational text, introductions, or summaries.`, + Model: newChatModel(), + }) + if err != nil { + log.Fatal(err) + } + return a +} + +func NewWriterAgent() adk.Agent { + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "WriterAgent", + Description: "Writes a report based on a research plan.", + Instruction: ` +You are an expert academic writer. +You will be provided with a detailed research plan. +Your task is to expand on this plan to write a comprehensive, well-structured, and in-depth report. +The user will provide the research plan. Your output should be the complete final report.`, + Model: newChatModel(), + }) + if err != nil { + log.Fatal(err) + } + return a +} +``` + +之后使用 Sequential Agent 编排两个子 Agent: + +```go +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudwego/eino/adk" + + "github.com/cloudwego/eino-examples/adk/intro/workflow/sequential/subagents" +) + +func main() { + ctx := context.Background() + + a, err := adk.NewSequentialAgent(ctx, &adk.SequentialAgentConfig{ + Name: "ResearchAgent", + Description: "A sequential workflow for planning and writing a research report.", + SubAgents: []adk.Agent{subagents.NewPlanAgent(), subagents.NewWriterAgent()}, + }) + if err != nil { + log.Fatal(err) + } + + runner := adk.NewRunner(ctx, adk.RunnerConfig{ + Agent: a, + }) + + iter := runner.Query(ctx, "The history of Large Language Models") + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + fmt.Printf("Error: %v\n", event.Err) + break + } + msg, err := event.Output.MessageOutput.GetMessage() + if err != nil { + log.Fatal(err) + } + fmt.Printf("Agent[%s]:\n %+v\n\n===========\n\n", event.AgentName, msg) + } +} +``` + +运行结果如下: + +``` +Agent[PlannerAgent]: + assistant: Step 1: Define the Research Scope +- Determine the time frame for your historical analysis, starting from the early development of large language models (LLMs) to the present. + +...... + +Step 10: Update and Revise +- Plan for periodic updates to incorporate new developments in large language models as they arise. +- Keep abreast of publications and ongoing research in the field to maintain the relevance and accuracy of your research. +finish_reason: stop +usage: &{86 675 761} + +=========== + +Agent[WriterAgent]: + assistant: # The History of Large Language Models + +## Introduction + +The development of Large Language Models (LLMs) marks a significant milestone in the field of artificial intelligence (AI) and natural language processing (NLP). These models, capable of understanding and generating human-like text, have evolved rapidly over the past few decades, showcasing profound improvements in language comprehension and generation. This report explores the history of LLMs, tracing their evolution from early linguistic theories to the sophisticated models we see today. + +## Early Foundations in Linguistics and Computation + +...... + +## Conclusion + +The history of Large Language Models is a testament to the rapid evolution of artificial intelligence. From early linguistic theories and basic neural networks to sophisticated models capable of human-like language generation, each milestone has contributed to our current understanding and capabilities. As LLMs continue to advance, their potential to transform industries, improve communication, and enable new technologies remains vast. However, it is crucial that ethical considerations keep pace with technological advances to ensure these models benefit society at large. + +--- + +This comprehensive overview of the history of Large Language Models outlines their origins, evolution, and impact, providing a foundation for further exploration and research in this dynamic field. +finish_reason: stop +usage: &{74 1066 1140} + +=========== +``` + +# LoopAgent + +LoopAgent 基于 SequentialAgent 实现,在 SequentialAgent 运行完成后,再次从头运行: + +![](/img/eino/loop_agents.png) + +Agent 产生 ExitAction Event 时退出 LoopAgent,也可以配置 MaxIteration 来控制最大循环次数。通过 adk.NewLoopAgent 创建: + +``` +adk.NewLoopAgent(ctx, &adk.LoopAgentConfig{ + Name: "name", + Description: "description", + SubAgents: []adk.Agent{a1,a2}, + MaxIterations: 3, +}) +``` + +# ParallelAgent + +ParallelAgent 会并发运行若干 Agent: + +![](/img/eino/parallel_agents.png) + +通过 adk.NewParallelAgent 创建: + +``` +adk.NewParallelAgent(ctx, &adk.ParallelAgentConfig{ + Name: "name", + Description: "desc", + SubAgents: []adk.Agent{a1,a2}, +}) +``` diff --git "a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/_index.md" "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/_index.md" new file mode 100644 index 00000000000..8e8dec916da --- /dev/null +++ "b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent \345\256\236\347\216\260/_index.md" @@ -0,0 +1,12 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Agent 实现' +weight: 0 +--- + +用户可以通过实现 Agent 接口自定义 Agent。自定义 Agent 建议严格遵守上述规则,在应用、迭代、合作中可以带来便利。 + +简单自定义 Agent 可以参考: github.com/cloudwego/eino-examples/adk/intro/custom/myagent.go diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent.md b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent.md new file mode 100644 index 00000000000..c0b28ba462d --- /dev/null +++ b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Multi-Agent' +weight: 0 +--- + + diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/Eino ADK: Plan-Executor.md b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/Eino ADK: Plan-Executor.md new file mode 100644 index 00000000000..bc211daf900 --- /dev/null +++ b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/Eino ADK: Plan-Executor.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Plan-Executor' +weight: 0 +--- + +Plan-Executor 是基于 Eino ADK 提供的开箱即用的 multi-agent 封装,TODO diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/Eino ADK: Supervisor.md b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/Eino ADK: Supervisor.md new file mode 100644 index 00000000000..8c763244779 --- /dev/null +++ b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/Eino ADK: Supervisor.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Supervisor' +weight: 0 +--- + +Supervisor 是基于 Eino ADK 提供的开箱即用的 multi-agent 封装,TODO diff --git a/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/_index.md b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/_index.md new file mode 100644 index 00000000000..6def288ec6b --- /dev/null +++ b/content/zh/docs/eino/core_modules/eino_adk/Eino ADK: Multi-Agent/_index.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Multi-Agent ' +weight: 0 +--- + + diff --git "a/content/zh/docs/eino/core_modules/eino_adk/WIP: Agent \346\212\275\350\261\241.md" "b/content/zh/docs/eino/core_modules/eino_adk/WIP: Agent \346\212\275\350\261\241.md" new file mode 100644 index 00000000000..d007563afb8 --- /dev/null +++ "b/content/zh/docs/eino/core_modules/eino_adk/WIP: Agent \346\212\275\350\261\241.md" @@ -0,0 +1,607 @@ +--- +Description: "" +date: "2025-07-22" +lastmod: "" +tags: [] +title: 'Eino ADK: Agent 抽象' +weight: 0 +--- + +todo:更新 eino-examples 代码的链接引用 + +# Agent 定义 + +Eino 定义了 Agent 的基础接口,实现此接口的 Struct 可被视为一个 Agent: + +```go +// github.com/cloudwego/eino/adk/interface.go + +type Agent interface { + Name(ctx context.Context) string + Description(ctx context.Context) string + Run(ctx context.Context, input *AgentInput, opts ...AgentRunOption) *AsyncIterator[*AgentEvent] +} +``` + + + + + + + + + + +
Method
说明
Name
Agent 的名称,作为 Agent 的标识
Description
Agent 的职能描述信息,主要用于让其他的 Agent 了解和判断该 Agent 的职责或功能
Run
Agent 的核心执行方法,返回一个迭代器,调用者可以通过这个迭代器持续接收 Agent 产生的事件
+ +ADK 提供了一些常用的 Agent 实现,单 Agent 如:ChatModelAgent;静态路由的多 Agent 如:SequentialAgent;动态路由的多 Agent 如:Supervisor 等。 + +## AgentInput + +Run 方法接收 AgentInput 作为 Agent 的输入: + +```go +type AgentInput struct { + Messages []_Message_ +_ _EnableStreaming bool +} +``` + +Agent 通常以 ChatModel 为核心,因此规定 Agent 的输入为 Message List, 与 ChatModel 相同。Message List 中可以包括用户指令、对话历史、背景知识、样例数据等任何你希望传递给 Agent 的数据。例如: + +```go +import ( + "github.com/cloudwego/eino/adk" + "github.com/cloudwego/eino/schema" +) + +input := &adk.AgentInput{ + Messages: []adk.Message{ + schema.UserMessage("What's the capital of France?"), + schema.AssistantMessage("The capital of France is Paris.", nil), + schema.UserMessage("How far is it from London? "), + }, +} +``` + +EnableStreaming 用于向 Agent **建议**其输出模式,但它并非一个强制性约束。它的核心思想是控制那些同时支持流式和非流式输出的组件的行为,例如 ChatModel,而仅支持一种输出方式的组件,EnableStreaming 不会影响他们的行为。 + +- 当 EnableStreaming 设置为 true 时,对于 Agent 内部能够流式输出的组件(如 ChatModel 调用),应以流的形式逐步返回结果。如果某个组件天然不支持流式,它仍然可以按其原有的非流式方式工作。 +- 当 EnableStreaming 设置为 false 时,对于那些既能流式也能非流式输出的组件,此时会使用一次性返回完整结果的非流式模式。 + +如下图所示,ChatModel 既可以输出非流也可以输出流,Tool 只能输出非流,当 EnableStream=false 时,二者均输出非流;当 EnableStream=true 时,ChatModel 输出流,Tool 因为不具备输出流的能力,仍然输出非流。 + +![](/img/eino/difference_when_enable_stream.png) + +在 AgentOutput 中,会标明实际输出类型。 + +## AgentRunOption + +AgentRunOption 由 Agent 实现定义,可以在请求维度修改 Agent 配置或者控制 Agent 行为。Eino ADK 提供了 `WrapImplSpecificOptFn` 和 `GetImplSpecificOptions` 两个方法供 Agent 定义、读取 AgentRunOption。例如可以定义 WithModelName,在请求维度修改调用的模型: + +```go +// github.com/cloudwego/eino/adk/call_option.go +// func WrapImplSpecificOptFn[T any](optFn func(*T)) AgentRunOption +// func GetImplSpecificOptions[T any](base *T, opts ...AgentRunOption) *T + +import "github.com/cloudwego/eino/adk" + +type options struct { + modelName string +} + +func WithModelName(name string) adk.AgentRunOption { + return adk.WrapImplSpecificOptFn(func(t *options) { + t.modelName = name + }) +} + +func (m *MyAgent) Run(ctx context.Context, input *adk.AgentInput, opts ...adk.AgentRunOption) *adk.AsyncIterator[*adk.AgentEvent] { + o := &options{} + o = adk.GetImplSpecificOptions(o, opts...) + // run code... +} +``` + +使用 `GetImplSpecificOptions` 方法读取 AgentRunOptions 时,与所需类型(如例子中的 options)不符的 AgentRunOption 会被忽略。 + +AgentRunOption 具有一个 `DesignateAgent` 方法,调用该方法可以在调用多 Agent 系统时指定 Option 生效的 Agent。 + +## AsyncIterator + +Agent.Run 返回了一个迭代器 AsyncIterator[*AgentEvent]: + +```go +// github.com/cloudwego/eino/adk/utils.go + +type AsyncIterator[T any] struct { + ... +} + +func (ai *AsyncIterator[T]) Next() (T, bool) { + ... +} +``` + +它代表一个异步迭代器(异步指生产与消费之间没有同步控制),允许调用者以一种有序、阻塞的方式消费 Agent 在运行过程中产生的一系列事件。 + +- AsyncIterator 是一个泛型结构体,可以用于迭代任何类型的数据。但在 Agent 接口中, Run 方法返回的迭代器类型被固定为 AsyncIterator[*AgentEvent] 。这意味着,你从这个迭代器中获取的每一个元素,都将是一个指向 AgentEvent 对象的指针。AgentEvent 会在后续章节中详细说明。 +- 迭代器的主要交互方式是通过调用其 Next() 方法。这个方法的行为是 阻塞式 的,每次调用 Next() ,程序会暂停执行,直到以下两种情况之一发生: + - Agent 产生了一个新的 AgentEvent : Next() 方法会返回这个事件,调用者可以立即对其进行处理。 + - Agent 主动关闭了迭代器 : 当 Agent 不会再产生任何新的事件时(通常是 Agent 运行结束),它会关闭这个迭代器。此时, Next() 调用会结束阻塞并在第二个返回值返回 false,告知调用者迭代已经结束。 + +AsyncIterator 常在 for 循环中处理: + +```go +iter := myAgent.Run(xxx) // get AsyncIterator from Agent.Run + +for { + event, ok := iter.Next() + if !ok { + break + } + // handle event +} +``` + +AsyncIterator 可以由 `NewAsyncIteratorPair` 创建,该函数返回的另一个参数 AsyncGenerator 用来生产数据: + +```go +// github.com/cloudwego/eino/adk/utils.go + +func NewAsyncIteratorPair[T any]() (*AsyncIterator[T], *AsyncGenerator[T]) +``` + +Agent.Run 返回 AsyncIterator 旨在让调用者实时地接收到 Agent 产生的一系列 AgentEvent,因此 Agent.Run 通常会在 Goroutine 中运行 Agent 从而立刻返回 AsyncIterator 供调用者监听: + +```go +import "github.com/cloudwego/eino/adk" + +func (m *MyAgent) Run(ctx context.Context, input *adk.AgentInput, opts ...adk.AgentRunOption) *adk.AsyncIterator[*adk.AgentEvent] { + // handle input + iter, gen := adk.NewAsyncIteratorPair[*adk.AgentEvent]() + go func() { + defer func() { + // recover code + gen.Close() + }() + // agent run code + // gen.Send(event) + }() + return iter +} +``` + +## AgentWithOptions + +使用 AgentWithOptions 方法可以在 Eino ADK Agent 做一些通用配置: + +```go +// github.com/cloudwego/eino/adk/flow.go +func AgentWithOptions(ctx context.Context, agent Agent, opts ...AgentOption) Agent +``` + +比如 WithDisallowTransferToParent、WithHistoryRewriter 等,具体功能将在相关的章节中详细说明。 + +# AgentEvent + +AgentEvent 是 Agent 在其运行过程中产生的核心事件数据结构。其中包含了 Agent 的元信息、输出、行为和报错: + +```go +// github.com/cloudwego/eino/adk/interface.go + +type AgentEvent struct { + AgentName string + + RunPath []string + + Output *AgentOutput + + Action *AgentAction + + Err error +} +``` + +## AgentName & RunPath + +AgentEvent 包含的 AgentName 和 RunPath 字段是由框架自动填充的,它们提供了关于事件来源的重要上下文信息,尤其是在复杂的、由多个 Agent 构成的系统中。 + +- AgentName 标明了是哪一个 Agent 实例产生了当前的 AgentEvent 。 +- RunPath 记录了到达当前 Agent 的完整调用链路。RunPath 是一个字符串切片,它按顺序记录了从最初的入口 Agent 到当前产生事件的 Agent 的所有 AgentName。 + +## Output + +AgentOutput 封装了 Agent 产生的输出。Message 输出被设置在 MessageOutput 字段中,其他类型的输出被设置在 CustomizedOutput 字段中: + +```go +// github.com/cloudwego/eino/adk/interface.go + +type AgentOutput struct { + MessageOutput *MessageVariant + + CustomizedOutput any +} + +type MessageVariant struct { + IsStreaming bool + + Message Message + MessageStream MessageStream + // message role: Assistant or Tool + Role schema.RoleType + // only used when Role is Tool + ToolName string +} +``` + +MessageOutput 字段的类型 MessageVariant 是一个核心数据结构,以下是其主要功能的分解说明: + +1. 统一处理流式与非流式消息 + +Agent 的输出可能是两种形式: + +- 非流式 : 一次性返回一个完整的消息( Message )。 +- 流式 : 随着时间的推移,逐步返回一系列消息片段,最终构成一个完整的消息( MessageStream )。 + +IsStreaming 是一个标志位。它的值为 true 表示当前 MessageVariant 包含的是一个流式消息(应从 MessageStream 读取),为 false 则表示包含的是一个非流式消息(应从 Message 读取)。 + +1. 提供便捷的元数据访问 + +Message 结构体内部包含了一些重要的元信息,如消息的 Role(Assistant 或 Tool),为了方便快速地识别消息类型和来源, MessageVariant 将这些常用的元数据提升到了顶层: + +- Role:消息的角色。 +- ToolName:如果消息角色是 Tool ,这个字段会直接提供工具的名称。 + +这样做的好处是,代码在需要根据消息类型进行路由或决策时, 无需深入解析 Message 对象的具体内容 ,可以直接从 MessageVariant 的顶层字段获取所需信息,从而简化了逻辑,提高了代码的可读性和效率。 + +## AgentAction + +Agent 产生包含 AgentAction 的 Event 可以控制多 Agent 协作,比如立刻退出、中断、跳转等: + +```go +// github.com/cloudwego/eino/adk/interface.go + +type AgentAction struct { + Exit bool + + Interrupted *_InterruptInfo_ + +_ _TransferToAgent *_TransferToAgentAction_ + +_ _CustomizedAction any +} +``` + +比如:当 Agent 产生 Exit Action 时,Multi-Agent 会立刻退出;当 Agent 产生 Transfer Action 时,会跳转到目标 Agent 运行。Action 的具体用法会在相应功能介绍中说明。 + +# 多 Agent 协作 + +Eino ADK 提供了多 Agent 协作能力,包括由 Agent 在运行时动态决定将任务移交给其他 Agent,或者预先决定好 Agent 运行顺序。 + +## 上下文传递 + +在构建多 Agent 系统时,让不同 Agent 之间高效、准确地共享信息至关重要。Eino ADK 提供了两种核心的上下文传递机制,以满足不同的协作需求: History 和 SessionValues。 + +### History + +多 Agent 系统中每一个 Agent 产生的 AgentEvent 都会被保存到 History 中,在调用一个新 Agent 时(Workflow/ Transfer),History 中的 AgentEvent 会被转换并拼接到 AgentInput 中。 + +默认情况下,其他 Agent 的 Assistant 或 Tool Message,被转换为 User Message。这相当于在告诉当前的 LLM:“刚才, Agent_A 调用了 some_tool ,返回了 some_result 。现在,轮到你来决策了。” + +通过这种方式,其他 Agent 的行为被当作了提供给当前 Agent 的“外部信息”或“事实陈述”,而不是它自己的行为,从而避免了 LLM 的上下文混乱。 + +![](/img/eino/transfer_agent_input.png) + +在 Eino ADK 中,当为一个 Agent 构建 AgentInput 时,会对 History 中的 Event 进行过滤,确保 Agent 只会接收到当前 Agent 的直接或间接父 Agent 产生的 Event。换句话说,只有当 Event 的 RunPath “属于”当前 Agent 的 RunPath 时,该 Event 才会参与构建 Agent 的 Input。 + +> 💡 +> RunPathA “属于” RunPathB 定义为 RunPathA 与 RunPathB 相同或者 RunPathA 是 RunPathB 的前缀 + +#### WithHistoryRewriter + +通过 AgentWithOptions 可以自定义 Agent 从 History 中生成 AgentInput 的方式: + +```go +// github.com/cloudwego/eino/adk/flow.go + +type HistoryRewriter func(ctx context.Context, entries []*HistoryEntry) ([]Message, error) + +func WithHistoryRewriter(h HistoryRewriter) AgentOption +``` + +### SessionValues + +SessionValues 是在一次运行中持续存在的全局临时 KV 存储,一次运行中的任何 Agent 可以在任何时间读写 SessionValues。Eino ADK 提供了三种方法访问 SessionValues: + +```go +// github.com/cloudwego/eino/adk/runctx.go +// 获取全部 SessionValues +func GetSessionValues(ctx context.Context) map[string]any +// 设置 SessionValues +func SetSessionValue(ctx context.Context, key string, value any) +// 指定 key 获取 SessionValues 中的一个值,key 不存在时第二个返回值为 false,否则为 true +func GetSessionValue(ctx context.Context, key string) (any, bool) +``` + +## Transfer SubAgents + +Agent 运行时产生带有包含 TransferAction 的 AgentEvent 后,Eino ADK 会调用 Action 指定的 Agent,被调用的 Agent 被称为子 Agent(SubAgent)。TransferAction 可以使用 `NewTransferToAgentAction` 快速创建: + +```go +import "github.com/cloudwego/eino/adk" + +event := adk.NewTransferToAgentAction("dest agent name") +``` + +为了让 Eino ADK 在接受到 TransferAction 可以找到子 Agent 实例并运行,在运行前需要先调用 `SetSubAgents` 将可能的子 Agent 注册到 Eino ADK 中: + +```go +// github.com/cloudwego/eino/adk/flow.go +func SetSubAgents(ctx context.Context, agent Agent, subAgents []Agent) (Agent, error) +``` + +> 💡 +> Transfer 的含义是将任务**移交**给子 Agent,而不是委托或者分配,因此: +> +> 1. 区别于 ToolCall,通过 Transfer 调用子 Agent,子 Agent 运行结束后,不会再调用父 Agent 总结内容或进行下一步操作。 +> 2. 调用子 Agent 时,子 Agent 的输入仍然是原始输入,父 Agent 的输出会作为上下文供子 Agent 参考。 + +以上描述中,产生 TransferAction Agent 天然清楚自己的子 Agent 有哪些,另外一些 Agent 需要根据不同场景配置不同的子 Agent,比如 Eino ADK 提供的 ChatModelAgent,这是一个通用 Agent 模板,需要根据业务实际场景配置子 Agent。这样的 Agent 需要能动态地注册父子 Agent,Eino 定义了 `OnSubAgents` 接口,用来支持此功能: + +```go +// github.com/cloudwego/eino/adk/interface.go +type OnSubAgents interface { + OnSetSubAgents(ctx context.Context, subAgents []Agent) error + OnSetAsSubAgent(ctx context.Context, parent Agent) error + + OnDisallowTransferToParent(ctx context.Context) error +} +``` + +如果 Agent 实现了 `OnSubAgents` 接口,`SetSubAgents` 中会调用相应的方法向 Agent 注册。 + +接下来以一个多功能对话 Agent 演示 Transfer 能力,目标是搭建一个可以查询天气或者与用户对话的 Agent,Agent 结构如下: + +![](/img/eino/transfer_sub_agents.png) + +三个 Agent 均使用 ChatModelAgent 实现: + +```go +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/adk" + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/components/tool/utils" + "github.com/cloudwego/eino/compose" +) + +func newChatModel() model.ToolCallingChatModel { + cm, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{ + APIKey: os.Getenv("OPENAI_API_KEY"), + Model: os.Getenv("OPENAI_MODEL"), + }) + if err != nil { + log.Fatal(err) + } + return cm +} + +type GetWeatherInput struct { + City string `json:"city"` +} + +func NewWeatherAgent() adk.Agent { + weatherTool, err := utils.InferTool( + "get_weather", + "Gets the current weather for a specific city.", // English description + func(ctx context.Context, input *GetWeatherInput) (string, error) { + return fmt.Sprintf(`the temperature in %s is 25°C`, input.City), nil + }, + ) + if err != nil { + log.Fatal(err) + } + + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "WeatherAgent", + Description: "This agent can get the current weather for a given city.", + Instruction: "Your sole purpose is to get the current weather for a given city by using the 'get_weather' tool. After calling the tool, report the result directly to the user.", + Model: newChatModel(), + ToolsConfig: adk.ToolsConfig{ + ToolsNodeConfig: compose.ToolsNodeConfig{ + Tools: []tool.BaseTool{weatherTool}, + }, + }, + }) + if err != nil { + log.Fatal(err) + } + return a +} + +func NewChatAgent() adk.Agent { + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "ChatAgent", + Description: "A general-purpose agent for handling conversational chat.", // English description + Instruction: "You are a friendly conversational assistant. Your role is to handle general chit-chat and answer questions that are not related to any specific tool-based tasks.", + Model: newChatModel(), + }) + if err != nil { + log.Fatal(err) + } + return a +} + +func NewRouterAgent() adk.Agent { + a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{ + Name: "RouterAgent", + Description: "A manual router that transfers tasks to other expert agents.", + Instruction: `You are an intelligent task router. Your responsibility is to analyze the user's request and delegate it to the most appropriate expert agent.If no Agent can handle the task, simply inform the user it cannot be processed.`, + Model: newChatModel(), + }) + if err != nil { + log.Fatal(err) + } + return a +} +``` + +之后使用 Eino ADK 的 Transfer 能力搭建 Multi-Agent 并运行,ChatModelAgent 实现了 OnSubAgent 接口,在 adk.SetSubAgents 方法中会使用此接口向 ChatModelAgent 注册父/子 Agent,不需要用户处理 TransferAction 生成问题: + +```go +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudwego/eino/adk" + + "github.com/cloudwego/eino-examples/adk/intro/transfer/subagents" +) + +func main() { + weatherAgent := subagents.NewWeatherAgent() + chatAgent := subagents.NewChatAgent() + routerAgent := subagents.NewRouterAgent() + + ctx := context.Background() + a, err := adk.SetSubAgents(ctx, routerAgent, []adk.Agent{chatAgent, weatherAgent}) + if err != nil { + log.Fatal(err) + } + + runner := adk.NewRunner(ctx, adk.RunnerConfig{ + Agent: a, + }) + + // query weather + println("\n\n>>>>>>>>>query weather<<<<<<<<<") + iter := runner.Query(ctx, "What's the weather in Beijing?") + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + log.Fatal(event.Err) + } + if event.Action != nil { + fmt.Printf("\nAgent[%s]: transfer to %+v\n\n======\n", event.AgentName, event.Action.TransferToAgent.DestAgentName) + } else { + fmt.Printf("\nAgent[%s]:\n%+v\n\n======\n", event.AgentName, event.Output.MessageOutput.Message) + } + } + + // failed to route + println("\n\n>>>>>>>>>failed to route<<<<<<<<<") + iter = runner.Query(ctx, "Book me a flight from New York to London tomorrow.") + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Err != nil { + log.Fatal(event.Err) + } + if event.Action != nil { + fmt.Printf("\nAgent[%s]: transfer to %+v\n\n======\n", event.AgentName, event.Action.TransferToAgent.DestAgentName) + } else { + fmt.Printf("\nAgent[%s]:\n%+v\n\n======\n", event.AgentName, event.Output.MessageOutput.Message) + } + } +} +``` + +得到结果: + +>>>>>>>>> query weather<<<<<<<<< +>>>>>>>>> +>>>>>>>> +>>>>>>> +>>>>>> +>>>>> +>>>> +>>> +>> +> +> Agent[RouterAgent]: +> +> assistant: +> +> tool_calls: +> +> {Index: ID:call_SKNsPwKCTdp1oHxSlAFt8sO6 Type:function Function:{Name:transfer_to_agent Arguments:{"agent_name":"WeatherAgent"}} Extra:map[]} +> +> finish_reason: tool_calls +> +> usage: &{201 17 218} +> +> ====== +> +> Agent[RouterAgent]: transfer to WeatherAgent +> +> ====== +> +> Agent[WeatherAgent]: +> +> assistant: +> +> tool_calls: +> +> {Index: ID:call_QMBdUwKj84hKDAwMMX1gOiES Type:function Function:{Name:get_weather Arguments:{"city":"Beijing"}} Extra:map[]} +> +> finish_reason: tool_calls +> +> usage: &{255 15 270} +> +> ====== +> +> Agent[WeatherAgent]: +> +> tool: the temperature in Beijing is 25°C +> +> tool_call_id: call_QMBdUwKj84hKDAwMMX1gOiES +> +> tool_call_name: get_weather +> +> ====== +> +> Agent[WeatherAgent]: +> +> assistant: The current temperature in Beijing is 25°C. +> +> finish_reason: stop +> +> usage: &{286 11 297} +> +> ====== +> +>>>>>>>>> failed to route<<<<<<<<< +>>>>>>>>> +>>>>>>>> +>>>>>>> +>>>>>> +>>>>> +>>>> +>>> +>> +> +> Agent[RouterAgent]: +> +> assistant: I'm unable to assist with booking flights. Please use a relevant travel service or booking platform to make your reservation. +> +> finish_reason: stop +> +> usage: &{206 23 229} +> +> ====== +> +> 完整示例见:github.com/cloudwego/eino-examples/ diff --git "a/content/zh/docs/eino/core_modules/eino_adk/WIP: Eino ADK: \346\246\202\350\277\260.md" "b/content/zh/docs/eino/core_modules/eino_adk/WIP: Eino ADK: \346\246\202\350\277\260.md" new file mode 100644 index 00000000000..9f8348c0074 --- /dev/null +++ "b/content/zh/docs/eino/core_modules/eino_adk/WIP: Eino ADK: \346\246\202\350\277\260.md" @@ -0,0 +1,345 @@ +--- +Description: "" +date: "2025-07-23" +lastmod: "" +tags: [] +title: 'Eino ADK: 概述' +weight: 0 +--- + +### Resources +| Category | Location | +|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| **Core Code** | [`cloudwego/eino@feat/adk`](https://github.com/cloudwego/eino/tree/feat/adk/adk) | +| **Releases** | Branch `release/v0.5.0-alpha.X`
(e.g. [`v0.5.0-alpha.1`](https://github.com/cloudwego/eino/releases/tag/v0.5.0-alpha.1)) | +| **Examples** | [`cloudwego/eino-examples@feat/adk`](https://github.com/cloudwego/eino-examples/tree/feat/adk/adk) | +| **Documentation** | [`cloudwego/cloudwego.github.io@feat/adk`](https://github.com/cloudwego/cloudwego.github.io/tree/main/content/zh/docs/eino/core_modules/eino_adk) | + +# 什么是 Eino ADK? + +Eino ADK 参考 [Google-ADK](https://google.github.io/adk-docs/agents/) 的设计,提供了 Go 语言 的 Agents 开发的灵活组合框架,即 Agent、Multi-Agent 开发框架。Eino ADK 为多 Agent 交互时,沉淀了通用的 上下文传递、事件流分发和转换、任务控制权转让、中断与恢复、通用切面等能力。 适用场景广泛、模型无关、部署无关,让 Agent、Multi-Agent 开发更加简单、便利,并提供完善的生产级应用的治理能力。 + +Eino ADK 旨在帮助开发者开发、管理 Agent 应用。提供灵活且鲁棒的开发环境,助力开发者搭建 对话智能体、非对话智能体、复杂任务、工作流等多种多样的 Agent 应用。 + +# ADK 框架结构 + +Eino ADK 的整体模块构成,如下图所示: + +![](/img/eino/eino_adk_architecture.png) + +## Agent Interface + +Eino ADK 的核心成员是 Agent 抽象(Agent Interface),ADK 的所有功能设计均围绕 Agent 抽象展开。 + +Agent 核心行为抽象大致可描述为: + +> 从入参中 AgentInput、AgentRunOption 和 可选的 Context Session,获取任务详情及相关数据 +> +> 执行任务,并将执行过程、执行结果输出到 AgenEvent Iterator +> +> 执行任务时,可通过 Context 中的 Session 暂存数据 + +```go +type Agent interface { + Name(ctx context.Context) string + Description(ctx context.Context) string + Run(ctx context.Context, input *AgentInput) *AsyncIterator[*AgentEvent] +} +``` + +在 Agent Run 的实现中,一般是 Future 模式的异步执行,大体分成三步,具体可参考 adk.ChatModelAgent 中 Run 方法的实现: + +1. 创建一对 Iterator、Generator +2. 启动 Agent 的异步任务,并传入 Generator,处理 AgentInput。在这个异步任务中,产生新的事件时,写入到 Generator 中,供 Agent 调用方在 Iterator 中消费 +3. 启动任务后,返回 Iterator + +Agent 扩展行为抽象大致可描述为: + +> Agent 既可添加子 Agent,也可添加父 Agent,即作为 父 Agent 的 子 Agent +> +> Agent 在执行任务时,可根据需要将任务转让(Transfer)给其 父 Agent 或 子 Agent + +```go +type OnSubAgents interface { + OnSetSubAgents(ctx context.Context, subAgents []Agent) error + OnSetAsSubAgent(ctx context.Context, parent Agent) error + + OnDisallowTransferToParent(ctx context.Context) error +} +``` + +中心化的运行状态管理: + +在 ADK 组合产物的运行中,每个 Agent 均可通过 context 中的 `Session map[string]any` 存取一些数据,Session 对所有 Agent 可见。Session 的生命周期对应着 ADK 组合产物的一次运行的生命周期(Interrupt&Resume 视为同一个生命周期)。 + +```go +func SetSessionValue(ctx context.Context, key string, value any) { + // omit code +} + +func GetSessionValue(ctx context.Context, key string) (any, bool) { + // omit code +} +``` + +## Agent Compose + +围绕 Agent 抽象,提供多种简单易用、场景丰富的组合原语,可支撑开发丰富多样的 Multi-Agent 协同策略,比如 Supervisor、Plan-Execute、Group-Chat 等 Multi-Agent 场景。从而实现不同的 Agent 分工合作模式,处理更复杂的任务。 + +Agent 协作过程中,可能存在的协作原语: + +- Agent 间协作方式 + + + + + + + + +
协助方式
描述

Transfer
直接将任务转让给另外一个 Agent,本 Agent 则执行结束后退出,不关心转让 Agent 的任务执行状态

ToolCall
(AgentAsTool)
将 Agent 当成 ToolCall 调用,等待 Agent 的响应,并可获取被调用Agent 的输出结果,进行下一轮处理

+ +- AgentInput 的上下文策略 + + + + + + + + +
上下文策略
描述

上游 Agent 全对话
获取本 Agent 的上游 Agent 的完整对话记录

全新任务描述
忽略掉上游 Agent 的完整对话记录,给出一个全新的任务总结,作为子 Agent 的 AgentInput 输入

+ +- 决策自主性 + + + + + + + + +
决策自主性
描述

自主决策

在 Agent 内部,基于其可选的下游 Agent, 如需协助时,自主选择下游 Agent 进行协助。 一般来说,Agent 内部是基于 LLM 进行决策,不过即使是基于预设逻辑进行选择,从 Agent 外部看依然视为自主决策

预设决策
事先预设好一个Agent 执行任务后的下一个 Agent。 Agent 的执行顺序是事先确定、可预测的

+ +接下来简要说明下,Agent Compose 下的不同的组合原语。 + +### SubAgents + +> - Agent 间协作方式:Transfer +> - AgentInput 的上下文策略:上游 Agent 全对话 +> - 决策自主性:自主决策 + +将用户提供的 agent 作为 父 Agent,用户提供的 subAgents 列表作为 子 Agents,组合而成可自主决策的 Agent,其中的 Name 和 Description 作为该 Agent 的名称标识和描述。 + +- 当前限定一个 Agent 只能有一个 父 Agent +- 可采用 SetSubAgents 函数,构建 「多叉树」 形式的 Multi-Agent +- 在这个「多叉树」中,AgentName 需要保持唯一 + +```go +func SetSubAgents(ctx context.Context, agent Agent, subAgents []Agent) (Agent, error) { + // omit code +} +``` + +![](/img/eino/sub_agents_outline.png) + +### Workflow + +提供了 顺序、并行和循环三种工作流模式,供用户灵活组合出不同的工作流图 + +在 Workflow Agent 中,每个 Agent 拿到相同的 AgentInput 输入,按照预先设定好的拓扑结构所表达的顺序依次运行 + +#### Sequential + +> - Agent 间协作方式:Transfer +> - AgentInput 的上下文策略:上游 Agent 全对话 +> - 决策自主性:预设决策 + +将用户提供的 SubAgents 列表,组合成按照顺序依次执行的 Sequential Agent,其中的 Name 和 Description 作为 Sequential Agent 的名称标识和描述。 + +Sequential Agent 执行时,将 SubAgents 列表,按照顺序依次执行,直至将所有 Agent 执行一遍后结束。 + +注: 由于 Agent 只能获取到上游 Agent 的全对话,后执行的 Agent 看不到先执行的 Agent 的 AgentEvent 输出。 + +```go +type SequentialAgentConfig struct { + Name string + Description string + SubAgents []Agent +} + +func NewSequentialAgent(ctx context.Context, config *SequentialAgentConfig) (Agent, error) { + // omit code +} +``` + +![](/img/eino/sequential_workflow.png) + +#### Parallel + +> - Agent 间协作方式:Transfer +> - AgentInput 的上下文策略:上游 Agent 全对话 +> - 决策自主性:预设决策 + +将用户提供的 SubAgents 列表,组合成基于相同上下文,并发执行的 Parallel Agent,其中的 Name 和 Description 作为 Parallel Agent 的名称标识和描述。 + +Parallel Agent 执行时,将 SubAgents 列表,并发执行,待所有 Agent 执行完成后结束。 + +```go +type ParallelAgentConfig struct { + Name string + Description string + SubAgents []Agent +} + +func NewParallelAgent(ctx context.Context, config *ParallelAgentConfig) (Agent, error) { + // omit code +} +``` + +![](/img/eino/parallel_workflow_outline.png) + +#### Loop + +> - Agent 间协作方式:Transfer +> - AgentInput 的上下文策略:上游 Agent 全对话 +> - 决策自主性:预设决策 + +将用户提供的 SubAgents 列表,按照数组顺序依次执行,循环往复,组合成 Loop Agent,其中的 Name 和 Description 作为 Loop Agent 的名称标识和描述。 + +Sequential Agent 执行时,将 SubAgents 列表,并发执行,待所有 Agent 执行完成后结束。 + +```go +type LoopAgentConfig struct { + Name string + Description string + SubAgents []Agent + + MaxIterations int +} + +func NewLoopAgent(ctx context.Context, config *LoopAgentConfig) (Agent, error) { + // omit code +} +``` + +![](/img/eino/loop_workflow_outline.png) + +### AgentAsTool + +> - Agent 间协作方式:ToolCall +> - AgentInput 的上下文策略:全新任务描述 +> - 决策自主性:自主决策 + +将一个 Agent 转换成 Tool,被其他的 Agent 当成普通的 Tool 使用。 + +注:一个 Agent 能否将其他 Agent 当成 Tool 进行调用,取决于自身的实现。adk 中提供的 ChatModelAgent 支持 AgentAsTool 的功能 + +```go +func NewAgentTool(_ context.Context, agent Agent, options ...AgentToolOption) tool.BaseTool { + // omit code +} +``` + +下图展示了 Agent1 把 Agent2、Agent3 当成 Tool 进行调用的过程,类似 Function Stack Call,即在 Agent1 运行过程中,将 Agent2、Agent3 当成工具函数来进行调用。 + +- AgentAsTool 可作为 Supervisor Multi-Agent 的一种实现方式 + +![](/img/eino/agent_as_tool_outline.png) + +## Single Agent + +> Built-In Single Agent + +Eino ADK 中将内置多种 Single Agent 实现,方便在各种业务场景中,找到合适的 Agent 实现,开箱即用 + +### ChatModelAgent + +ChatModelAgent 实现了 ReAct 范式的 Agent,基于 Eino 中的 Graph 编排出 ReAct Agent 控制流,通过 callbacks.Handler 导出 ReAct Agent 运行过程中产生的事件,转换成 AgentEvent 返回。 + +想要进一步了解 ChatModelAgent,请看:[Eino ADK: ChatModelAgent](/zh/docs/eino/core_modules/eino_adk/Eino ADK: Agent 实现/Eino ADK: ChatModelAgent) + +```go +type ChatModelAgentConfig struct { + Name string + Description string + Instruction string + + Model model.ToolCallingChatModel + + ToolsConfig ToolsConfig + + // optional + GenModelInput GenModelInput + + // Exit tool. Optional, defaults to nil, which will generate an Exit Action. + // The built-in implementation is 'ExitTool' + Exit tool.BaseTool + + // optional + OutputKey string +} + +func NewChatModelAgent(_ context.Context, config *ChatModelAgentConfig) (*ChatModelAgent, error) { + // omit code +} +``` + +### A2AAgent + +> 开发中 + +ResponseAgent + +> 待规划 + +# Agent 运行 + +AgentRunner 是 Agent 的执行器。 + +通过 ADK 框架,编排组合出的 Multi-Agent,推荐采用 adk.NewRunner 包装执行。 只有通过 Runner 执行 agent 时,才可以使用 ADK 的如下功能: + +- Interrupt & Resume +- 切面机制 +- Context 环境的预处理 + +```go +type RunnerConfig struct { + EnableStreaming bool +} + +func NewRunner(_ context.Context, conf RunnerConfig) *Runner { + // omit code +} +``` + +## Agent 运行示例 + +Agent 执行时,通过 adk.NewRunner 包装执行,方便使用 adk 提供的各种扩展能力 + +```go +func runWithRunner() { + ctx := context.Background() + + // 创建 Runner + runner := adk.NewRunner(ctx, adk.RunnerConfig{ + EnableStreaming: true, + }) + + // 执行代理 + messages := []adk.Message{ + schema.UserMessage("What's the weather like today?"), + } + + events := runner.Run(ctx, agent, messages) + for { + event, ok := events.Next() + if !ok { + break + } + + // 处理事件 + handleEvent(event) + } +} +``` diff --git a/content/zh/docs/eino/core_modules/eino_adk/_index.md b/content/zh/docs/eino/core_modules/eino_adk/_index.md new file mode 100644 index 00000000000..cec28ab2a97 --- /dev/null +++ b/content/zh/docs/eino/core_modules/eino_adk/_index.md @@ -0,0 +1,10 @@ +--- +Description: "" +date: "2025-07-21" +lastmod: "" +tags: [] +title: 'Eino: ADK - Agent Development Kit' +weight: 4 +--- + + diff --git a/static/img/eino/agent_as_tool_outline.png b/static/img/eino/agent_as_tool_outline.png new file mode 100644 index 00000000000..f8867a4f694 Binary files /dev/null and b/static/img/eino/agent_as_tool_outline.png differ diff --git a/static/img/eino/difference_when_enable_stream.png b/static/img/eino/difference_when_enable_stream.png new file mode 100644 index 00000000000..97991621fc6 Binary files /dev/null and b/static/img/eino/difference_when_enable_stream.png differ diff --git a/static/img/eino/eino_adk_architecture.png b/static/img/eino/eino_adk_architecture.png new file mode 100644 index 00000000000..33f84374715 Binary files /dev/null and b/static/img/eino/eino_adk_architecture.png differ diff --git a/static/img/eino/loop_agents.png b/static/img/eino/loop_agents.png new file mode 100644 index 00000000000..dbb389587a0 Binary files /dev/null and b/static/img/eino/loop_agents.png differ diff --git a/static/img/eino/loop_workflow_outline.png b/static/img/eino/loop_workflow_outline.png new file mode 100644 index 00000000000..034f7454717 Binary files /dev/null and b/static/img/eino/loop_workflow_outline.png differ diff --git a/static/img/eino/parallel_agents.png b/static/img/eino/parallel_agents.png new file mode 100644 index 00000000000..8456027be32 Binary files /dev/null and b/static/img/eino/parallel_agents.png differ diff --git a/static/img/eino/parallel_workflow_outline.png b/static/img/eino/parallel_workflow_outline.png new file mode 100644 index 00000000000..954bc1cc441 Binary files /dev/null and b/static/img/eino/parallel_workflow_outline.png differ diff --git a/static/img/eino/react_agent_pattern.png b/static/img/eino/react_agent_pattern.png new file mode 100644 index 00000000000..0aa304f769f Binary files /dev/null and b/static/img/eino/react_agent_pattern.png differ diff --git a/static/img/eino/sequential_agents.png b/static/img/eino/sequential_agents.png new file mode 100644 index 00000000000..f404e4e3dbb Binary files /dev/null and b/static/img/eino/sequential_agents.png differ diff --git a/static/img/eino/sequential_workflow.png b/static/img/eino/sequential_workflow.png new file mode 100644 index 00000000000..d56e4b23f59 Binary files /dev/null and b/static/img/eino/sequential_workflow.png differ diff --git a/static/img/eino/sub_agents_outline.png b/static/img/eino/sub_agents_outline.png new file mode 100644 index 00000000000..de1b9c0c4cb Binary files /dev/null and b/static/img/eino/sub_agents_outline.png differ diff --git a/static/img/eino/transfer_agent_input.png b/static/img/eino/transfer_agent_input.png new file mode 100644 index 00000000000..2e1b362a6c9 Binary files /dev/null and b/static/img/eino/transfer_agent_input.png differ diff --git a/static/img/eino/transfer_sub_agents.png b/static/img/eino/transfer_sub_agents.png new file mode 100644 index 00000000000..ab00e50c006 Binary files /dev/null and b/static/img/eino/transfer_sub_agents.png differ