Files
cs-note/Eino/docs/overview/graph_or_agent.md
T
2026-05-24 11:42:38 +08:00

349 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
Description: ""
date: "2026-03-24"
lastmod: ""
tags: []
title: Agent 还是 Graph?AI 应用路线辨析
weight: 8
---
## 引言:两种并存的 AI 交互范式
许多应用程序的界面都集成了不同形态的 AI 功能,如下图所示:
<a href="/img/eino/eino_ai_app_form.png" target="_blank"><img src="/img/eino/eino_ai_app_form.png" width="100%" /></a>
这张看似简单的截图,代表了“AI 应用”的两种形态:
- 以“聊天框”为代表性标志的“Agent(智能体)”。**Agent 以 LLM(大语言模型)为决策中心,自主规划并能进行多轮交互**,天然适合处理开放式、持续性的任务,表现为一种“对话”形态。
- 以“按钮”或者“API”为代表性标志的“Graph(流程图)”。比如上面的“录音纪要”这个“按钮”,其背后的 Graph 大概是“录音”-》“LLM 理解并总结” -》“保存录音”这种固定流程。**Graph 的核心在于其流程的确定性与任务的封闭性**,通过预定义的节点和边来完成特定目标,表现为一种“功能”形态。举个例子,视频生成是 “API”形态 AI 应用:
<a href="/img/eino/eino_complex_workflow_as_api.png" target="_blank"><img src="/img/eino/eino_complex_workflow_as_api.png" width="100%" /></a>
```mermaid
flowchart TD
linkStyle default stroke-width:2px,stroke:#000000
classDef startend_style fill:#EAE2FE,stroke:#000000,stroke-width:2px,color:#1f2329
classDef process_style fill:#F0F4FC,stroke:#000000,stroke-width:2px,color:#1f2329
classDef decision_style fill:#FEF1CE,stroke:#000000,stroke-width:2px,color:#1f2329
classDef subgraph_style fill:#f5f5f5,stroke:#bbbfc4,stroke-width:1px,color:#000000
S(["AI 应用形态"])
D{"任务特征"}
A("Agent")
G("Graph")
A1("LLM 决策中心")
A2("多轮交互")
G1("预设拓扑结构")
G2("确定性输出")
S --> D
D -->|"开放式或不确定"| A
D -->|"封闭且确定"| G
A --> A1
A --> A2
G --> G1
G --> G2
class S startend_style
class D decision_style
class A,G,A1,A2,G1,G2 process_style
```
本文详细探讨了 Agent 和 Graph 两种 AI 应用形态的区别和联系,提出“两者的最佳结合点,在于将 Graph 封装为 Agent 的 Tool(工具)”,并为 [Eino](https://github.com/cloudwego/eino) 开发者给出建议的使用姿势。
## 核心概念辨析
### 基础定义
- **Graph**: 一个由开发者**预先定义**的、具有明确拓扑结构的流程图。它的节点可以是代码函数、API 调用或 LLM,输入和输出通常是结构化的。**核心特征是“确定性”**,即给定相同输入,其执行路径和最终产出是可预测的。
- **Agent**: 一个以 LLM 为核心,能够**自主规划、决策和执行**任务的实体。它通过与环境(Tool、用户、其他 Agent)的**动态交互**来完成目标,其行为具有不确定性。**核心特征是“自主性”**。
- **Tool**: Agent 可以调用的任何外部能力,通常是一个**封装了特定功能的函数或 API**。Tool 本身可以是同步或异步的、有状态或无状态的。它只负责执行,不具备自主决策能力。
- **编排**: **组织和协调多个计算单元(节点、Agent)协同工作**的过程。在本文中,特指通过 Graph 的方式来预定义静态流程。
### 深度对比
<table>
<tr><td>特征维度</td><td>Agent</td><td>Graph</td></tr>
<tr><td><strong>核心驱动力</strong></td><td><strong>LLM 自主决策</strong></td><td><strong>开发者预设流程</strong></td></tr>
<tr><td><strong>输入</strong></td><td><strong>非结构化</strong>的自然语言、图像等</td><td><strong>结构化</strong>的数据</td></tr>
<tr><td><strong>交付物</strong></td><td><strong>过程与结果并重</strong></td><td><strong>聚焦最终结果</strong></td></tr>
<tr><td><strong>状态管理</strong></td><td><strong>长时程、跨执行</strong></td><td><strong>单次执行、stateless</strong></td></tr>
<tr><td><strong>运行模式</strong></td><td>偏向<strong>异步</strong></td><td>偏向<strong>同步</strong></td></tr>
</table>
总结:Agent 可认为是自主的,整体由 LLM 驱动,以 Tool Call 的形式使用外部能力。Graph 是确定性的,以明确拓扑结构串联外部能力,同时在局部利用 LLM 做决策/生成等。
```mermaid
flowchart TD
subgraph AIApp["AI 应用"]
Agent["Agent (自主性)"]
Graph["Graph (确定性)"]
end
subgraph CoreDrive["智能来源"]
LLM["LLM (决策/生成)"]
end
subgraph ExternalCap["外部能力"]
Tool["External Capacity<br>(函数/API)"]
end
Agent -- "驱动力来源" --> LLM
Graph -- "包含节点" --> LLM
Agent -- "工具调用" --> Tool
Graph -- "包含节点" --> Tool
classDef agent fill:#EAE2FE,stroke:#000000
classDef graphClass fill:#F0F4FC,stroke:#000000
classDef llm fill:#FEF1CE,stroke:#000000
classDef tool fill:#DFF5E5,stroke:#000000
class Agent agent
class Graph graphClass
class LLM llm
class Tool tool
```
## 历史视角:从确定性走向自主性
当 Langchain 框架在 2022 年首次发布时,LLM 世界的 API 范式还是 OpenAI 的 [Completions API](https://platform.openai.com/docs/guides/completions),一个简单的“文本进,文本出”的 API。发布之初,Langchain 的口号是“[connect LLMs to external sources of computation and data](https://blog.langchain.com/langchain-second-birthday/)”。典型的“Chain”可能是这样的:
```mermaid
flowchart LR
S[retrievers, loaders, <br>prompt templates, etc...]
L[LLM]
P[output parsers, other handlers, etc...]
S-->L-->P
```
随后,[ReAct](https://react-lm.github.io/)(Reasoning and Acting)范式的提出,首次系统性地展示了如何让 LLM 不仅生成文本,更能通过“思考-行动-观察”的循环来与外部交互,解决复杂问题。这一突破为 Agent 的自主规划能力奠定了理论基础。近乎同时,OpenAI 推出了 [ChatCompletions API](https://platform.openai.com/docs/api-reference/chat),推动了 LLM 交互能力从“单次的文本输入输出”向“多轮对话”转变。之后 [Function Calling](https://platform.openai.com/docs/guides/function-calling)(函数调用) 能力出现,LLM 具备了标准的与外部函数和 API 交互的能力。至此,我们已经可以搭建出“多轮对话并与可以与外界自主交互”的 LLM 应用场景,即 Agent。在这个背景下,AI 应用框架产生了两个重要发展:
- Langchain 推出了 Langgraph:静态编排由简单的输入输出 Chain 向复杂拓扑结构转变。这类编排框架非常契合“Graph”类型的 AI 应用形态:“任意”的结构化输入,以“最终结果”为核心交付物,将消息历史等状态管理机制与核心编排逻辑解耦,可支持各种拓扑结构的灵活编排能力,以及以 LLM、知识库为代表的各种节点/组件。
- Agent 及 Multi-Agent 框架大量出现:比如 AutoGen,CrewAI,Google ADK 等。这些 Agent 框架的共同点,是尝试解决“LLM 驱动流程”、“上下文传递”、“记忆管理”以及“Multi-Agent 通用模式”等问题,与编排类框架尝试解决的“在复杂流程中连接 LLM 与外部系统”问题并不相同。
即便定位不同,使用编排框架也可以实现 ReAct Agent 或者其他 Multi-Agent 模式,因为“Agent”是“LLM 与外部系统交互”的一种特殊形式,而“LLM 驱动流程”可以通过“静态分支穷举”等方式来实现。然而,这种实现方式本质上是一种“模拟”,就像用 Word 写代码,可以写,但不匹配。编排框架的设计初衷是管理确定性的 Graph,而 Agent 的核心是响应动态变化的‘思考链’。将后者强行适配于前者,必然会在交付物、运行模式等方面产生“错位”。例如,在实际使用中,可能会发现一些痛点:
- 交付物的不匹配:编排出的 ReAct Agent 的输出是“最终结果”,而实际应用往往关注各种中间过程。用 Callback 等方案可以解决,足够完备,但依然属于“补丁”。
```mermaid
flowchart LR
A[ReAct Agent]
P@{ shape: processes, label: "全过程数据" }
A--o|关注|P
G[Graph]
F[最终结果]
G-->|主流程输出,<br>但被旁路输出涵盖|F
G-.->|旁路抽取|P
```
- 运行模式的不匹配:由于是同步运行,所以“为了尽快把 LLM 的回复展示给用户”,要求 ReAct Agent 编排内的各节点都尽量“快”,这主要是“在判断 LLM 的输出是否包含 ToolCall”的分支判断逻辑中,要尽可能根据第一个包或者前几个包完成判断。这个分支判断逻辑可以自定义,比如“读流式输出直到看到 Content,才判断为没有 ToolCall”,但有时并不能完全解决问题,只能通过 Callback 这样的“旁路”手动切换“同步”为“异步”。
```mermaid
flowchart LR
L[LLM 节点]
S@{ shape: processes, label: "流式内容"}
L-->|生成|S
B{是否包含<br>工具调用}
D@{ shape: processes, label: "流式内容"}
B-->|否,上屏展示|D
S-->|逐帧判断|B
```
这些痛点源于两者本质的差异。一个为确定性流程(Graph)设计的框架,很难原生支持一个以动态“思考链”为核心的自主系统(Agent)。
## 融合路径探索:Agent 与 Graph 的关系
Eino 框架的目标是同时支持 Graph 和 Agent 两种场景。我们的演进路径是从 Graph 和编排框架(eino-compose)做起,并在编排框架之外引入了相对独立的 Agent 能力(eino-adk)。这看上去会有些不必要的割裂,似乎“作为编排框架的 Eino”和“作为 Agent 框架的 Eino”是相互独立的,开发经验无法共享。现状确实如此,长期来看“相对独立”的状态会一直持续,但同时也会有局部的深度融合。
下面我们从下列三个角度分析“Agent”和“Graph”两个形态在 Eino 框架中的具体关系:
- Multi-Agent 的编排
- Agent 作为节点
- Graph 作为 Tool
### Multi-Agent 与编排
虽然“Agent”和“Graph”两个形态有本质的差异,那是否存在一些场景,属于两个形态的“交叉融合”,没法非黑即白的做选择呢?一个典型的场景是 Multi-Agent,即多个 Agent 以“某种方式”进行交互,对用户呈现的效果是一个完整的 Agent。这里的“某种交互方式”,可以理解为“Graph 编排”吗?
下面我们依次观察几种主流的协作模式:
- 层级调用(Agent as Tool):这是最常见的模式(参考 Google ADK 的[定义](https://google.github.io/adk-docs/agents/multi-agents/#c-explicit-invocation-agenttool)和[举例](https://google.github.io/adk-docs/agents/multi-agents/#hierarchical-task-decomposition))。一个上层 Agent 将特定子任务委托给专门的“Tool Agent”。例如,一个主 Agent 负责与用户交互,当需要执行代码时,它会调用一个“代码执行 Agent”。在这种模式下,子 Agent 通常是无状态的,不与主 Agent 共享记忆,其交互是一个简单的 Function Call。上层 Agent 和子 Agent 只有一种关系:调用与被调用。因此,我们可以得出,Agent as Tool 的 Multi-Agent 模式,不是“Graph 编排”中的“节点流转”关系。
```mermaid
flowchart LR
subgraph 主 Agent
L[主 Agent 的 LLM]
T1[子 Agent 1]
T2[子 Agent 2]
L-->|工具调用|T1
L-->|工具调用|T2
end
```
- 预设流程:对于一些成熟的协作模式,如“规划-执行-反思”(Plan-Execute-Replan)(参考 Langchain 的[样例](https://langchain-ai.github.io/langgraph/tutorials/plan-and-execute/plan-and-execute/)),Agent 间的交互顺序和角色是固定的。框架(如 Eino adk)可以将这些模式封装为“预制 Multi-Agent 模式”,开发者可以直接使用,无需关心内部的细节,也不需要手动设置或调整子 Agent 之间的流程关系。因此,我们可以得出,针对成熟的协作模式,“Graph 编排”是封装在预制模式内部的实现细节,开发者不感知。
```mermaid
flowchart LR
subgraph Plan-Execute-Replan
P[planner]
E[executor]
R[Replanner]
P-->E
E-->R
R-->E
end
user -->|整体使用| Plan-Execute-Replan
```
- 动态协作:在更复杂的场景中,Agent 的协作方式是动态的(参考 Google ADK 的[定义](https://google.github.io/adk-docs/agents/multi-agents/#b-llm-driven-delegation-agent-transfer)和[举例](https://google.github.io/adk-docs/agents/multi-agents/#coordinatordispatcher-pattern)),可能涉及竞价、投票或由一个“协调者 Agent”在运行时决定。这种模式下,Agent 之间的关系是“Agent 流转”,与“Graph 编排”中的“节点流转”有相似之处,都是“控制权”由 A 到 B 的完全转交。但是,这里的“Agent 流转”可以是完全动态的,其动态特性不仅体现在“可以流转到哪些 Agent”,更体现在“如何做出流转到哪个 Agent 的决策”上,都不是由开发者预设的,而是 LLM 的实时动态行为。这与“Graph 编排”的静态确定性形成了鲜明的对比。因此,我们可以得出,动态协作的 Multi-Agent 模式,从本质上与“Graph 编排”完全不同,更适合在 Agent 框架层面给出独立的解决方案。
```mermaid
flowchart LR
A[Agent 1]
B[Agent 2]
C[Agent 3]
A-.->|动态转交|B-.->|动态转交|C
```
综上所述,Multi-Agent 的协作问题,或可通过“Agent as Tool”模式降维解决,或可由框架提供固化模式,或是本质上完全动态的协作,其对“编排”的需求与 Graph 的静态的、确定性的流程编排有着本质区别。
### Agent 作为 Graph 的节点
在探讨完“Multi-Agent 与 Graph 编排的关系”后,我们可以从另一个角度提出问题:在 Graph 编排中是否需要使用 Agent?换句话说,Agent 是否可以作为一个“节点”进入到一个 Graph 中?
我们先回忆下 Agent 和 Graph 各自的特点:
- Agent 的输入来源更为多样,除了能接收来自上游节点的结构化数据外,还严重依赖于自身的会话历史(Memory)。这与 Graph 节点严格依赖其上游输出作为唯一输入的特性形成了鲜明对比。
- Agent 的输出是异步的全过程数据。这意味着其他节点很难使用“Agent 节点”的输出。
```mermaid
flowchart LR
U[前置节点]
A[Agent 节点]
D[后置节点]
M[Memory]
U-->|不是全部输入<br>|A
M-.->|外部状态注入|A
A-->|全过程数据<br>面向用户或 LLM<br>|D
```
因此,向 Graph 中加入 Agent 节点,意味着将一个需要多轮交互、长时记忆和异步输出的 Agent 强行嵌入到一个确定性的、同步执行的 Graph 节点中,这通常是不优雅的。Agent 的启动可以被 Graph 编排,但其内部的复杂交互不应阻塞主流程。
实际上,在 Graph 中我们需要的并非一个完整的 Agent 节点,而是一个功能更纯粹的**“LLM 节点”**。该节点负责在确定性流程中,接收特定输入,完成意图识别或内容生成,并产出结构化的输出,从而为流程注入智能。
同时,如果简单的“LLM”节点确实不满足需求,确实需要“Agent”,更合适的做法也许不是把 Agent 塞到静态预定义的 Graph 中,而是给“Agent”增加前置处理、后置处理等各种“插件”,把具体的业务逻辑嵌入到 Agent 内部。
综上所述:将 Agent 简单视为 Graph 的一个节点是**低效**的,更好的方式是使用 LLM 节点,或将业务逻辑作为插件注入 Agent。
### 融合之道:将 Graph 封装为 Agent 的 Tool
既然 Agent 和 Graph 在微观层面(节点)的直接融合存在困难,那么它们是否在宏观层面有更优雅的结合方式呢?答案是肯定的,这座桥梁就是“Tool”。如果观察 Graph 和 Tool 的含义,能发现很多相似之处:
<table>
<tr><td>特征维度</td><td>Graph</td><td>Tool</td></tr>
<tr><td>输入</td><td><strong>结构化的数据</strong></td><td><strong>结构化的数据</strong></td></tr>
<tr><td>交付物</td><td><strong>聚焦最终结果</strong></td><td><strong>聚焦最终结果</strong></td></tr>
<tr><td>状态管理</td><td><strong>单次执行、stateless</strong></td><td><strong>单次执行、stateless</strong></td></tr>
<tr><td>运行模式</td><td><strong>整体是同步</strong></td><td> <strong>LLM 的视角 Tool 是同步的</strong></td></tr>
</table>
这些相似之处,意味着“Graph 在表现形式上,与 Tool 的要求非常匹配,因此将 Graph 封装成 Tool 是直观、简单的”。因此,绝大多数 Graph 都适合通过 Tool 机制加入到 Agent 中,成为 Agent 能力的一部分。这样一来,Agent 可以明确的使用 Graph 的大部分能力,包括对“任意”业务拓扑的高效编排,对大量相关组件的生态集成,以及配套的框架和治理能力(流处理、callback、中断恢复等)。
“Agent”与“Graph”的“路线之争”,实现了对立统一。
```mermaid
flowchart TD
subgraph Agent ["Agent"]
A["LLM 决策"] --> B{"调用工具?"}
B -- "是" --> C["Tool: my_graph_tool"]
end
subgraph Tool ["Tool"]
C -- "封装" --> D["Graph: my_graph"]
end
subgraph Graph ["Graph"]
D -- "执行" --> E["节点1"]
E --> F["节点2"]
F --> G["返回结果"]
end
G -- "输出" --> C
C -- "结果" --> A
classDef agent fill:#EAE2FE,stroke:#000000
classDef tool fill:#DFF5E5,stroke:#000000
classDef graphGroup fill:#F0F4FC,stroke:#000000
class A,B agent
class C tool
class D,E,F,G graphGroup
```
Graph-Tool-Agent 关系图
## 结论
Agent 与 Graph 并非路线之争,而是能力互补的两种 AI 应用范式。
- Graph 是构建可靠、确定性 AI 功能的基石。 它擅长将复杂的业务逻辑、数据处理管道和 API 调用编排成可预测、可维护的工作流。当你需要一个“功能按钮”或一个稳定的后端服务时,Graph 是不二之选。
- Agent 是实现通用智能与自主探索的未来。 它以 LLM 为核心,通过动态规划和 Tool 来解决开放式问题。当你需要一个能与人对话、能自主完成复杂任务的“智能助理”时,Agent 是核心方向。
两者的最佳结合点,在于将 Graph 封装为 Agent 的 Tool。
通过这种方式,我们可以充分利用 Graph 在流程编排和生态集成上的强大能力,来扩展 Agent 的 Tool 列表。一个复杂的 Graph 应用(如一套完整的 RAG 流程、一个数据分析管道)可以被简化成 Agent 的一个原子能力,被其在合适的时机动态调用。
对于 Eino 的开发者而言,这意味着:
- 用 eino-compose 编写你的 Graph,将确定性的业务逻辑封装成“功能模块”。
- 用 eino-adk 构建你的 Agent,赋予它思考、规划和与用户交互的能力。
- 将前者作为后者的 Tools,最终实现“1+1 > 2”的效果。
代码示意:
```go
// NewInvokableGraphTool converts ANY Graph to the `InvokableTool` interface.
func NewInvokableGraphTool[I, O any](graph compose.Graph[I, O],
name, desc string,
opts ...compose.GraphCompileOption,
) (*InvokableGraphTool[I, O], error) {
tInfo, err := utils.GoStruct2ToolInfo[I](name, desc)
if err != nil {
return nil, err
}
return &InvokableGraphTool[I, O]{
graph: graph,
compileOptions: opts,
tInfo: tInfo,
}, nil
}
func (g *InvokableGraphTool[I, O]) InvokableRun(ctx context.Context, input string,
opts ...tool.Option) (output string, err error) {
// trigger callbacks where needed
// compile the graph
// convert input string to I
// run the graph
// handle interrupt
// convert output O to string
}
func (g *InvokableGraphTool[I, O]) Info(_ context.Context) (*schema.ToolInfo, error) {
return g.tInfo, nil
}
```
[eino-example 项目链接](https://github.com/cloudwego/eino-examples/tree/main/adk/common/tool/graphtool)