首页
/ Langflow 架构边界与单向依赖规范:代码落点决策树、API 变更协议与 Service 设计指南

Langflow 架构边界与单向依赖规范:代码落点决策树、API 变更协议与 Service 设计指南

2026-09-06 11:44:32作者:邓越浪Henry

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/

五条依赖规则

  1. lfx 绝不能 import langflow.* 注意一个容易踩坑的事实:langflow-base 会安装名为 langflow 的导入包,所以从 lfx 代码里 from langflow... 实际上是"向上依赖",属于违规。如果 LFX 代码需要某个服务,正确做法是在 lfx 内部定义接口(interface),由应用层在启动时注入具体实现。文档还指出:现存代码中已有的向上导入是"已知违规",不要再增加。
  2. langflow-base 可以 import lfx,但绝不能 import langflow.components.<vendor> 中的厂商组件模块(如 openai、pinecone 等),组件必须通过组件注册表动态加载。
  3. 独立的 lfx-* 扩展可以 import 公共的 LFX bundle API,但绝不能 import langflow-base 的应用服务。
  4. langflow 只是依赖元数据,不是另一层应用。 它的作用是把策展的扩展集合叠加到同一个 langflow-base 可执行文件与运行时之上,而不是引入新的应用分层。
  5. 前端只能通过 HTTP/WebSocket 与 langflow 通信,不允许共享文件系统状态。

二、"这段代码该放哪":八步决策树

这是本文档最具实操价值的部分。规则是自上而下走,命中第一条即停止

  1. 框架无关的流程执行、基础组件类或 Component 原语? → 放 src/lfx/src/lfx/:共享原语放 base/,随 lfx 发布的内置组件放 components/
  2. FastAPI 路由、鉴权、数据库模型、alembic 迁移,或生命周期管理的单例? → 放 src/backend/base/langflow/:路由在 api/,服务在 services/<X>/,迁移在 alembic/versions/
  3. 厂商集成(OpenAI、Pinecone、Notion 等)——包装第三方 SDK 的 Component 子类? → 放 src/bundles/<provider>/ 下的独立包,并携带 extension.json 清单。只有当它属于默认发行版时才加入策展的 langflow 依赖。永远不要重命名组件类。
  4. UI、状态或图标? → 放 src/frontend/src/。如果消费了新的 API 字段,还要同步更新 src/frontend/src/types/
  5. lfx run / lfx serve 的 CLI 行为? → 放 src/lfx/src/lfx/cli/
  6. SQLAlchemy/SQLModel 模型变更? → 改 services/database/models/,并且必须执行 make alembic-revision message="..." 生成迁移、再 make alembic-upgrade 应用。仓库根目录的 Makefile 中定义了这两个目标(alembic-revision 生成新迁移,alembic-upgrade 升级数据库到最新版本)。
  7. Flow JSON schema 变更?停手。 已保存的 Flow 必须能继续加载。正确做法是新增版本映射,而不是修改既有形状。细节见 CONTRACTS.md
  8. 同时被 lfxlangflow-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/ 是活跃重构面,当前覆盖 filesmcpregistrationworkflow 四个领域,且两者都在运行时于 api/router.py 中挂载(router.include_router(router_v1)router.include_router(router_v2) 同时生效)。
  • 新端点进 v2 的准入条件只有两条:(a) 用破坏性形状变更替代某个 v1 端点;或 (b) 属于上述四个 v2 领域之一。否则一律在 v1 上增量扩展。
  • 对 v1 端点做破坏性变更被明令禁止。正确姿势是加一个 v2 兄弟端点,v1 原地保留。

五、跨切面变更协议:一个 PR 必须同时改三处

任何触及请求/响应形状的变更,必须在同一个 PR 中更新以下三处:

  1. Pydantic 模型langflow/api/v{1,2}/schemas.py(或路由的局部 schema)。
  2. TypeScript 类型src/frontend/src/types/ 中被受影响页面/store 消费的类型。文档特别强调:项目没有 OpenAPI 生成器,类型是手工维护的——漏掉这一步,前端不会在构建期报错,而是静默地在运行时损坏。
  3. 持久化层(如果该字段被持久化):新增 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 把"一段有状态的逻辑该以什么形态存在"明确划分为三类:

  • Serviceservices/<name>/):生命周期管理的单例,继承 services.base.Service,通过 services/factory.py 注册,经 services/deps.py 访问。适用于有状态、有启动/关闭钩子或持有共享连接(数据库、缓存、队列)的对象。src/backend/base/langflow/services/base.pyService 基类就是一个抽象基类,带 nameready 两个类属性与 teardown()set_ready() 两个生命周期方法,并提供 get_schema() 自动汇总公开方法的签名与文档——这解释了为什么"服务"必须是一个对象而非函数集合。
  • Utilityhelpers/utils/lfx/utils/):纯函数或近纯函数。没有共享状态、没有生命周期就用它。
  • Componentsrc/lfx/src/lfx/components/<category>/):图中用户可见的节点,Component 的子类,带 display_nameinputsoutputs只有当用户必须在画布上连线时才使用 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 检查清单

这份架构文档的价值在于它把"架构品味"翻译成了可执行的检查项。落地时可以直接按顺序核对:

  1. 新增文件前,用八步决策树定位落点,命中即停;
  2. 检查 import 方向:lfx 不碰 langflow.*,langflow-base 核心不碰 langflow.components.<vendor>,扩展不碰 base 应用服务;
  3. 涉及 API 形状变更时,确认同一 PR 内 Pydantic 模型、前端 TS 类型、alembic 迁移(及 Flow-JSON 版本映射)三处齐备;
  4. v1 只做加法,破坏性变更走 v2 兄弟端点;
  5. 共享原语下沉 src/lfx/src/lfx/base/,不再向 langflow/base/ 添加代码;
  6. 有状态单例走 Service 体系(基类 + factory 注册 + deps 访问),纯函数走 helpers/utils,画布节点才用 Component。

掌握以上规则后,你在 Langflow 仓库中的每一次改动都能事先回答"这段代码为什么在这里",并天然避免破坏已保存 Flow 的加载兼容性与前端运行时稳定性。

登录后查看全文
热门项目推荐
相关项目推荐