LibreChat — 架构与原理
30 秒导读: LibreChat 是一个自托管的 AI 聊天平台——你把它部署在自己的服务器上,就得到一个类 ChatGPT 的界面,但背后能接 Anthropic、OpenAI、Google、Bedrock,以及任何 OpenAI 兼容的模型。它真正的工程量不在界面,而在中间那层收敛:几十家风格各异的模型 API,被抽象成同一个叫
endpoint的东西;每条聊天消息最终都走进一套 agent 集成层(工具调用、MCP、文件检索、会话记忆、流式输出),由一个外部的 langgraph 运行器@librechat/agents端到端跑完,再流式回传前端并落库。
1. 这是什么(零基础也能懂)
一句话定义: LibreChat 是一个开源、多用户、可自托管的 AI 对话应用,一个界面统一接入所有主流大模型,并叠加 agent / 工具 / 检索 / 记忆等能力。
解决什么问题、给谁用。 想象你是一个团队的技术负责人:
- 你不想让公司数据经过第三方 SaaS,想把聊天服务跑在自己机房。
- 你的人有的爱用 Claude、有的爱用 GPT、有的要接本地 Ollama——你不想为每家维护一套界面和权限。
- 你还想让模型能调工具、查内部文档、记住用户偏好,而不只是干聊。
LibreChat 就是干这个的:一套后端 + 一套前端,把上面全部收进一个可自托管、带多用户鉴权的系统。
它能做什么(功能一览):
| 能力 | 说明 |
|---|---|
| 多 provider 统一接入 | Anthropic / OpenAI / Azure / Google / Vertex / Bedrock + 任意 OpenAI 兼容 API |
| Agents | 无代码自定义助手,可挂工具、文件检索、代码执行、子 agent |
| 工具与 MCP | 内置工具 + Model Context Protocol 服务器,给模型装"手脚" |
| RAG 文件问答 | 上传文件、向量检索、把命中片段注入上下文 |
| 会话记忆 | 跨轮次抽取并复用用户事实 |
| 流式与可恢复流 | SSE 实时输出,断线自动重连续传(Redis 支撑横向扩展) |
| 多用户与权限 | OAuth2 / LDAP / 邮箱登录、角色/群组、额度与审计 |
用起来什么样。 前端本质是给后端发一个 POST。一条最小的聊天请求长这样(示意,非源码):
// POST /api/agents/chat/ —— 前端发起一次对话
{
"endpoint": "agents", // 走哪套接入:agents 是核心入口
"agent_id": "agent_xxx", // 用哪个 agent(带工具/指令/模型)
"conversationId": "conv_xxx",
"text": "帮我总结这份文档",
"files": [ /* 附件引用 */ ]
}
后端不会一次性返回一个大 JSON,而是开一条 SSE 流,把模型 token、工具调用步骤、用量统计一段段推回来。
一句话直觉。 把 LibreChat 想成机场的中央调度塔:几十家航空公司(provider)机型、跑道、通信协议各不相同;塔台(endpoint 抽象 + agent 层)用一套统一话术指挥所有飞机起降,旅客(用户)只跟塔台打交道,不需要知道每家航司的内部规矩。
2. 顶层全景(它大概怎么转)
怎么读下面这张图: 从上到下就是一条聊天消息的流向——前端发请求,经中间件闸门,进 AgentController,由 initializeClient 组装出 AgentClient,交给外部运行器 @librechat/agents 真正驱动模型与工具,结果沿 SSE 原路流回前端、同时落库 MongoDB。
┌────────────────────────────────────────────────────────┐
│ React 前端 (client/) —— 发 POST,收 SSE 流 │
└──────────────────────────┬─────────────────────────────┘
│ POST /api/agents/chat
▼
┌────────────────────────────────────────────────────────┐
│ ① 闸门中间件链 │
│ 鉴权 · 封禁 · PII过滤 · 内容审核 · 权限 · │
│ buildEndpointOption(挑 provider 并解析会话) │
└──────────────────────────┬─────────────────────────────┘
▼
┌────────────────────────────────────────────────────────┐
│ ② AgentController (request.js) —— 主线编排/中止/落库 │
│ └─ initializeClient() 组装 AgentClient │
└──────────────────────────┬─────────────────────────────┘
▼
┌────────────────────────────────────────────────────────┐
│ ③ AgentClient —— 组消息 · 记忆 · 工具集 · 预算 │
│ └─ createRun() ← @librechat/agents (外部 langgraph)│
│ 工具调用循环 · 子 agent · 流式事件 │
└──────────────┬───────────────────────────┬─────────────┘
│ SSE 事件回流 │ saveMessage
▼ ▼
前端逐段渲染 MongoDB (data-schemas)
各部件一句话职责:
| 部件 | 干什么 | 在哪(相对克隆根) |
|---|---|---|
| 前端 SPA | 发起对话、渲染流式结果 | client/src/ |
| 路由与闸门 | 挂载 /api/agents、跑鉴权/审核/权限中间件 | api/server/index.js:268、api/server/routes/agents/chat.js |
| Endpoint 抽象 | 按 endpoint 字段挑 provider 构建器、解析会话 | api/server/middleware/buildEndpointOption.js:21 |
| AgentController | 主线编排:开流、监听中止、结束落库 | api/server/controllers/agents/request.js |
| initializeClient | 加载 agent、工具、模型,组装出客户端 | api/server/services/Endpoints/agents/initialize.js |
| AgentClient | 组消息、注入记忆、跑 run、聚合用量 | api/server/controllers/agents/client.js:91 |
| @librechat/agents | 外部 langgraph 运行器,真正驱动"模型↔工具"循环 | npm 依赖(源码不在本仓) |
| 工具 / MCP | 加载内置工具与 MCP 服务器工具 | api/server/services/ToolService.js、packages/api/src/mcp/MCPManager.ts |
| 数据层 | Mongo 模型:会话、消息、agent、记忆等 | packages/data-schemas/src/models/ |
| 共享 SDK | 前后端共用的类型、端点、数据服务 | packages/data-provider/ |
主线走一遍(高层,不进代码):
- 前端 POST 一条消息到
/api/agents/chat(核心入口就是 agents 这套)。 - 一串中间件当闸门:登录了吗、被封了吗、内容合规吗、有权用这个 agent 吗;最后
buildEndpointOption根据endpoint字段决定"这条消息用哪家 provider、按什么参数解析"。 AgentController接手,调initializeClient把 agent 定义、工具、模型凭证组装成一个AgentClient,并开一条 SSE 流。AgentClient组好消息(拼历史、注入记忆、附上文件上下文与工具集),调用外部createRun把控制权交给@librechat/agents;后者跑"模型说话 → 要不要调工具 → 调完再喂回模型"的循环。- 循环里产生的每个事件(token、工具步骤、用量)沿 SSE 实时回流前端;结束时把最终消息
saveMessage落进 MongoDB。
关键认知:几乎所有对话都从
agents这个 endpoint 走。 即使你只想直连 OpenAI,LibreChat 也是用 agent 层去包裹 provider——所以"agent 集成层"不是可选功能,而是主干。
3. 阅读地图(建议顺序)
这套文档按由 浅入深排。想快速建立心智模型就读 §1–§2;想读懂某个子系统再下钻对应章节。
| 顺序 | 章节 | 一句话讲什么 | 谁该读 |
|---|---|---|---|
| 0 | 总览:这是什么 · 全景图 · 阅读地图 | 本页:是什么、怎么转、去哪读 | 所有人先读 |
| 1 | 主线:一条聊天消息的一生 | 从 POST 到 SSE 落库,端到端追一遍请求 | 想懂整体流程 |
| 2 | Endpoint 抽象:把几十家 provider 收敛成一套接口 | EModelEndpoint、buildOptions、provider 映射 | 想加/改 provider |
| 3 | Agent 编排与流式:AgentClient 深入 | chatCompletion、createRun、子 agent、用量统计 | 想懂 agent 内核 |
| 4 | 工具与 MCP:给模型装手脚 | 工具加载、MCP 连接管理、工具调用回调 | 想扩展工具 |
| 5 | 上下文工程:文件/RAG、会话记忆与 token 预算 | buildMessages、useMemory、文件注入与剪枝 | 想调上下文行为 |
| 6 | 支撑层:数据模型、共享 SDK 与前端骨架 | Mongo 模型、data-provider、React 前端 | 想懂持久化与前端 |
这个仓库长什么样(monorepo 布局):
LibreChat/
├── api/ 后端 Express 服务(遗留 JS,主入口)
│ ├── server/ 路由 · 中间件 · 控制器 · 服务
│ └── app/clients/ BaseClient 等基类
├── client/ 前端 React SPA
└── packages/ 新代码归口(TypeScript)
├── api/ 后端新逻辑(endpoints / mcp / tools / memory …)
├── data-provider/ 前后端共享:类型 · 端点 · 数据服务
├── data-schemas/ Mongo 模型与 schema
└── client/ 前端共享工具
一个约定值得记住:老代码在 api/(JS),新逻辑一律进 packages/api/(TypeScript),api/ 只做薄壳去调它。所以你在 client.js 里看到一大串从 @librechat/api import 的函数(createRun、buildToolSet、initializeAgent…),那些实现体都在 packages/api/。
4. 巧妙之处(可借鉴的技术)
这几处是读完能带走的"精华",每条先说妙在哪,再给锚点。
① 用一个 endpoint 字段收敛所有 provider。 前端不关心底层是 Claude 还是 GPT,只发 endpoint: "agents";后端一个 buildFunction 查表就把请求路由到对应的构建器,再由 agent 层统一驱动。加一家新 provider = 加一个 endpoint 配置 + 一个 provider 映射,主干不动。依据:api/server/middleware/buildEndpointOption.js:21(buildFunction)、packages/data-provider/src/schemas.ts:18(EModelEndpoint)、:31(Providers,注释明说"Mirrors @librechat/agents providers")。
② 把"跑 agent"外包给独立包 @librechat/agents。 LibreChat 本体不实现"模型↔工具"的 langgraph 循环,而是把它抽成同团队的独立 npm 包,主仓只负责组装输入、接住流式事件、落库计费。client.js 顶部从 @librechat/agents 一次 import 进 createRun / formatAgentMessages / Providers 等。依据:api/server/controllers/agents/client.js:56、:1378(createRun)。
③ 可恢复流(resumable streams)。 生成任务由 GenerationJobManager 管理,SSE 断了能重连、重放漏掉的 chunk 再续传;单机到 Redis 横向扩展都适用。这让"刷新页面/切设备不丢回复"成为默认行为。依据:api/server/routes/agents/index.js:63(GET /chat/stream/:streamId,"replay support")。
④ agent 层内建记忆与 token 预算。 AgentClient 在组消息时并行拉起会话记忆、把命中的用户事实注入指令,同时对超长上下文做剪枝、用 EMA 校准 token 计数——上下文工程被做成主线的一部分,而非外挂。依据:api/server/controllers/agents/client.js:292(buildMessages)、:628(useMemory)。
⑤ 单向依赖的分层 monorepo。 data-provider(纯共享类型)在最底层,前后端都依赖它;data-schemas 依赖它;packages/api 再依赖上两者;老 api/ 只做薄壳。类型有单一真相来源,前后端不会各写一份。依据:仓库 package.json 的 workspaces + 各包 package.json 依赖方向。
5. 代码地图(导航索引)
给人和 agent 的跳转表。认准符号名(函数/类/常量)——上游更新后行号会漂,符号名通常还在,可直接 grep。
| 主题 | 文件路径(相对克隆根) | 关键符号 |
|---|---|---|
路由挂载 /api/agents | api/server/index.js:268 | app.use('/api/agents', routes.agents) |
| agents 路由树 · 流/中 止端点 | api/server/routes/agents/index.js:63,333 | chatRouter、/chat/stream/:streamId |
| 聊天中间件链与入口 | api/server/routes/agents/chat.js | controller、buildEndpointOption、initializeClient |
| 主线编排控制器 | api/server/controllers/agents/request.js | AgentController(module.exports) |
| Endpoint→provider 分发 | api/server/middleware/buildEndpointOption.js:21 | buildFunction、buildEndpointOption |
| Endpoint 枚举与 provider 映射 | packages/data-provider/src/schemas.ts:18,31 | EModelEndpoint、Providers |
| 组装 AgentClient | api/server/services/Endpoints/agents/initialize.js | initializeClient、AgentClient |
| Agent 客户端内核 | api/server/controllers/agents/client.js:91 | class AgentClient extends BaseClient |
| 跑一次补全 | api/server/controllers/agents/client.js:1141 | chatCompletion |
| 创建 run(外部运行器) | api/server/controllers/agents/client.js:1378 | createRun(from @librechat/agents) |
| 组消息 / 文件上下文 | api/server/controllers/agents/client.js:292 | buildMessages |
| 会话记忆 | api/server/controllers/agents/client.js:628,815 | useMemory、runMemory |
| 客户端基类 | api/app/clients/BaseClient.js | BaseClient |
| 工具加载 | api/server/services/ToolService.js | loadAgentTools、loadToolsForExecution |
| MCP 连接管理 | packages/api/src/mcp/MCPManager.ts | MCPManager |
| Mongo 模型集合 | packages/data-schemas/src/models/ | agent.ts、convo.ts、message.ts、memory.ts … |
| 前端 SPA 入口 | client/src/main.jsx、client/src/App.jsx | App |
溯源说明: 以上引用均 as-of
sourceCommit 9e74cc0e(仓库✨ v0.8.7 (#13907))。@librechat/agents是独立 npm 包,其 langgraph 运行细节不在本仓源码内,本页只描述 LibreChat 侧对它的调用契约(createRun及回传事件);其内部实现标记为本仓外,不在此下断言。