跳到主要内容

LangChain 还是 pi-agent?两套 Agent 框架的哲学差异(含同任务实测对照)

· 阅读需 8 分钟

「我该用 LangChain 还是自己写 / 换别的?」——这是开始做 Agent 时最常见的纠结。这篇把 LangChain(JS)pi-agent 摆在一起比:不是比谁的 API 漂亮,而是比它们对「Agent 应该由谁来管」这件事的答案

为了不空谈,我给同一类任务(一个会调工具的查天气 Agent)在两边各写了一份代码并真跑起来:pi 侧用内置的 fauxProvider(不需要 key),LangChain 侧用真实模型端点(DeepSeek 的 Anthropic 兼容接口)。文中的事件序列、消息序列、最终回答都是真实输出

版本:@earendil-works/pi-ai / pi-agent-core 0.85.1langchain 1.5.11 / @langchain/langgraph 1.4.15(2026-09 npm 实测)。本站已有两个系列:pi-agent 系列LangChain + LangGraph 系列

一、先给结论

LangChain / LangGraphpi(pi-ai + pi-agent-core)
一句话给你一整套「Agent 平台」:抽象、集成、可观测、生态给你一层薄而显式的「harness」:循环、事件、会话,别的你自己写
气质平台化、约定多、上手快极简、透明、可控
适合快速搭原型 / 接大量外部集成 / 团队协作与可观测想完全掌控循环 / 深度定制 / 嵌入自己的产品(如 InkOS)

一句话选型要生态和速度 → LangChain;要透明和掌控 → pi。

二、架构对照:抽象层数不一样

两边看起来都是「两层」,但厚薄完全不同

  • LangChain 的 core + LangGraph 里塞的是方法论级抽象(Runnable、Chain、StateGraph、Reducer、Checkpointer、回调体系…);
  • pi 的两层里,pi-ai 只做「统一各家 LLM 的接入」,pi-agent-core 只做「状态 + 工具循环 + 事件流」。它没有图、没有 reducer、没有 checkpointer 这些概念——因为它假设「循环之外的事你自己管」。

三、同任务对照:一个「查天气」Agent

pi 的写法(真实代码,35 行左右)

import { Type, createModels, fauxProvider, fauxAssistantMessage, fauxToolCall, fauxText } from '@earendil-works/pi-ai';
import { Agent } from '@earendil-works/pi-agent-core';

const faux = fauxProvider(); // ← 无需 key 的假模型
const models = createModels();
models.setProvider(faux.provider);

const agent = new Agent({
initialState: {
systemPrompt: '你是一个会调用工具的助手。',
model: faux.getModel(),
tools: [{
name: 'get_weather', label: '查天气', description: '查询某城市天气',
parameters: Type.Object({ city: Type.String() }), // ← TypeBox schema
execute: async (_id, { city }) => ({ content: [{ type: 'text', text: `${city}: 25°C 晴` }] }),
}],
},
streamFn: models.streamSimple.bind(models), // ← 只注入「怎么连模型」
});

agent.subscribe((e) => { /* 事件流:turn_start / message_end / tool_execution_* … */ });
await agent.prompt('杭州天气怎么样?');

真实运行输出(事件序列):

== turn_start ==
[message_end] user = [text]
[message_end] assistant = [toolCall] ← 模型提议调工具
[tool_execution_start] get_weather
[tool_execution_end] ok=true
[message_end] toolResult = [text]
== turn_end == toolResults=1
== turn_start == ← 自动进入下一轮
[message_end] assistant = [text] ← 读到工具结果后回答
== turn_end == toolResults=0
== agent_end == messages=4

LangChain 的写法(一行起 Agent)

import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';

const getWeather = tool(async ({ city }) => `${city}: 25°C 晴`, {
name: 'get_weather',
description: '查询某城市天气',
schema: z.object({ city: z.string() }), // ← zod schema
});

const agent = createReactAgent({ llm: model, tools: [getWeather] });
const res = await agent.invoke({ messages: [{ role: 'user', content: '查一下杭州的天气' }] });

真实运行输出(真实模型,两次工具调用):

▶ 工具被调用: get_weather(杭州) -> 25°C 晴
消息角色序列: HumanMessage -> AIMessage -> ToolMessage -> ToolMessage -> AIMessage
最终回答: "杭州:25°C 晴。"

(同一份 demo 里换成「查杭州和深圳人口」,模型会并行调两次工具,于是出现连续两个 ToolMessage。)

对照着看,差在哪

piLangChain
模型接入models.streamSimple(注入一个函数)构造 Chat 模型实例(ChatAnthropic / ChatOpenAI
工具 schemaTypeBox(可 JSON 序列化)zod(TS 优先)
循环Agent 内部跑,事件全暴露createReactAgent 内部跑,消息全保留
你观察到的事件序列(turn/message/tool_execution)消息序列(Human/AI/Tool)
无 key 开发内置 fauxProvider(一等公民)一般要真模型或自己搭 fake

四、五条本质差异

1. 循环的「可见度」不同

pi 把循环暴露成事件流turn_start / message_start / message_update / message_end / tool_execution_start|update|end / turn_end / agent_end。你订阅事件就能画进度条、做审计、接 UI——循环本身是可见的

LangChain 把循环收进「图」里:你能拿到状态快照、能流式拿更新(streamMode: 'updates' | 'values'),但中间过程是消息和状态,不是「事件」。

2. 状态的「归属」不同

  • pi:状态就是消息序列,加一层 AgentMessage → convertToLlm() 的转换管线(可以把「只有 UI 看得懂的消息」混在里面,发给模型前过滤掉);记忆靠会话sessionId,持久化有独立的 SQLite 后端包)。
  • LangChain:状态是你显式声明的图状态——Annotation.Root({...}) + reducer 决定「同一字段多次写入怎么合并」;记忆靠 checkpointer(按 thread_id 存)。更工程化,也更啰嗦(要理解 reducer 才能不出错)。

3. 工具与 schema 的取舍

  • pi 用 TypeBox:schema 本身就是 JSON Schema,可序列化、可跨进程传(这也是它在 MCP/分布式场景顺手的原因);
  • LangChain 生态用 zod:TS 推导最强、生态最大,但跨语言/进协议要转换MCP 那篇 讲过这个链路)。

4. 「模型调用的自由度」不同

LangChain 想帮你把模型调用的花样都覆盖:withStructuredOutput(结构化输出)、bindTools、流式、回调、缓存……我在实测里也踩到过它的边界:推理模型不支持强制 tool_choicewithStructuredOutput 直接 400(错误原文 Thinking mode does not support this tool_choice)。

pi 的选择是只给原语stream/streamSimplecomplete/completeSimplevalidateToolCall——能做多少取决于你写多少

5. 体量与依赖

  • pi 的 pi-agent-core 依赖只有几个:pi-aipi-telemetrychordtypeboxdiffignoreyaml(本机从 package.json 读的);
  • LangChain 的依赖树大得多(厂商 SDK、SDK 适配层、core、langgraph……),能力多,装的东西也多

五、各自的「隐藏优势」

LangChain 的优势在生态:文档加载器、向量库、检索器、LangSmith 可观测、海量集成——要快速拼一个 RAG/多工具应用,它省下的时间很实在。(前提是你能接受它的抽象;遇到边界时也要有能力往下钻。)

pi 的优势在透明:整个循环你能读懂、能改。这也是为什么 InkOS 这种生产 harness 会建在它上面——需要自己掌控「确认闸门、原子落盘、状态机」时,薄框架 + 自己写比「厚框架 + 绕开它」要顺。

还有一点:pi 的 Agent.subscribe 事件流 + streamProxy(给浏览器走后端代理)+ 低层 agentLoop,让它既能当「开箱 Agent」,也能拆开只用循环原语——分层的粒度是按「你要接管多少」设计的

六、选型清单

你的情况建议
快速验证想法、要接很多现成集成LangChain / LangGraph
要图结构、条件分支、人工介入、持久化状态LangGraph(它的强项)
要自己掌控循环/权限/落盘、做生产 harnesspi-agent-core
只要「多厂商统一接入 + 工具调用原语」pi-ai 单用也成立
完全不想用框架参考本站 frontend-agent,手写 while + 工具循环

混合用法也存在:用 LangGraph 管编排、用 pi-ai 当 LLM 接入层(或反过来只在某些节点用 LangChain 的集成)。但别为混而混——两套抽象叠在一起,调试成本会翻倍,除非确有一个「另一侧做不到」的刚需。

七、一句话收尾

LangChain 在回答「Agent 应该长什么样」,pi 在回答「Agent 循环最少要多小」。 前者给你一栋装好的房子,后者给你砖和图纸——选哪个,取决于你是想快点住进去,还是想自己决定户型

关联

参考

  • pi 仓库/包:github.com/earendil-works/pi(npm @earendil-works/pi-ai@earendil-works/pi-agent-core
  • LangChain JS 文档:js.langchain.com | LangGraph JS:langchain-ai.github.io/langgraphjs
  • 本文实测:pi 侧 fauxProvider 事件序列、LangChain 侧真实端点消息序列与工具调用日志,均为本机运行结果;版本如文首标注