谁应该拥有代理循环:运行时工具与组合框架
构建人工智能智能体的决策框架取决于一个核心问题:谁来掌控运行循环?同时还需比较Node harness与LangGraph、CrewAI及LangChain的表现。
来源: Who Owns the Agent Loop? Node.js Harnesses vs. Python Frameworks (LangGraph, CrewAI, LangChain)
从远处看,构建人工智能智能体的工具似乎可以互相替代。每种工具都会提供一个模型、一种调用工具的方式,以及一些让模型持续运行直至任务完成的机制,使用其中任何一种工具,你都可以在一个下午内让智能体开始运行。这些工具之间的差异在演示阶段是隐藏的,直到进入实际应用后才会显现出根本性的区别:谁拥有驱动智能体的循环控制权?有一类工具会直接提供运行环境并退居幕后;而另一类则提供抽象接口,要求你自行组合使用。本文明确划分了这两种理念的界限,并为你提供一种方法来判断特定项目应属于哪一方。其目的并非选出胜出者,而是让各种权衡变得清晰可见。
一类是托管式框架:它们掌控着完整的发送提示、运行工具、结果反馈以及循环执行流程。你无需自行编写该流程,只需对其进行配置和扩展即可。另一类则是 LangGraph、CrewAI 和 LangChain 等框架,它们提供明确的构建模块、图结构、任务组及流程链,但需要你自行连接控制流。虽然这常被描述为 Node 与 Python 的对比,但语言其实只是次要因素;真正关键的是你想掌控多少底层机制。
两种能在压力下依然成立的定义
在依赖这些概念之前,先明确这两个词的含义。所谓“框架”,其实就是一种负责管理智能体循环的运行时环境。它会代你完成整个循环流程:发送提示词、收集模型所需的工具调用信息、执行这些操作、返回结果,如此反复直到模型不再提出任何请求。此外,它还通常会处理各种复杂的难题,包括上下文与内存管理、随着令牌预算耗尽而对历史记录的压缩处理、重试机制以及会话持久化等问题。你可以通过配置来引导它,比如系统提示词、模型选择、允许使用的工具以及各种限制参数,还可以通过钩子与扩展点来实现控制,而无需手动编写循环代码。在这个领域中有一个名为Pi的框架,它是一种基于Node和TypeScript的运行时环境,其设计理念是注重基础元素而非功能特性:你需要让运行时适应你的工作流程,而不是强行调整自己的工作流程来适配它。
框架或SDK实际上是一组可在代码中组合使用的明确抽象概念。LangGraph提供由边连接的节点图结构;CrewAI则提供角色扮演型智能体团队;LangChain则提供链式结构。在所有这些工具中,控制流的构建由你负责,而库仅提供相关组件并帮助你将它们连接起来。真正重要的区别在于:在工具集中,循环逻辑是隐藏的,属于运行时系统;而在框架中,循环结构、图形态或团队配置都是公开可见的,由你自行掌控。
这更像是一个连续谱而非非此即彼的选择。在一种极端情况下,你通过配置和钩子来引导运行时系统,循环逻辑由它负责;而在另一种极端情况下,你需要亲自设计每一个节点、边、团队或链式结构,循环逻辑实际上就是你自己的代码。
HARNESS you → [config + hooks] → ╔═══ runtime owns the loop ═══╗ → result
║ prompt → tools → results → ↺║
FRAMEWORK you author every node / edge / crew / chain → the loop IS your code
请牢记那个坐标轴,因为下面的所有内容都取决于工具在轴上的位置。
Harness方法:将循环交给运行时处理
Pi是一种极简的、基于Node的原生Harness,采用分层堆栈结构,你可以仅使用所需的层。最底层是涵盖多种模型提供者的抽象层;中间层负责代理循环与工具调度;其上则是具备会话持久化功能的完整运行时环境;最表层则是终端用户界面。对大多数工程师而言,中间层的循环机制最为有趣。启动一个代理意味着先描述它的功能,然后将控制权交予它。基于会话API构建的一个小型研究代理如下所示。
import { getModel } from "@mariozechner/pi-ai";
import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
const { session } = await createAgentSession({
model: getModel("anthropic", "claude-sonnet-4-6"),
cwd: process.cwd(),
sessionManager: SessionManager.open("./session.jsonl"),
tools: ["read", "grep", "bash", "search_docs"], // built-ins + your custom tools, by name
customTools: [searchDocsTool], // your typed tools
});
// You steer the runtime with config, not loop code:
session.agent.state.systemPrompt = "You are a research assistant. Always cite sources.";
// A single call runs the entire prompt → tools → results → repeat cycle:
await session.prompt("Summarize the latest on agent frameworks and cite sources.");
注意缺少了什么。没有while循环,也没有“如果模型返回了工具调用,则执行这些调用并再次调用模型”的分支逻辑,更没有用于判断何时应压缩历史记录的令牌计数机制。所有这些工作都由运行时处理。你所提供的仅有一个模型、它可能使用的工具以及一个系统提示语,随后还有一次对提示方法的调用。自定义工具同样也是一个结构简单的对象:包含名称、面向模型的描述、参数的结构定义以及一个执行函数。
const searchDocsTool = {
name: "search_docs",
description: "Search the internal docs and return matching passages.",
parameters: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
async execute(_id: string, params: { query: string }, signal?: AbortSignal) {
const passages = await search(params.query, signal);
return { content: [{ type: "text", text: passages }], details: {} };
},
};
目前来看,这似乎只是提供了一些便利功能而已。一旦需要改变循环的行为方式,问题就会显现出来,因为你无法重写不属于自己的循环。相反,你必须通过事件和钩子来操作它。订阅事件可以让你观察循环的运行过程并适时干预,例如通过计数轮次,在达到上限时终止循环。
let turns = 0;
const MAX_TURNS = 20;
const unsubscribe = session.agent.subscribe((event) => {
if (event.type === "turn_end" && ++turns >= MAX_TURNS) {
session.agent.abort(); // pull the cord on a loop you didn't write
}
});
同样的机制也可用于设置成本上限:随着消息的到达不断累计每条消息的成本,一旦超过阈值则终止循环。运行时持续驱动循环,而你则负责观察并决定何时停止它。钩子功能更为强大,允许你在决策生效之前进行拦截,且模型不会察觉到这一操作。在工具调用之前的钩子可以检查待处理的调用并予以拒绝。
session.agent.beforeToolCall = async (ctx) => {
if (ctx.toolCall.name === "bash" && isDangerous(ctx.args.command)) {
return { block: true, reason: "blocked by policy" };
}
return undefined; // allow
};
该模型“请求”执行某个shell命令;钩子函数默默拒绝并返回了原因,而循环则仿佛什么都没发生一样继续运行。你实际上是根据一个并非自己编写的循环来调整智能体的行为。这就是框架所提供的交易——你放弃重新编写控制流的能力,作为交换,则能快速得到一个可用的智能体,以及在上下文管理、数据压缩和持久化等真正棘手的方面具备强大的默认设置,同时还有一套完善的接口、配置选项、事件处理机制和钩子函数,用于调整智能体的行为。当运行时的设计理念与你的想法一致时,这确实是一笔极佳的交易。
框架式方法:自行组合控制流
Python生态系统则选择了相反的策略。它并不提供一个掌控循环的运行时环境,而是提供一些供你自行组合的抽象组件,其中有三类尤为重要。
LangChain:构建链式结构
在三者之中,LangChain是最古老且功能最全面的。它最初只是用于将多个大语言模型调用串联起来的工具,后来发展成为一个功能极为丰富的平台:拥有上千种集成选项,其接口无需关心背后使用的是哪种模型,同时还配有用于监控功能的配套产品。对于智能体而言,它提供了类似ReAct风格的工具调用机制,其组合式架构可将一个组件与另一个组件依次连接起来。它的最大优势在于覆盖范围之广:只要存在某种模型、向量存储或外部工具,很可能就已有对应的LangChain集成。但其缺点正是这种通用性带来的代价——过高的抽象层次会让调试工作变得极为复杂,而且由于它默认是无状态的,因此持久性需要额外实现而非与生俱来。LangChain更适合那些标准、大致呈线性结构的单智能体工作流,尤其是在集成范围是核心需求的情况下。
LangGraph:状态机的建模
LangGraph提出了一个明确的观点:智能体系统即状态机,且应当对每一次状态转换进行控制。该框架将智能体建模为图结构,节点代表不同状态,边代表状态转换,所有操作都在带类型的状态对象上执行。
from langgraph.graph import StateGraph, MessagesState, START, END
def call_model(state: MessagesState):
return {"messages": [llm.invoke(state["messages"])]}
graph = StateGraph(MessagesState)
graph.add_node(call_model)
graph.add_edge(START, "call_model")
graph.add_edge("call_model", END)
app = graph.compile()
app.invoke({"messages": [{"role": "user", "content": "hi"}]})
当传统框架通过封装隐藏循环结构时,LangGraph则将其转化为由你直接编写的代码。条件分支也被明确地表述出来。
graph.add_conditional_edges("call_model", should_continue, {"tools": "tools", "end": END})
那个“继续或停止”函数正是框架在每次调用提示词后自动执行的分支逻辑。现在它属于你了:可见、可测试且可版本控制。多此一举的代价则是LangGraph的突出优势——即在每次状态转换时都能进行原生检查点保存。由于状态会随着图结构的推进而写入磁盘,因此程序能够在重启后继续运行、在崩溃后恢复,还能回放并查看之前的状态,同时还可以在运行过程中暂停等待用户操作。不过其缺点是学习曲线较陡,且设计上刻意不便于无服务器部署。LangGraph更适合需要长时间运行、循环处理、具备故障恢复能力且有状态的工作流。
CrewAI:组建团队
CrewAI认为,大多数多智能体问题都可以很好地分解为由各领域专家组成的团队。每个智能体都有其角色、目标以及背景故事,随后将它们整合成一个团队并分配任务给他们。
from crewai import Agent, Task, Crew
researcher = Agent(role="Researcher", goal="Find key insights on {topic}",
backstory="A meticulous analyst.")
writer = Agent(role="Writer", goal="Summarize the research clearly",
backstory="A concise technical writer.")
research = Task(description="Research {topic}.", expected_output="3 insights.", agent=researcher)
summary = Task(description="Write a summary.", expected_output="A short summary.", agent=writer)
Crew(agents=[researcher, writer], tasks=[research, summary]).kickoff(inputs={"topic": "agent frameworks"})
这种思维模型直观易用,构建可运行的多智能体流程的路径也很短,正因如此它深受企业青睐。但另一方面,角色与目标的抽象在一切正常时固然方便,一旦出现问题就会变成黑箱;智能体之间的任务分配可能陷入循环;而且还需要自行设置机制来防止成本失控。CrewAI适用于基于角色的多智能体系统及快速原型开发。将这三者联系在一起的要点在于:你需要自行编写控制流程。这既是成本——需要更多代码、更多决策、更复杂的维护工作——也是优势,因为你可以查看、测试并调整每一个流程转换。在数据与机器学习领域,Python的强大影响力为这一技术提供了巨大助力,因为智能体可以直接与你已使用的库和笔记本协同工作。
实际做出选择的考量因素
这些方法虽然并排存在,但实际应用中它们会在少数几个维度上产生分歧,其中几种方法承担了绝大部分工作量。
运行循环的所有权是核心维度,其他一切皆由此衍生。有些工具会将循环掌握在自己手中,而有些框架则将其交由用户控制——LangGraph代表完全由用户编写代码的端点,Pi代表运行时自动处理所有内容的端点,其余方法则介于两者之间。
Durability是LangGraph的核心优势。在每次状态转换时都会保存检查点,这使得系统几乎无需额外努力即可实现崩溃恢复和回放。Harness提供的会话持久化功能以及CrewAI提供的内存类型固然有用,但都无法替代可持久回放的状态。当进程在运行过程中被终止时,带有检查点的图结构可以从上一次转换处继续运行,而会话日志通常只能让人们在事后拼凑出当时发生的情况。
CrewAI通过角色机制来实现对多个智能体的协调,LangGraph则通过子图实现这一功能。Harness则依靠其他方式来实现相同目标,比如子智能体、扩展模块或用户自定义的协调逻辑。
周围的语言生态系统往往会默默决定一切。选择 Python 会将该工具引入数据与机器学习领域,使其与数据框、训练代码及笔记本并存。而选择 TypeScript 则会将其引向 Web 和边缘计算领域,使其与 API 接口、共享的前端类型以及服务器less函数的部署位置紧密关联。这种引力往往比对比清单上的任何单一特性都更为重要,因此诚实地了解团队当前所处的技术环境至关重要。
还有一个要点能确保比较的客观性:使用框架搭配 Node 的模式只是一种趋势,并非绝对规则。也有用 Python 编写的类似工具框架的运行时,以及用 TypeScript 编写的图式调度库。决策时应基于编程范式和实际约束,而非仅看软件包上的语言标识。
同一项任务,不同视角
想象一项普通的任务:一个智能体通过工具获取某些信息,然后生成对其的总结。每种范式首先让使用者面对的就是其中的关键部分。在框架之下,大部分工作都是配置工作——一旦定义了搜索工具和系统提示语,只需发起一次调用,运行时便会接管任务,调用模型,判断模型需要搜索,执行搜索操作,返回结果,让模型据此生成总结,最后自动终止。该框架实际上只问了你一个问题:这个智能体应该具备哪些能力?
在 LangGraph 这样的框架下,工作几乎完全围绕结构展开。你需要指定状态对象、调用模型的节点、执行工具的节点,以及用于决定是返回模型还是结束流程的条件边。在该图完全构建完成之前,根本不会有任何操作被执行。而 LangGraph 提出的则是另一个问题:控制流究竟是什么?
需要理解的关键点是:两种方式都没有难度,任务在两者中都十分简单。区分二者的并非所需付出的努力,而是你必须提前做出的决策,以及之后是否还能对某些内容进行查看或恢复。框架能让第一个问题消失,迅速为你提供一个结构清晰的智能体;而工具库则不会让这个问题消失,而是在系统在不当时刻出现故障时,让你能够逐个追踪流程变化。随着系统的规模扩大,这种差异带来的影响也会愈发显著。当你正在构建代码助手或嵌入在应用程序中的智能体时,能力问题才是关键,此时框架能带来极大便利;而当你正在构建需要长时间运行、能够承受崩溃并通过审计的工作流时,控制流程问题才是重点,此时显式图中的每一行代码都物有所值。
将工具适配到你必须拥有的环状物上
这些启发式原则直接源自上述所有内容。当优先考虑高效的工作代理、运行时内置选项已能满足需求、目标是为编码或开发者工具类代理,或是要将代理嵌入 TypeScript 应用并确保类型安全及与前端紧密集成时,应选择相应方案。当无法承受进度丢失的情况——比如那些需要持续运行或循环执行、必须抵御崩溃影响、需要对状态及每次转换进行精确控制,或是需要等待人工干预的工作流——则应选择 LangGraph。当问题确实可以分解为多个专业角色,且希望快速搭建多代理处理流程时,应选择 CrewAI。而在传统单代理、工具调用流程中,可用的集成选项数量远超其他考量因素时,尤其是在已有的 Python 代码库中,应选择 LangChain。再次强调……
充满情感地看,这些界限是模糊的:应确定范式与约束条件,而非决定罐子上印哪种语言。问题从一开始就是循环
这场争论从来都不是 Node 对抗 Python,或是某个框架与其竞争对手之间的较量。核心问题在于:运行时应当掌控代理循环,还是由开发者自己掌控?工具库放弃了重新编写流程的自由,以此换取快速构建可运行的代理,并通过配置、事件和钩子来调整行为,而这些功能都是应用于开发者并未编写的循环中的。框架则用速度换取明确的指令、稳定性以及编排能力,同时要求开发者持续承担编写和维护流程的成本。对大多数团队而言,合理的起步方式是使用工具库来开发原型、编写代理以及产品中嵌入的各类功能,因为此时速度才是最重要的;一旦对稳定状态有明确要求,则可转向 LangGraph;而当工作确实需要专业团队来完成时,再选择 CrewAI。由于这些工具不断吸收彼此的优秀理念,真正重要的能力并非精通某一种工具。
API,但关键在于观察眼前的智能体,从而判断谁应该拥有其循环控制权。