多模型编排的三层:框架、模型路由与提供商路由
原文:OpenRouter Blog – 《LangChain vs CrewAI: Orchestration Compared to OpenRouter-Native Routing》(https://openrouter.ai/blog/insights/langchain-vs-crewai-orchestration-compared-to-openrouter-native-routing/)
做 Agent 应用时,「多模型」很容易被当成一件事:接几个模型,写个 if-else 挑一个。真到要落地才发现这里混着三件不同的事——谁负责拆任务、谁负责在模型之间切换、谁负责给同一个模型挑服务端点。三层揉成一层,代码会越写越难改;分开看,很多「我是不是得上 LangGraph」的纠结能自己回答。下面按 OpenRouter 10 月 2 日那篇对比文的结构把三层摊开,再给出可以直接跑的最小代码。
一、先分清三层职责
| 工作流编排 | 任务怎么拆、状态放哪、哪一步要人看、子任务交给谁 | LangGraph、CrewAI |
| 模型路由 | 这一次调用打哪个模型,它报错之后怎么办 | 请求里的 models 参数 |
| 提供商路由 | 已经选定的模型,由哪个端点来服务 | OpenRouter 自动完成 |
三层是三种不同的问题,混在一个「模型选择器」里写,后面任何一层要改都得动同一坨代码。分开之后,三层可以各自替换。
二、工作流编排层:LangGraph 和 CrewAI 给了什么
这一层解决的是「长任务怎么活下去」,两个主流框架走的是两条路。
2.1 LangGraph:把工作流画成图
LangGraph 把工作流建模成一张节点图,确定性的人写步骤和模型驱动的步骤可以混在同一张图里。它靠两个组件解决持久化:
- Checkpointer:按 thread 保存图的状态,中断后可以从断点继续;
- Store:把应用数据存在图状态之外,跨 thread 复用状态;
- interrupt():在图里任意位置暂停,等人审批后继续。
它的取舍很清楚:控制权完全在你手里,代价是节点、边、状态 schema、持久化配置都得自己写。
2.2 CrewAI:用角色和任务描述替代图
CrewAI 的心智模型更接近「写一张任务单」,而不是「画一张图」:
- Crew:一组 Agent,每个有 role、goal、backstory,按分配的任务推进;流程可以是 sequential(顺序),也可以是 hierarchical(带管理者);
- allow_delegation 打开后 Agent 之间可以互相委派;每个 Agent 有 max_iter 上限(默认 20),还可以设 max_execution_time;
- Flow:包在 Crew 外面的结构化、事件驱动层。官方把 Flow 定位成应用的骨架,Crew 是里面的工作单元。
你花的力气更多在 role / goal / task 这三段文字上,更少在连节点上。代价是把更多执行路径的决定权交给了 Agent 自己。
三、模型路由层:一个列表就能覆盖大部分需求
这一层不需要框架。直接请求 chat/completions 端点,把候选模型按优先级写进 models 参数,剩下的交给服务端:第一个模型报错就试下一个。默认情况下任何错误都能触发回退,包括上下文长度校验失败、被过滤模型的审核拦截、限流、服务不可用。计费按最终提供服务的那个模型算,响应里的 model 字段告诉你是哪一个。
fallback 只对错误生效,它不判断第一个模型的答案好不好。 这句话是整个话题里最容易误会的地方:想要「答案质量不达标就升级」,那是工作流编排层的活,得自己在代码里判。
最小可跑的例子(官方原文示例):
# 直接请求 OpenRouter,用 models 列表做优先级回退
import os
import requests
def route(models: list[str], prompt: str) –> tuple[str, str]:
response = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
# models 是有序回退列表:第一个报错就用下一个
json={"models": models, "messages": [{"role": "user", "content": prompt}]},
timeout=120,
)
response.raise_for_status()
body = response.json()
# 返回值里带上是哪个模型真正服务的,方便埋点
return body["choices"][0]["message"]["content"], body["model"]
draft, draft_model = route(
["anthropic/claude-sonnet-5", "openai/gpt-5.6-sol"],
"Draft a one-paragraph summary of what a model fallback list does.",
)
review, review_model = route(
["openai/gpt-5.6-sol", "anthropic/claude-sonnet-5"],
f"Review this draft for accuracy and suggest one improvement:\\n\\n{draft}",
)
print(f"draft by {draft_model}, review by {review_model}")
print(review)
这段代码里三个参数值得记一下:models 是有序列表,越靠前越优先;timeout 给足(示例取 120 秒),因为回退意味着可能真的跑完一次才失败;返回值直接取 model 字段。拿到它以后建议立刻打点——回退率、回退最终命中哪个模型,是判断这条链健不健康的第一手数据。
同样的两步流程换成 LangChain,写法是给每个模型对象挂 fallbacks:
# 同样的事情换成 LangChain:编排由框架管,模型层仍然走 OpenRouter
from langchain_openrouter import ChatOpenRouter
drafter = ChatOpenRouter(model="anthropic/claude-sonnet-5").with_fallbacks(
[ChatOpenRouter(model="openai/gpt-5.6-sol")]
)
reviewer = ChatOpenRouter(model="openai/gpt-5.6-sol").with_fallbacks(
[ChatOpenRouter(model="anthropic/claude-sonnet-5")]
)
draft = drafter.invoke("Draft a one-paragraph summary of what a model fallback list does.")
review = reviewer.invoke(
f"Review this draft for accuracy and suggest one improvement:\\n\\n{draft.content}"
)
print(review.content)
对比两段代码能看出分工:一个是你自己写调用顺序,一个是框架替你管流程。模型层的部分(用哪些模型、出错怎么退)两边完全一样。
四、提供商路由层:同一个模型,不同端点
一个模型在 OpenRouter 上往往有多个提供商在服务。提供商路由换的是端点,不是模型:你请求的是同一个模型,系统在符合条件的提供商里挑一个。当请求里带工具时,Auto Exacto 默认生效,按吞吐、工具调用成功率、基准数据重排提供商顺序。
这一层平时不需要你操心,但对 Agent 场景有两层含义:工具调用密集的链路,端点质量会直接影响成功率;而这类重排发生在「你已经选定的模型」内部,不会悄悄换成另一个模型。
五、中间那一段:带边界的工具循环
很多 Agent 既不需要一张持久化的图,也不需要一支带角色的团队,只需要一个「有上限的多轮工具循环」。OpenRouter 的 Agent SDK 就是冲这一段来的(示例为官方 TypeScript 代码):
// 有界的多轮工具循环:停止条件写在调用里
import { OpenRouter, tool, stepCountIs, maxCost } from "@openrouter/agent";
import { z } from "zod";
const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
const result = client.callModel({
model: "anthropic/claude-sonnet-5",
input: "What time is it in Tokyo?",
tools: [
tool({
name: "get_time",
description: "Get the current time in a timezone",
inputSchema: z.object({ timezone: z.string() }),
execute: async ({ timezone }) => ({
time: new Date().toLocaleString("en-US", { timeZone: timezone }),
}),
}),
],
// 什么时候停:步数上限或成本上限,先到先停
stopWhen: [stepCountIs(5), maxCost(0.5)],
});
const text = await result.getText();
console.log(text);
stepCountIs(5) 和 maxCost(0.5) 是停止条件,不是消费上限——原文特意点明了这一点:达到条件循环就停,别把它当成账单封顶。该 SDK 里还有一个 openrouter:subagent 能力,标注为 beta。
六、对照表与选型建议
| 为谁而生 | 显式状态、持久化、人工审批的图式编排 | 事件驱动 Flow 里的角色制 Agent 团队 | 每次调用选模型、错误驱动回退、提供商路由 |
| 多模型支持 | 每个节点或 Agent 一个模型对象 | 每个 Agent、Crew 或 manager 一个 LLM | 每个请求一个 models 列表 |
| 规划、记忆、委派 | 有,写在图、Checkpointer、Store 里 | 有,通过 Agent、流程与 Flow 状态 | 没有,只有路由 |
| 流式输出 | 有 | 有,Crew 级别 | 有,按请求 |
| 人工介入 | interrupt + Checkpointer | 自己写 Flow 逻辑 | 不提供 |
| 你要写什么 | 节点、边、状态 schema、持久化配置 | Agent、Task、Crew、Flow 定义 | 一个请求体 |
选型上原文的建议很实际:先做小承诺。 想不清楚需不需要框架,就先用 models 列表把一个两步流程跨两个模型跑通,再判断是不是真需要上面那层。两层不互相替代,也不需要为了拿到下面两层而先引入框架——一个列表加几个 if 就能在多模型间路由。
反过来,如果你已经在用 LangChain 或 CrewAI,也没必要换掉:LangChain 有专门的 ChatOpenRouter 集成,CrewAI 通过 LLM 类把 OpenRouter 当 provider。保留框架的编排,把模型与提供商路由放在下面一层,是更常见的组合。
七、给 Agent 开发学习者的启示
真正的分界线不是「用不用框架」,而是「状态要不要跨轮、跨运行活下去」。 需要人工审批、需要断点续跑、需要把子任务稳定地派给不同 Agent,就上编排层;只要一次请求内把活干完,一个 models 列表往往就够了,多引入一层只是多一层要维护的东西。
另外两条可以直接拿来用的经验:回退策略要按错误类型梳理一遍,别默认「所有错误都该回退」——比如内容审核拦截,回退到另一个模型大概率还是被拦,白白多花一次调用;工具调用链路的成功率要跟端点质量一起看,工具密集时这类差异会被放大。
小结
多模型编排是三层:工作流编排负责规划、状态、记忆与委派,LangGraph 用图换来显式控制,CrewAI 用角色与 Flow 换来更少的连边工作;模型路由负责选模型和在报错时回退,一个 models 列表即可;提供商路由负责在同一个模型的多个端点间挑选,并在带工具时优先工具调用质量。三层可以拆开用,也可以叠起来用——先判断题目的边界,再决定要不要框架。
文中模型名与代码来自 OpenRouter 原文示例,框架与 SDK 的具体版本请以官方文档为准,此处未验证最新版本。
网硕互联帮助中心



评论前必须登录!
注册