Langflow 架构边界与单向依赖规范:代码落点决策树、API 变更协议与 Service 设计指南
Langflow 不是一个单一的"应用",而是由可运行的基础应用(langflow-base)、策展发行版(langflow)、执行器 SDK(lfx)、独立扩展包(lfx-*)和一个前端组成的分层体系。本文基于仓库中的 ARCHITECTURE.md 展开,系统讲解 Langflow 的包依赖方向规则、"这段代码该放哪"的决策树、v1/v2 API 变更协议、跨端切面变更流程,以及 Service / Utility / Component 三者的边界判定方法;读完并掌握这些约束后,你可以在向仓库中新增文件、路由、数据模型或前端类型时,准确判断代码落点并避免破坏既有存档 Flow 的兼容性。
一、单向依赖图:Langflow 的五个组成部分
Langflow 官方将项目定义为:
Langflow is a runnable base application, a curated distribution, an executor SDK, standalone extensions, and one frontend. Dependencies point in one direction. Most "off-narrative" code is a boundary violation.
即:一个可运行的基础应用、一个策展发行版、一个执行器 SDK、若干独立扩展,外加唯一的前端。依赖只能单向流动,绝大多数"不符合叙事"的代码都是边界违规。文档明确要求:在新增任何文件之前先阅读这份边界文档。
完整的包依赖图如下(引自原文档):
frontend (TS) ──HTTP──▶ langflow-base (UI/API, services, graph, db, alembic)
│
▼ may import
lfx (executor, primitives, built-in components)
│
▼ may import
langchain-core, pydantic
langflow (curated distribution) ──depends on──▶ langflow-base
│
└──depends on──▶ standalone lfx-* extensions ──depends on──▶ lfx
从源码结构可以印证这条分层:
lfx位于 src/lfx/src/lfx/,包含base/(共享原语)、components/(随 lfx 分发的内置组件)、cli/(lfx run/lfx serve命令行)、execution/、graph/、interface/、services/等子模块,是一个可独立发布的执行器 SDK;langflow-base位于 src/backend/base/langflow/,承载 API、services、graph、db 与 alembic 迁移,其中服务层 src/backend/base/langflow/services/ 下可以看到auth/、database/、flow/、job_queue/、session/、tracing/、telemetry/等十余个按领域划分的子包;- 独立扩展位于 src/bundles/,如
openai/、ollama/、firecrawl/、anthropic/等,每个扩展包带有自己的extension.json清单; - 前端位于 src/frontend/src/,类型定义集中在 src/frontend/src/types/。
五条依赖规则
lfx绝不能 importlangflow.*。 注意一个容易踩坑的事实:langflow-base会安装名为langflow的导入包,所以从 lfx 代码里from langflow...实际上是"向上依赖",属于违规。如果 LFX 代码需要某个服务,正确做法是在lfx内部定义接口(interface),由应用层在启动时注入具体实现。文档还指出:现存代码中已有的向上导入是"已知违规",不要再增加。langflow-base可以 importlfx,但绝不能 importlangflow.components.<vendor>中的厂商组件模块(如 openai、pinecone 等),组件必须通过组件注册表动态加载。- 独立的
lfx-*扩展可以 import 公共的 LFX bundle API,但绝不能 importlangflow-base的应用服务。 langflow只是依赖元数据,不是另一层应用。 它的作用是把策展的扩展集合叠加到同一个langflow-base可执行文件与运行时之上,而不是引入新的应用分层。- 前端只能通过 HTTP/WebSocket 与
langflow通信,不允许共享文件系统状态。
二、"这段代码该放哪":八步决策树
这是本文档最具实操价值的部分。规则是自上而下走,命中第一条即停止:
- 框架无关的流程执行、基础组件类或
Component原语? → 放src/lfx/src/lfx/:共享原语放base/,随 lfx 发布的内置组件放components/。 - FastAPI 路由、鉴权、数据库模型、alembic 迁移,或生命周期管理的单例?
→ 放
src/backend/base/langflow/:路由在api/,服务在services/<X>/,迁移在alembic/versions/。 - 厂商集成(OpenAI、Pinecone、Notion 等)——包装第三方 SDK 的
Component子类? → 放src/bundles/<provider>/下的独立包,并携带extension.json清单。只有当它属于默认发行版时才加入策展的langflow依赖。永远不要重命名组件类。 - UI、状态或图标?
→ 放
src/frontend/src/。如果消费了新的 API 字段,还要同步更新src/frontend/src/types/。 lfx run/lfx serve的 CLI 行为? → 放src/lfx/src/lfx/cli/。- SQLAlchemy/SQLModel 模型变更?
→ 改
services/database/models/,并且必须执行make alembic-revision message="..."生成迁移、再make alembic-upgrade应用。仓库根目录的 Makefile 中定义了这两个目标(alembic-revision生成新迁移,alembic-upgrade升级数据库到最新版本)。 - Flow JSON schema 变更? → 停手。 已保存的 Flow 必须能继续加载。正确做法是新增版本映射,而不是修改既有形状。细节见 CONTRACTS.md。
- 同时被
lfx和langflow-base共享? → 放src/lfx/src/lfx/base/,绝不放langflow/base/。
三、依赖方向的坏例子与好例子
文档用三组对照示例把抽象规则落到了具体 import 语句层面:
| 场景 | Bad(违规) | Good(合规) |
|---|---|---|
| lfx 需要数据库会话 | 在 src/lfx/... 中写 from langflow.services.deps import session_scope |
定义 lfx.interfaces.SessionProvider 接口,通过构造函数参数注入;由 langflow 在启动时把具体的 session_scope 接线进去 |
| langflow-base 核心引用厂商组件 | 在 api/、services/、graph/ 中写 from langflow.components.openai import ... |
组件通过组件注册表动态加载,核心代码只引用 Component 基类 |
| 新增一个"纯函数集合"的服务 | 建一个只有函数的 MyHelperService |
工具函数放 langflow/helpers/ 或 lfx/utils/;真正的服务继承 services/base.Service 并经 services/factory.py 注册 |
第二条规则与 src/backend/base/langflow/api/router.py 的实际结构一致:核心路由层只挂接各业务 router,任何厂商 SDK 都不在核心 import 链中出现,厂商能力全部经由 src/bundles/ 下的扩展包以注册表方式注入。
四、API 变更协议:v1 冻结兼容,v2 是活跃重构面
这是文档中纠偏性最强的一段,直接纠正了一个常见误解——v2 不是"未来版本",旧文档中"cursor 规则"的说法是错误的。
api/v1/是线上稳定面(约 25 个 router)。既有 v1 端点必须保持向后兼容:只允许新增字段,禁止重命名或删除。从 router.py 可以看到 v1 实际挂载了 chat、flows、validate、store、users、api_key、login、files、monitor、traces、folders、projects、knowledge_bases、memories、mcp、voice_mode、a2a、openai_responses、models 以及 authz 系列(shares / audit / roles / role_assignments / teams / me)等大量 router,全部受"只增不改"约束。api/v2/是活跃重构面,当前覆盖files、mcp、registration、workflow四个领域,且两者都在运行时于api/router.py中挂载(router.include_router(router_v1)与router.include_router(router_v2)同时生效)。- 新端点进 v2 的准入条件只有两条:(a) 用破坏性形状变更替代某个 v1 端点;或 (b) 属于上述四个 v2 领域之一。否则一律在 v1 上增量扩展。
- 对 v1 端点做破坏性变更被明令禁止。正确姿势是加一个 v2 兄弟端点,v1 原地保留。
五、跨切面变更协议:一个 PR 必须同时改三处
任何触及请求/响应形状的变更,必须在同一个 PR 中更新以下三处:
- Pydantic 模型:
langflow/api/v{1,2}/schemas.py(或路由的局部 schema)。 - TypeScript 类型:
src/frontend/src/types/中被受影响页面/store 消费的类型。文档特别强调:项目没有 OpenAPI 生成器,类型是手工维护的——漏掉这一步,前端不会在构建期报错,而是静默地在运行时损坏。 - 持久化层(如果该字段被持久化):新增 alembic 迁移(
make alembic-revision message=...),并且如果形状存在于已保存的 Flow 内部,还要加一条 Flow-JSON 版本映射。
文档对此的裁决非常直接:"If you cannot do all three in one PR, do not start."(无法在一个 PR 里完成这三件事,就不要开始。)
仓库结构与这一协议互相印证:src/frontend/src/types/ 下按领域划分了 api/、flow/、flow-events/、mcp/、messages/、models/、permissions/、store/ 等目录,正是 v1/v2 各端点响应的手动镜像;而后端 services/database/ 与 alembic/versions/ 则构成第三处的落点。
六、Service vs Utility vs Component:三种扩展点的边界
Langflow 把"一段有状态的逻辑该以什么形态存在"明确划分为三类:
- Service(
services/<name>/):生命周期管理的单例,继承services.base.Service,通过services/factory.py注册,经services/deps.py访问。适用于有状态、有启动/关闭钩子或持有共享连接(数据库、缓存、队列)的对象。src/backend/base/langflow/services/base.py 中Service基类就是一个抽象基类,带name、ready两个类属性与teardown()、set_ready()两个生命周期方法,并提供get_schema()自动汇总公开方法的签名与文档——这解释了为什么"服务"必须是一个对象而非函数集合。 - Utility(
helpers/、utils/或lfx/utils/):纯函数或近纯函数。没有共享状态、没有生命周期就用它。 - Component(
src/lfx/src/lfx/components/<category>/):图中用户可见的节点,Component的子类,带display_name、inputs、outputs。只有当用户必须在画布上连线时才使用 Component,绝不要为了暴露内部管线而添加 Component。
判定顺序可以概括为:有状态且有生命周期 → Service;无状态纯函数 → Utility;要出现在画布上 → Component。
七、lfx/base/ 与 langflow/base/:新旧两套 base 树的取舍
两个目录都存在,且各自都有 agents/、data/、models/、prompts/ 子树。文档给出的规则没有歧义:
- 新共享原语一律放
src/lfx/src/lfx/base/; langflow/base/是遗留树,禁止再往里加东西。
这与第二节决策树的第 8 条(双向共享代码进 lfx/base/)和第 1 条(框架无关执行与原语进 src/lfx/src/lfx/)形成闭环:随着架构向"lfx 为核心执行 SDK"演进,langflow/base/ 只保留存量代码供旧路径兼容,任何新增都必须下沉到 lfx 一侧。
小结:把边界当成 PR 检查清单
这份架构文档的价值在于它把"架构品味"翻译成了可执行的检查项。落地时可以直接按顺序核对:
- 新增文件前,用八步决策树定位落点,命中即停;
- 检查 import 方向:lfx 不碰
langflow.*,langflow-base 核心不碰langflow.components.<vendor>,扩展不碰 base 应用服务; - 涉及 API 形状变更时,确认同一 PR 内 Pydantic 模型、前端 TS 类型、alembic 迁移(及 Flow-JSON 版本映射)三处齐备;
- v1 只做加法,破坏性变更走 v2 兄弟端点;
- 共享原语下沉
src/lfx/src/lfx/base/,不再向langflow/base/添加代码; - 有状态单例走 Service 体系(基类 + factory 注册 + deps 访问),纯函数走 helpers/utils,画布节点才用 Component。
掌握以上规则后,你在 Langflow 仓库中的每一次改动都能事先回答"这段代码为什么在这里",并天然避免破坏已保存 Flow 的加载兼容性与前端运行时稳定性。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00