第 2 课: Monorepo 架构与工程体系

0 阅读7分钟

课程目标

理解 LangChain.js 的工程基础设施:pnpm workspace、Turborepo、tsdown,并搭建本地开发环境。


2.1 为什么是 Monorepo?

LangChain.js 有 40+ 个 npm 包。如果每个包一个仓库 (polyrepo):

  • 跨包改动需要多个 PR、多次发布、版本同步困难
  • 共享代码(如构建配置、TS 配置)需要额外的包管理
  • CI 测试无法在一次提交中验证所有包的兼容性

Monorepo 的收益:

  • 原子提交:一个 PR 可以同时改 core、langchain、openai provider
  • 统一构建:一条命令构建所有包,Turborepo 自动处理依赖顺序
  • 共享配置:TypeScript 配置、构建工具、lint 规则只维护一份
  • 依赖一致workspace:* 确保包之间始终用最新代码

2.2 pnpm Workspace

2.2.1 工作区定义

# pnpm-workspace.yaml
packages:
  - "libs/*"           # 核心包: langchain-core, langchain, textsplitters 等
  - "libs/providers/*" # Provider 包: langchain-openai, langchain-anthropic 等
  - "examples"         # 使用示例
  - "internal/*"       # 内部构建工具

这个配置告诉 pnpm 在这些目录下寻找包(每个目录下的 package.json 构成一个 workspace 成员)。

2.2.2 包间依赖

包之间使用 workspace: 协议互相引用:

// libs/langchain/package.json
{
  "dependencies": {
    "@langchain/core": "workspace:^"  // 始终引用本地最新代码
  }
}

// libs/providers/langchain-openai/package.json
{
  "peerDependencies": {
    "@langchain/core": "^1.0.0"       // 发布后由用户提供
  },
  "devDependencies": {
    "@langchain/core": "workspace:^"  // 开发时用本地版本
  }
}

workspace:^ vs workspace:*

  • workspace:^:发布时替换为带 ^ 的版本号(如 ^1.2.3),允许用户用更高版本
  • workspace:*:发布时替换为精确版本号,仅内部工具使用

2.2.3 常用 pnpm 命令

# 安装所有依赖
pnpm install

# 针对特定包执行命令
pnpm --filter @langchain/core build      # 构建 core 包
pnpm --filter @langchain/core test       # 测试 core 包
pnpm --filter langchain build            # 构建 langchain 包

# 针对多个包
pnpm --filter "@langchain/openai" --filter "@langchain/anthropic" test

# 针对所有包
pnpm -r build   # 递归所有 workspace 执行 build(不推荐,用 turbo 更好)

2.3 Turborepo — 增量构建编排

2.3.1 核心问题

40+ 个包,如果每次都全量构建,耗时巨大。Turborepo 解决两个问题:

  1. 依赖顺序@langchain/openai 依赖 @langchain/core,必须先构建 core
  2. 增量构建:没改动的包不重复构建,利用缓存

2.3.2 配置解析

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env", "pnpm-lock.yaml"],
  "globalPassThroughEnv": ["CI", "NODE_ENV", "GITHUB_ACTIONS"],
  "ui": "stream",
  "tasks": {
    "build:compile": {
      "dependsOn": ["^build:compile"],        // ^ 表示先构建上游依赖
      "env": ["BUILD_MODE"],
      "inputs": ["src/**", "tsconfig.json", "tsdown.config.ts", "package.json"],
      "outputs": ["dist/**"]                  // 缓存产物
    },
    "test": {
      "dependsOn": ["build:compile"],         // 测试前先构建(无 ^,只构建自己)
      "inputs": ["src/**", "tests/**", "**/*.test.ts", "vitest.config.ts"],
      "outputs": []                           // 测试无产物
    },
    "test:single": {
      "dependsOn": ["build:compile"],
      "cache": false                          // 单文件测试不缓存
    },
    "test:integration": {
      "dependsOn": ["build:compile"],
      "cache": false                          // 集成测试不缓存(依赖外部 API)
    }
  }
}

关键语义

  • "dependsOn": ["^build:compile"] — 先构建所有上游依赖包
  • "dependsOn": ["build:compile"] — 先构建自己(不递归上游)
  • "inputs" — 这些文件变化才触发重新执行
  • "outputs" — 执行产物,用于缓存
  • "cache": false — 禁用缓存(集成测试等不可缓存场景)

2.3.3 执行流程示例

# 执行 pnpm build(即 turbo build:compile)
# Turborepo 自动分析依赖图:

# 1. 先构建无依赖的包
#    @langchain/tsconfig  (无依赖)
#    @langchain/build     (无依赖)
#
# 2. 构建 core(依赖 tsconfig 和 build)
#    @langchain/core
#
# 3. 并行构建所有 Provider(都依赖 core)
#    @langchain/openai     ┐
#    @langchain/anthropic   │ 并行执行
#    @langchain/google      │
#    ...                   ┘
#
# 4. 构建 langchain(依赖 core)
#    langchain

2.4 tsdown — TypeScript 编译

2.4.1 为什么是 tsdown?

LangChain.js 需要输出两种格式:

  • ESM (ES Modules):现代 Node.js、Deno、Browser 使用
  • CJS (CommonJS):旧版 Node.js 项目兼容

tsdown 基于 esbuild,速度极快,原生支持双格式输出。

2.4.2 配置解析

@langchain/core 的配置为例:

// libs/langchain-core/tsdown.config.ts
import {
  getBuildConfig,      // 标准化构建配置生成器
  cjsCompatPlugin,     // CJS 兼容插件
  lcSecretsPlugin,     // 密钥检测插件
  importMapPlugin,     // import map 生成插件
  importConstantsPlugin, // import 常量插件
} from "@langchain/build";
import pkg from "./package.json" with { type: "json" };

export default getBuildConfig({
  entry: [
    "./src/index.ts",
    "./src/runnables/index.ts",
    "./src/messages/index.ts",
    "./src/language_models/base.ts",
    "./src/language_models/chat_models.ts",
    "./src/tools/index.ts",
    "./src/prompts/index.ts",
    // ... 80+ 个入口文件
  ],
  define: { __PKG_VERSION__: JSON.stringify(pkg.version) },
  plugins: [
    cjsCompatPlugin({ files: ["dist/", "CHANGELOG.md", "README.md", "LICENSE"] }),
    lcSecretsPlugin(),       // 构建时检测代码中的硬编码密钥
    importMapPlugin(),       // 生成 import_map.ts 用于动态加载
    importConstantsPlugin(), // 生成 import_constants.ts
  ],
});

getBuildConfig() 来自 @langchain/buildinternal/build/src/index.ts),它生成标准化配置:

  • 双格式输出 (ESM + CJS)
  • TypeScript 声明文件 (.d.ts)
  • Source maps
  • 通过 ATTW、publint 验证包质量

2.4.3 构建产物

libs/langchain-core/dist/
├── index.js          # ESM 版本
├── index.cjs         # CJS 版本
├── index.d.ts        # 类型声明
├── index.d.cts       # CJS 类型声明
├── runnables/
│   ├── index.js
│   ├── index.cjs
│   ├── index.d.ts
│   └── ...
└── ...

2.5 共享 TypeScript 配置

2.5.1 基础配置

// internal/tsconfig/base.json
{
  "compilerOptions": {
    "target": "ES2022",           // 编译目标
    "module": "ESNext",           // 模块系统
    "moduleResolution": "bundler", // bundler 模式解析
    "strict": true,               // 严格模式
    "declaration": true,          // 生成 .d.ts
    "declarationMap": true,       // 声明映射
    "sourceMap": true,            // 源码映射
    "esModuleInterop": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "isolatedModules": true,      // 支持独立编译
    "composite": true             // 支持项目引用
  }
}

关键配置解读

  • target: "ES2022" — 使用 ES2022 特性(top-level await、class fields 等)
  • module: "ESNext" — 输出 ESM 模块
  • moduleResolution: "bundler" — 使用 bundler 模式(支持 .js 扩展名引用 .ts 文件)
  • strict: true — 开启所有严格检查
  • composite: true — 支持 Turborepo 的增量构建

2.5.2 包级配置

每个包继承基础配置:

// libs/langchain-core/tsconfig.json
{
  "extends": "../../internal/tsconfig/base.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

2.6 Lint 与格式化

2.6.1 oxlint (不是 ESLint)

pnpm lint        # 检查
pnpm lint:fix    # 自动修复

关键规则(定义在 .oxlintrc.jsonc):

  • no-process-env: error — 禁止直接用 process.env(用 getEnvironmentVariable() 代替)
  • no-explicit-any: error — 禁止 any 类型
  • prefer-template: error — 用模板字符串代替字符串拼接
  • import/extensions: error — import 必须带 .js 扩展名

2.6.2 oxfmt (不是 Prettier)

pnpm format        # 格式化
pnpm format:check  # 只检查不修改

oxlint 和 oxfmt 都用 Rust 实现,对 1000+ 文件的 monorepo 来说性能优势明显。


2.7 目录结构全景

langchainjs/
├── libs/                          # 所有发布到 npm 的包
│   ├── langchain-core/            # @langchain/core — 核心抽象
│   ├── langchain/                 # langchain — 上层实现
│   ├── langchain-textsplitters/   # @langchain/textsplitters — 文本分割
│   ├── langchain-mcp-adapters/    # @langchain/mcp-adapters — MCP 适配
│   ├── langchain-classic/         # @langchain/community — 旧版兼容
│   ├── langchain-standard-tests/  # @langchain/standard-tests — 标准测试
│   └── providers/                 # 33 个 Provider 集成
│       ├── langchain-openai/
│       ├── langchain-anthropic/
│       ├── langchain-google-genai/
│       └── ...
│
├── internal/                      # 内部构建工具(不发布到 npm)
│   ├── build/                     # @langchain/build — 构建配置生成器
│   ├── tsconfig/                  # @langchain/tsconfig — 共享 TS 配置
│   ├── standard-tests/            # 标准测试基础设施
│   ├── model-profiles/            # 模型配置信息
│   └── test-helpers/              # 测试辅助工具
│
├── examples/                      # 使用示例
│   └── src/
│       ├── createAgent/           # Agent 创建示例
│       ├── multi-agent/           # 多 Agent 协作
│       ├── llms/                  # LLM 调用示例
│       └── provider/              # 各 Provider 示例
│
├── package.json                   # 根 package.json(scripts、devDependencies)
├── pnpm-workspace.yaml            # workspace 定义
├── turbo.json                     # Turborepo 配置
├── .oxlintrc.jsonc                # oxlint 配置
├── AGENTS.md                      # AI Agent 开发指南
└── CONTRIBUTING.md                # 贡献指南

2.8 实操:从零搭建开发环境

Step 1: 前置条件

# 确认 Node.js 版本(需要 v20+ ,推荐 v24)
node -v
# v24.x.x

# 确认 pnpm 版本
pnpm -v
# 10.14.0

Step 2: 安装依赖

cd langchainjs
pnpm install

这会:

  • 安装所有包的依赖到根 node_modules(pnpm 的 content-addressable store)
  • 建立 workspace 内部包之间的 symlink

Step 3: 构建核心包

# 必须先构建 core,因为其他包依赖它
pnpm --filter @langchain/core build

Step 4: 验证构建成功

# 运行 core 的单元测试
pnpm --filter @langchain/core test

Step 5: 构建所有包(可选)

# Turborepo 自动按依赖顺序构建
pnpm build

Step 6: 日常开发

# Watch 模式:修改代码自动重新构建
pnpm watch

# 运行单个测试文件
pnpm --filter @langchain/core test src/runnables/tests/runnable.test.ts

# 检查代码风格
pnpm lint
pnpm format:check

2.9 包依赖关系图

@langchain/tsconfig ─────────────────┐
@langchain/build ────────────────────┤
                                     ▼
                              @langchain/core
                                     │
              ┌──────────────────────┼──────────────────────┐
              ▼                      ▼                      ▼
    @langchain/openai        @langchain/anthropic    @langchain/google
    @langchain/ollama        @langchain/deepseek     @langchain/groq
    @langchain/pinecone      @langchain/mongodb      ... (33 个 Provider)
              │                      │                      │
              └──────────────────────┼──────────────────────┘
                                     ▼
                                 langchain
                                     │
                                     ▼
                                 examples

核心原则

  • @langchain/core 不依赖任何 Provider
  • Provider 包通过 peerDependencies 声明对 core 的依赖
  • langchain 依赖 core,提供上层编排能力
  • 用户只需安装 @langchain/core + 需要的 Provider

2.10 源码精读路线

优先级文件关注点
P0pnpm-workspace.yamlworkspace 定义的 4 个路径
P0turbo.json任务依赖图、dependsOn/inputs/outputs 语义
P1internal/tsconfig/base.json共享 TypeScript 配置 (target, module, strict)
P1libs/langchain-core/tsdown.config.tstsdown 构建配置、80+ 入口文件、4 个构建插件
P2internal/build/src/index.tsgetBuildConfig() 生成器、cjsCompatPlugin、lcSecretsPlugin
P2package.json (根目录)脚本命令:build, test, lint, format

本课收获总结

级别你应该掌握的
🟢 基础能搭建环境、构建 core、运行测试;知道 pnpm --filter 命令
🔵 中阶理解 pnpm workspace 的 workspace:^ 语义;理解包之间的依赖关系
🟡 高阶掌握 turbo.jsondependsOninputsoutputs;理解增量构建
🟠 资深理解 tsdown 双输出策略 (ESM+CJS) 的必要性;分析 @langchain/build 的插件设计
🔴 架构能评估 monorepo 对此项目的工程收益;理解包拆分边界(core vs langchain vs providers)

下一课预告

第 3 课将聚焦 TypeScript 高级特性在框架中的应用,包括泛型链、异步迭代器、Zod 双版本兼容等。