本节要纠正的一个流行误解:上一轮规划曾把"节点式 vs MCP"并列成"两种范式对比"——这是一次范畴错误,它把一个协议层的东西(MCP)和一个编排层的东西(节点图)放在同一维度比较。§1 先把区分讲透,后续所有对比都建立在"编排器实现决定范式,MCP 是共用工具层"这一正确框架上。
阅读路径:§1 范畴纠正 → §2–§3 两种编排范式原理 → §4 MCP 共用工具层(附最小 server) → §5 同一任务两种实现对照 → §6 四维对比表 → §7 选型决策树 → §8 混合模式(推荐主线+架构图) → §9 可观测性与失败定位 → §10 与系列其它节的接口。
1. 范畴纠正:编排 ≠ 协议
一个能"用模型+工具完成任务"的 AI 系统,无论长什么样,都拆得出三种角色:
- LLM / 模型:提供推理与生成能力(可被调用多次,也可能一次不调、纯编排数据)。
- 工具 / Tool:外部能力单元——查库、读文件、调 API、跑一段模型推理。
- 编排器 / Orchestrator:决定"何时调哪个模型/工具、按什么顺序、失败怎么办"。它的实现方式决定了范式。
关键在于:MCP(Model Context Protocol)不是编排器,它是工具层的一套 client-server 协议。它规定"一个工具如何被描述(schema)、如何被发现、如何被调用、返回什么格式",但不规定谁来决定调用顺序。决定调用顺序的,仍然是上面那个"编排器"。因此同一个 MCP 工具,既被"节点图编排器"按固定拓扑调用,也被"Agent 编排器"在 loop 里临时决定调用——MCP 处在被两种范式共享的下一层。把它们并列比较,等于问"Python 和 for 循环哪个更好",层次不同,无法对齐。
1.1 概念层次(表格 1)
| 层次 | 它回答的问题 | 典型成员 | 可变性 |
|---|---|---|---|
| 协议层 (工具接口契约) | "一个工具怎么被描述、被发现、被调用?" | MCP 函数调用/OpenAPI、gRPC、REST | 与编排范式正交,谁都能接 |
| 编排范式层 (谁决定调用顺序) | "任务步骤由图固定,还是由模型临时决定?" | 声明式节点图 · 过程式 Agent loop · 状态机 | 决定系统的可控性/动态性取舍 |
| 具体框架层 | "用哪个产品把范式落地?" | ComfyUI / n8n / Dify(A 型)· Hermes / LangGraph / AutoGPT / Claude Code(B 型) | 换框架通常不换范式 |
一句话记住:「协议」管工具长什么样,「范式」管谁来决定下一步,「框架」管你实际装的是哪个软件。三者不同层,别拿不同层的东西 PK。本节后面说的"两种范式",专指中间那一层,与 MCP 不在同一维度。
2. 范式 A · 声明式节点图(ComfyUI 型)
代表:ComfyUI n8n Node-RED Dify 工作流。心智模型一句话:节点 = 组件,连线 = 数据契约,整张图 = 一段程序。
在 pr02 里我们已经拆过 ComfyUI 的节点流水线——它本质是"声明式软件流水线":你先把执行拓扑写死,运行时只是把数据从上游端口灌进下游端口。它的性质因此非常"传统软件":
- 静态可推演:不用真跑,光看图就能算出数据流、分支、汇聚点,成本/延迟可静态估算。
- 可版本化 / 可 diff:图就是一段 JSON,Git 里能逐行 diff,"这次改了哪个节点的哪个参数"一目了然。
- 失败精确归因:某条边断了、某个节点抛异常,错误直接钉在那一个节点上(§9 详述)。
- 缺点:动态性差:分支一多、组合一爆炸,图会变得难以维护;无法在运行时"灵机一动"改路线。
3. 范式 B · 过程式 Agent 自主编排(Hermes 型)
代表:Hermes LangGraph 状态机 AutoGPT Claude Code。心智模型:把 LLM 放进一个 while 循环——每一轮都把"当前上下文 + 可用工具 schema"喂给模型,模型输出"要么调某工具(带参数)、要么给出最终答案",执行工具后把观察结果再喂回,直到收敛或触发 max_steps/预算护栏。核心是"下一步的决策权在运行期交给了模型"。
- 动态性强:能处理"事先没画全"的开放任务,工具选择/参数都是临时定的。
- 缺点:可控性弱——同一个输入,两次跑可能走不同路径。
- 可观测性依赖 trace 中间件:失败不发生在"某个节点",而是发生在"第 7 步模型做了一个不太对的选择",得靠逐轮 trace 还原(§9,pr03 已建
traces.csvschema)。 - 成本与延迟方差大:步数不固定,token 消耗和墙钟时间都是随机变量,难以静态预算。
4. 共用工具层:MCP
MCP 是一套 client-server 协议:编排器(无论 A 还是 B)作为 client,通过 MCP 连接到若干 server,server 对外暴露工具。约定很朴素:
- 一个工具 = 一份 schema + 一个执行体:schema 描述"叫什么/收什么参数/返回什么",让 client 不看实现即可发现并调用;执行体是 server 侧真正干活的函数。
- schema 让"工具描述"与"谁调用它"解耦——所以同一次工具定义,既能被节点图的一个节点调用,也能被 Agent loop 里的一次 decision 调用。
下面给一个最小 MCP server:暴露一个"查询服务器显存"的工具 query_vram。两种范式接入的都是它。
# server.py —— 最小 MCP server(伪 API,真实签名以官方 SDK 为准)
from mcp.server import Server, Tool # 库名/接口以官方为准
import subprocess
app = Server("vram-tool") # 一个工具型 MCP server
# 1) 工具描述(schema):给 client 看的接口契约,不含实现
@app.list_tools
async def list_tools:
return [
Tool(
name="query_vram",
description="查询指定 GPU 当前已用/总显存 (MiB)",
inputSchema={
"type": "object",
"properties": {
"gpu_index": {"type": "integer", "default": 0}
},
"required": []
},
)
]
# 2) 工具执行体:client 决定何时调用;server 只负责按参数返回结果
@app.call_tool
async def call_tool(name: str, args: dict):
if name != "query_vram":
raise ValueError(f"unknown tool: {name}")
idx = args.get("gpu_index", 0)
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=memory.used,memory.total",
"--format=csv,noheader,nounits", "-i", str(idx)]
).decode.strip
used, total = [int(x) for x in out.split(",")]
return {"gpu_index": idx, "used_mib": used, "total_mib": total,
"free_mib": total - used}
if __name__ == "__main__":
app.run_stdio # stdio 传输;SSE/streamable-http 亦可
同一个
query_vram,两种接法:在范式 A 里它是图上一个 Custom Node,端口类型写死、上游是谁一目了然;在范式 B 里它出现在tools_schema列表里,由 LLM 在某一步"决定要不要查显存、查第几号卡"。工具没变、契约没变,变的只是"谁决定调用它"——这就是"MCP 被共用"的具体含义。
5. 同一任务,两种实现的对照
把抽象落到一个具体任务上。任务定义:"把用户提供的图片打标签并生成一段中文标题。"我们对同一批 20 张图,分别用两种范式实现,再横向比较。
5.1 实现 A · ComfyUI 节点工作流
固定拓扑:Load Image → Vision Model(打标)→ Caption Prompt → LLM 生成中文标题 → Save。下面是 Save (API Format) 风格的骨架 JSON(节点 class_type 以你实际导入的工作流为准):
{
"1": {
"class_type": "LoadImage",
"inputs": { "image": "input/photo_01.jpg" }
},
"2": {
"class_type": "VisionClassifier",
"inputs": {
"image": ["1", 0],
"model": "siglip-large",
"top_k": 8
}
},
"3": {
"class_type": "StringConcatenate",
"inputs": {
"template": "标签: {tags}。请用不超过 20 字为这张图写一个中文标题。",
"values": { "tags": ["2", 0] }
}
},
"4": {
"class_type": "QueryLLM",
"inputs": {
"prompt": ["3", 0],
"model": "qwen2.5-7b",
"temperature": 0.3,
"max_tokens": 64
}
},
"5": {
"class_type": "SaveText",
"inputs": { "text": ["4", 0], "path": "out/title_01.txt" }
}
}
决策全在设计期定死:top_k=8、temperature=0.3、标题 ≤20 字都是图上的常量;换图只改 LoadImage.image,拓扑不动。
5.2 实现 B · Hermes Agent 提示词 + 3 工具
把同样的能力拆成 3 个工具(其中 vlm_tag 可以就是一个 MCP server),交给 Agent 自己组织:
name: img-title-agent
model: qwen2.5-7b
max_steps: 6 # 预算护栏,防止 loop 漂移
system_prompt: |
你的任务:给定一张用户图片,先读出它的内容标签,
再据此写一段不超过 20 字的中文标题。
规则:
1) 先调用 load_image 拿到图片;若失败最多重试 1 次。
2) 用 vlm_tag 获取候选标签,若标签明显互相矛盾,
可再调一次 vlm_tag 换 top_k。
3) 结合标签调用 write_title 生成标题,要求自然、不堆词。
4) 只有当标题可读时才输出最终答案,否则说明失败原因。
tools:
- name: load_image
description: "读取本地/URL 图片,返回可被视觉模型消费的句柄"
source: builtin
- name: vlm_tag
description: "对图片打标签,参数 image_handle、top_k"
source: mcp://vlm-server # ← 与实现 A 用的是同一份视觉能力
- name: write_title
description: "根据标签+风格生成中文标题"
source: mcp://llm-writer
工具 vlm_tag、write_title 各自带 inputSchema(image_handle/top_k、tags/max_chars),与实现 A 用的是同一份视觉与文本能力。这里"先做什么、标签矛盾要不要重打、什么时候算完成"全是运行期由模型决定——同一张图两次跑,步数可能不一样。
5.3 一批 20 张图的横向对照(示意)
下表是这一对照实验的预期趋势示意(非某次实测真值,仅建立直觉):
| 观测项 | 实现 A(节点图) | 实现 B(Agent) |
|---|---|---|
| 成功率(流程完整跑通) | 高且稳定(≈拓扑固定,20 张走同一条路) | 略低且波动(个别图 loop 早停或超 max_steps) |
| 平均步数 | 恒定 = 图中节点数(此处 5) | 随机变量(示例:3–6 步,看模型是否重打标签) |
| 可观测性 | 天然:每节点输入输出可截图/落盘 | 需 trace 中间件逐轮记录才看得清 |
| 出错定位难度 | 低:错误钉在具体节点(如 class_type 不匹配) | 高:要回放"第几步模型选错工具/参数" |
| 对"开放图"的适应性 | 弱:标签异常时只会照流程硬跑 | 强:能自主重打/调整再决定 |
| 成本方差 | 低(可静态估算 token 与时间) | 高(步数不定 → 延迟/费用波动) |
注意:这里
vlm_tag与实现 A 的VisionClassifier用的是同一份视觉能力(通过 MCP 暴露)。两者的差异不来自工具,只来自编排器决定调用的方式。这正是 §1 那个区分的实证回收。
6. 四维对比表(表格 2)
把两种范式放到同一张评分表上,逐维给"A 优 / B 优 / 平"与一句解释。不给"谁绝对更好"的判断——取舍取决于任务性质(§7)。
| 维度 | 评级 | 解释 |
|---|---|---|
| 开发成本 (简单固定流程) | A 优 | 拖几个节点连线即成,不用写 loop 与护栏;但复杂分支下 A 反而更贵。 |
| 可控性 / 可预测性 | A 优 | 拓扑写死,行为可静态推演;B 每次可能不同路。 |
| 成功率 · 简单任务 | A 优 | 路径固定,几乎不偏离;B 的多余决策反而引入不确定。 |
| 成功率 · 复杂/开放任务 | B 优 | 需要临场选工具/重试/改策略时,B 的动态性救场;A 的图会组合爆炸。 |
| 可观测性 | A 优 | 节点即观测点;B 必须额外挂 trace 中间件才等价。 |
| 复用性 | 平 | 都靠工具层复用;共用 MCP 时,两边复用的是同一批工具。 |
| 出错定位 | A 优 | A 错在节点、可精确归因;B 错在 loop 状态漂移,需回放 trace。 |
| 成本 / 延迟预算 | A 优 | A 可静态估算(步数恒定);B 步数是随机变量,方差大。 |
| 动态适应 / 自进化空间 | B 优 | B 可接记忆、反思、工具自选择;A 要把新情形画进图才行。 |
读表要点:A 在"确定性相关"维度(可控/可观测/定位/预算)几乎全线占优;B 只在"开放性相关"维度(复杂任务成功率、动态适应)占优;复用性因共享 MCP 而打平——这就是选型的底层结构。
7. 选型决策树
把 §6 的取舍变成可执行的问题串。按顺序问自己,命中即停:
- 任务是否是固定流程? → 是 → 范式 A(流程已知,没必要让模型现编)。
- 是否需要在运行时动态选择工具/参数? → 是 → 范式 B(决策权该交给模型)。
- 是否需要人机协作回路(中途介入/审批)? → 是 → B 更适合在 loop 里插一轮人工确认。
- 是否需要精确的成本与延迟预算? → 是 → A(步数恒定才能静态估算 SLA)。
- 是否需要长上下文记忆 / 自进化 / 反思? → 是 → B。
- 以上都不明显、任务既有稳定骨干又有偶发开放分支 → 看 §8 混合模式。
一个经验法则:"重复一千次都不变"→ A;"每一次都可能要临场应变"→ B。拿不准时,先问"能不能提前把图全画出来"——画得出来就别上 Agent,省下的都是方差。
8. 混合模式:外层 Agent + 内层节点图(推荐主线)
生产系统里最常见、也是本节推荐的主线:让 Agent 处理开放任务与用户交互,一旦命中"稳定高频的子流程",就把它委托给一段 ComfyUI/n8n 工作流跑。这不是"选边站",而是把 §6 两张优点分层组合——外层享受 B 的动态性与协作回路,内层享受 A 的确定性与可观测性,两者用 MCP 共享工具、避免重复实现。"稳定高频子流程"最好固化、交给节点图收益最大;"开放交互与临场决策"留给 Agent。
两种典型触发方式:
- workflow-as-a-tool(正向):把一整段节点图打包成 MCP 里的一个工具,Agent 在需要时"调用一次工作流",返回一个结果。对 Agent 而言它就是个黑盒工具。
- workflow 内抛回 Agent(反向):稳定流程里遇到事先无法确定的分支,工作流用一个"handoff 节点"把控制权连同上下文交回外层 Agent,由 Agent 决策后再决定续跑还是换路。
9. 可观测性与失败定位对比
两种范式的"失败"长得不一样,定位方式因此不同:
- 节点图(A):失败 = 某个节点抛异常/某条边类型不匹配,错误直接归属到那一个节点 id。回放即定位。ComfyUI 的
/prompt校验失败会返回具体节点错误(见 pr02 §6.2)。 - Agent(B):失败 = loop 状态漂移——没有哪个节点"崩了",而是模型在某一步选错了工具、填错了参数,或陷入反复自我确认。必须有逐轮 trace 中间件才看得见。pr03 已建
traces.csvschema,pr09 提供失败编码。
一段示意性的 Agent 失败 trace(对应 §5 实现 B 的某个坏 case):
step,role,tool,args_summary,observation,latency_ms,tokens,fail_code
1,assistant,vlm_tag,"handle=img_07,top_k=8","[cat,sofa,indoor,...]",210,340,
2,tool,-,-,"ok",-, -,
3,assistant,vlm_tag,"handle=img_07,top_k=16","标签矛盾:sofa vs bed",190,360,
4,assistant,write_title,"tags=[...],max=20","标题含矛盾词'卧室沙发'",120,80,F2
5,assistant,write_title,"重试,同参数","同上",118,82,F2
6,-,-,-,"超步数/预算耗尽,loop 未收敛",-, -,F5
定位方法:用
fail_code(pr09 编码)定位到第 3–5 步——根因是"模型在标签矛盾时反复用同一策略重试",而非任何工具报错。这类失败在范式 A 里不会发生(矛盾只会被硬塞进write_title)。Agent 的灵活正是它难排障的来源,所以 pr03 的 trace schema 不是可选项。
10. 与本系列其它节的接口
本节不是孤岛,它和 pr 系列其它讲义是"正交 + 复用"关系:
- pr03(Agent trace schema):§9 的失败定位直接依赖 pr03 的
traces.csv字段——pr16 讲"为什么需要它",pr03 给"它长什么样"。 - pr11 / pr12(量化 / 提速):讲模型侧工程,与编排范式正交——两种范式底下都能接同一份被量化/加速过的模型。
- pr02(ComfyUI 案例):是 §2 / §5.1「范式 A」的落地样本;本节把它从"一个工具"上升为与 Agent 对等的一种编排范式来比较。
- pr17(SWE-bench 最小复现):属典型范式 B——Agent 在代码库上自主探索、逐步决定编辑/测试/提交,可用来检验 §3 与 §9 的 loop/trace 心智模型。
诚实标注:文中 ComfyUI 的
class_type、Hermes/Agent 配置字段、以及 MCP SDK 的Server/ToolAPI 均为示意最小写法,真实接口、方法与版本以各项目官方文档为准。§5.3 与 §6 的评级是结构性趋势判断,非某次实测真值,也不构成"A 优于 B"的绝对结论——选型永远回到 §7 的任务性质。
一页纸总结:MCP 是工具协议,不是编排范式;节点式(设计期写死拓扑)与 Agent 自主(运行期交给模型 loop)才是两种对等的编排范式,二者共享 MCP 工具层。确定性维度(A 优:可控/可观测/定位/预算)与开放维度(B 优:动态/协作/自进化)互补,生产主线是"外层 Agent + 内层节点图 + 中间 MCP"的混合模式;失败定位上 A 归因到节点、B 归因到 trace 里的某一步决策。