> ## Documentation Index
> Fetch the complete documentation index at: https://opc-43d279b8.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# LangChain

> LangChain 的 Runnable/LCEL 与 LangGraph 状态图编排，覆盖框架选型与迁移考点。

> 我最早用 LangChain 只记住了链式调用，碰到分支和暂停恢复，才转向 LangGraph 的图结构。线性链和有状态图的分界，比背 API 重要。

***

## Q1: LangChain 和 LangGraph 有什么区别？

**1. 2022年: LLM API 的原始形态**

开发者直接调用 OpenAI/Anthropic 的 API，手动拼接 prompt、处理输出、管理上下文。简单的对话没问题，但多步骤的工作流（如 RAG、Agent）需要大量胶水代码。

**2. 致命痛点: 复杂工作流难以编排**

当你需要"检索文档 → 拼接 prompt → 调用 LLM → 解析输出 → 调用工具 → 再次调用 LLM"这样的多步骤流程时，手写代码会变成一团乱麻，错误处理和状态管理极其困难。

**3. LangChain 的引入: 链式调用**

LangChain 把 LLM、工具、提示词抽象为 Runnable，用 `RunnableSequence` 串联成 Pipeline。每个步骤的输出是下一个步骤的输入，像管道一样流动。

**4. LangGraph 的引入: 图结构编排**

LangChain 的链式调用是线性的（A → B → C），无法处理条件分支、循环、并行。LangGraph 用 `StateGraph` 定义有向图，节点是函数，边是条件跳转，支持任意复杂的拓扑结构。

**5. 关键设计哲学: 从一次性请求到有状态工作流**

LangGraph 的核心价值不是"图结构"本身，而是把 LLM 调用从"一次性请求"变成"有状态的工作流"——支持暂停/恢复、错误重试、人机协作。

**LangChain 和 LangGraph 解决的核心问题：**

```text theme={null}
问题：LLM 应用的多步骤工作流难以编排

手写代码：每个步骤手动拼接，错误处理散落各处，状态管理靠全局变量

解决：LangChain 提供链式抽象，LangGraph 提供图结构编排

LangChain：prompt → model → parser（线性 Pipeline）
LangGraph：StateGraph（条件分支 + 循环 + 并行 + 暂停/恢复）
```

> [!NOTE]
>
> LangChain 是线性的管道（A → B → C），LangGraph 是非线性的图（任意拓扑结构 + 状态管理）。

> [card] 🚫 没有 LangChain/LangGraph
>
> 你手写了一个 RAG 流程：手动拼接 prompt、手动调用 LLM、手动解析输出、手动处理错误。50 行代码里有 30 行是胶水代码，换个模型就要改一大片。

> [card] ✅ 引入 LangChain/LangGraph
>
> 你用 LangGraph 定义了一个 StateGraph：prepareNode（拆分文档）→ fanOut（并行检索）→ analyzeNode（LLM 分析）→ mergeNode（合并结果）。代码清晰，支持暂停/恢复，换个模型只改一行配置。

> [!TIP]
>
> 回答时先说"为什么"（多步骤工作流难以编排），再说"怎么解决"（LangChain 链式抽象 + LangGraph 图结构编排）。想听的是你对框架设计动机的理解，而不是背 API。

***

## Q2: LangGraph 的核心组件有哪些？

LangGraph 的本质是**有状态的有向图**，核心逻辑只有 4 个组件：

```text theme={null}
┌───────────────────────────────────────┐
│        StateGraph（状态图定义）         │  ← 定义节点、边、状态结构
└─────────────────┬─────────────────────┘
                  │
      ┌───────────┼───────────┐
      ▼           ▼           ▼
  Node（节点）  Edge（边）  State（状态）
  函数，执行逻辑  条件跳转    Annotation + Reducer
```

```javascript theme={null}
// 1. StateGraph：定义有向图
import { Annotation, StateGraph, START, END } from "@langchain/langgraph";

// 2. Annotation + Reducer：定义状态结构和合并规则
const GraphState = Annotation.Root({
  input: Annotation, // 普通字段：覆盖写
  results: Annotation({
    reducer: (a, b) => a.concat(b), // 数组合并
    default: () => [],
  }),
});

// 3. 节点：函数，接收 state，返回部分 state
async function analyzeNode(state) {
  const response = await llm.invoke(buildPrompt(state.input));
  return { results: [parseResponse(response)] }; // reducer 会自动合并
}

// 4. 组装图
const graph = new StateGraph(GraphState)
  .addNode("analyze", analyzeNode)
  .addEdge(START, "analyze")
  .addEdge("analyze", END)
  .compile();

// 5. 执行
const result = await graph.invoke({ input: "什么是 Fiber？" });
```

> [!WARNING]
>
> 当多个并行节点同时更新同一个状态字段时，Reducer 定义了怎么合并。
>
> * 默认行为：后面的值覆盖前面的值
> * `reducer: (a, b) => a.concat(b)`：数组追加合并
> * `reducer: (a, b) => ({ ...a, ...b })`：对象合并
>
> 没有 Reducer，并行节点的结果会互相覆盖。这是 Multi-Agent 场景下最常见的坑。

> [!TIP]
>
> LangGraph 就像乐高积木。StateGraph 是底板，Node 是积木块（每个块做一件事），Edge 是连接器（决定积木怎么拼），State 是积木之间的管道（数据怎么流转）。你可以拼出直线（Pipeline）、分叉（Fan-out）、循环（Loop）、甚至暂停（Interrupt）——任意拓扑结构。

### LangChain 的历史演进

LangChain 由 Harrison Chase 于 2022 年 10 月开源，最初是一个 Python 库，用于简化 LLM 应用的开发。2023 年发布了 TypeScript 版本（LangChain.js）。2024 年，团队意识到链式调用的局限性（不支持循环、分支、状态管理），推出了 LangGraph 作为图结构编排层。现在 LangChain 定位为"LLM 应用的基础组件库"，LangGraph 定位为"有状态的 Agent 编排框架"。

```text theme={null}
LangChain 的演进时间线：
2022.10：LangChain Python 开源（链式调用）
2023 年初：LangChain.js 发布（TypeScript 版本）
2024.01：LangGraph 发布（图结构编排）
2024.05：LangChain v0.2（组件化重构，Runnable 抽象）
2024 年内：LangGraph 0.2 系列陆续补齐 interrupt、Command、Checkpoint
2024.09：LangChain v0.3（稳定版，与 LangGraph 集成）
```

### Annotation 和 Reducer 的类型系统

LangGraph 的 `Annotation` 不只是定义字段——它定义了完整的类型系统。每个字段可以指定类型、默认值、合并策略（Reducer）。这让图的执行引擎在运行时知道怎么处理并行节点的状态冲突。

```javascript theme={null}
// Annotation 的完整类型定义
import { Annotation } from '@langchain/langgraph';

const GraphState = Annotation.Root({
  // 普通字段：后面的值覆盖前面的值
  step: Annotation<string>,

  // 带 Reducer 的字段：数组追加合并
  messages: Annotation({
    reducer: (a, b) => a.concat(b),
    default: () => [],
  }),

  // 带 Reducer 的字段：对象深度合并
  context: Annotation({
    reducer: (a, b) => ({ ...a, ...b }),
    default: () => ({}),
  }),

  // 带 Reducer 的字段：自定义合并逻辑
  results: Annotation({
    reducer: (a, b) => {
      // 去重合并
      const merged = new Map([...a, ...b].map(r => [r.id, r]));
      return Array.from(merged.values());
    },
    default: () => [],
  }),
});
```

***

## Q3: Send API 和 Conditional Edges 有什么区别？

### 基础梳理

| 维度 | addEdge（静态路由） | Conditional Edges（条件路由） | Send（动态路由） |
| - | - | - | - |
| **确定时机** | 编译时 | 运行时 | 运行时 |
| **目标数量** | 固定 1 个 | 固定 1 个（根据条件选择） | 动态 N 个 |
| **并行能力** | 无 | 无 | 有（并行执行） |
| **适用场景** | 简单线性流程 | 条件分支 | Map-reduce 模式 |

### 专家视角深度剖析

很多人能说出"Send 用于并行"，但实际更在意的是：**为什么不用 `Promise.all` 手动并行？LangGraph 的 Send 比手动并行好在哪里？**

```javascript theme={null}
// 手动并行（Promise.all）：
async function fanOut(state) {
  const results = await Promise.all([
    agentA(state),
    agentB(state),
    agentC(state),
  ]);
  return { results: results.flat() };
}

// LangGraph Send：
function fanOut(state) {
  return state.items.map((item) => new Send("analyzeNode", { item }));
}
// LangGraph 自动并行执行 N 个 analyzeNode，结果通过 Reducer 自动合并
```

LangGraph 的优势不是并行本身，而是：

1. **状态自动管理**：不用手动传参和合并，Reducer 自动处理
2. **条件分支**：可以根据结果决定下一步（`addConditionalEdges`）
3. **可中断可恢复**：可以加 `interrupt` 节点等待人类审批
4. **图结构可可视化**：LangGraph Studio 可以看到图的执行过程
5. **错误处理**：节点失败可以重试或走备选路径（`addConditionalEdges` 根据错误状态路由）

```javascript theme={null}
// LangGraph 的错误处理
graph.addConditionalEdges("analyze", (state) => {
  if (state.error) return "errorHandler"; // 错误走降级路径
  if (state.fallback) return "fallbackNode"; // 备选方案
  return "nextNode"; // 正常路径
});
```

***

## Q4: RAG 是什么？怎么实现？

**RAG（Retrieval-Augmented Generation）：检索增强生成。** 先检索相关文档，再把文档作为上下文给 LLM 回答。

```javascript theme={null}
// RAG 的完整流程：
// 1. 文档切片 → 2. 向量化 → 3. 存入向量数据库
// 4. 用户提问 → 5. 向量检索 → 6. LLM 根据文档回答

import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import {
  RunnableSequence,
  RunnablePassthrough,
} from "@langchain/core/runnables";

// 1. 文档切片（chunkSize 推荐 500-1500 字符，overlap 10-20%）
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,
  chunkOverlap: 200,
});
const docs = await splitter.splitDocuments(documents);

// 2. 向量化 + 存储
const embeddings = new OpenAIEmbeddings();
const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings);

// 3. 检索
const retriever = vectorStore.asRetriever({ k: 3 });

// 4. 组装 RAG 链
const chain = RunnableSequence.from([
  {
    context: retriever, // 检索相关文档
    question: new RunnablePassthrough(), // 原样传递用户问题
  },
  prompt, // 拼接 prompt
  llm, // 调用 LLM
  new StringOutputParser(), // 解析输出
]);

const answer = await chain.invoke("什么是 Fiber？");
```

**chunkSize 怎么选？**

| chunkSize | 效果 |
| - | - |
| 太小（100 字符） | 语义不完整，检索到的片段没用 |
| 太大（5000 字符） | 包含太多无关信息，LLM 注意力分散 |
| 推荐（500-1500 字符） | 平衡语义完整性和精确性 |

> [!TIP]
>
> RAG 的质量取决于三个环节：切片策略（按语义切 > 按段落切 > 按字符切）、Embedding 模型的选择（OpenAI > 开源模型，但成本更高）、检索策略（向量检索 + BM25 混合检索效果最好）。不要只优化 LLM，检索质量对最终结果的影响更大。

### 向量数据库的选择

向量数据库是 RAG 的基础设施。主流选择包括：

| 向量数据库 | 特点 | 适用场景 |
| - | - | - |
| **Pinecone** | 全托管，开箱即用 | 快速原型、小团队 |
| **Weaviate** | 支持混合检索（向量 + BM25） | 需要精确检索的场景 |
| **Chroma** | 轻量级，嵌入式 | 本地开发、小型项目 |
| **Qdrant** | 高性能，Rust 实现 | 大规模生产环境 |
| **pgvector** | PostgreSQL 扩展 | 已有 PostgreSQL 的团队 |

### RAG 的高级优化策略

基础 RAG 只做"检索 → 生成"，高级 RAG 会加入多个优化环节：

```text theme={null}
基础 RAG：问题 → 检索 → 生成

高级 RAG：
├── 查询改写：用 LLM 改写用户问题，提高检索命中率
├── 混合检索：向量检索（语义相似）+ BM25（关键词匹配）
├── 重排序（Reranker）：先粗检索 20 个候选，再用 Cross-Encoder 精排取 Top 3
├── 多跳检索：第一次检索的结果不够，用结果再检索一次
└── 答案验证：用另一个 LLM 检查答案是否基于检索到的文档
```

***

## Q5: LangChain 的核心概念有哪些？

**1. LangChain 的设计哲学: 把 LLM 调用组件化**

LangChain 的核心思想是把 LLM 应用的每个环节抽象为独立组件：Chain（链）、Agent（代理）、Tool（工具）、Memory（记忆）。每个组件可以独立开发、测试、替换。

**2. Chain: 串联多个步骤**

Chain 是 LangChain 最基础的抽象。它把多个步骤（prompt 模板 → LLM 调用 → 输出解析）串联成一个 Pipeline。输入进去，输出出来，像管道一样。

**3. Agent: 让 LLM 自主决策**

Agent 是 Chain 的进化版。Chain 是固定的流程（A → B → C），Agent 是动态的——LLM 根据中间结果自主决定下一步调用什么工具。核心是 ReAct 模式：Reasoning（推理）+ Acting（行动）。

**4. Tool 和 Memory: 扩展 Agent 的能力**

Tool 是 Agent 可以调用的函数（搜索、计算、API 调用）。Memory 是 Agent 的上下文记忆（对话历史、长期记忆）。没有 Tool，Agent 只能聊天；没有 Memory，Agent 每次对话都从零开始。

**LangChain 四大核心概念：**

```text theme={null}
问题：LLM 应用的各个环节（调用、决策、工具、记忆）耦合在一起

混在一起：换一个环节就要改一大片代码，无法独立测试和替换

解决：Chain、Agent、Tool、Memory 四个独立组件

Chain：prompt → llm → parser（固定流程）
Agent：llm 自主决定调用什么 Tool（动态决策）
Tool：Agent 可调用的函数（搜索、计算、API）
Memory：Agent 的上下文记忆（对话历史、长期记忆）
```

```javascript theme={null}
// Chain：固定流程
import { RunnableSequence } from "@langchain/core/runnables";

const chain = RunnableSequence.from([
  promptTemplate, // 1. 拼接 prompt
  llm, // 2. 调用 LLM
  outputParser, // 3. 解析输出
]);
const result = await chain.invoke({ input: "什么是 Fiber？" });

// Agent：动态决策
import { createReactAgent } from "@langchain/langgraph/prebuilt";

const agent = createReactAgent({
  llm: new ChatOpenAI({ model: "gpt-4o" }),
  tools: [searchTool, calculatorTool, fileReaderTool],
  // Agent 自主决定调用哪个 Tool，调用几次
});

// Tool：Agent 可调用的函数
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const searchTool = tool(async ({ query }) => await searchWeb(query), {
  name: "search",
  description: "搜索互联网获取信息",
  schema: z.object({ query: z.string() }),
});

// Memory：上下文记忆
import { ChatMessageHistory } from "langchain/stores/message/in_memory";

const memory = new ChatMessageHistory();
await memory.addUserMessage("我叫张三");
await memory.addAIMessage("你好张三！");
const messages = await memory.getMessages(); // 包含完整对话历史
```

> [!WARNING]
>
> Chain 和 Agent 的核心区别是**决策权**：
>
> * **Chain**：开发者决定流程（A → B → C），LLM 只负责每一步的执行
> * **Agent**：LLM 决定流程（根据中间结果自主选择下一步），开发者只提供工具和约束
>
> Agent 更灵活但也更不可控。实际项目中，能用 Chain 解决的就不要用 Agent——Chain 的行为可预测，Agent 的行为取决于 LLM 的推理。

> [!TIP]
>
> Chain 就像流水线工人——每个工人只做一件事，按固定顺序执行，不会自己决定下一步做什么。Agent 就像一个主管——他拿到任务后，自己决定先找谁了解情况（调用搜索工具），再决定怎么处理（调用计算工具），最后给出结论。主管更灵活，但也更难管——他可能做出你意想不到的决策。

### Runnable 抽象与 LCEL

LangChain 的核心抽象是 `Runnable`——所有组件（Prompt、LLM、Parser、Tool）都实现了 `Runnable` 接口，可以用 `|` 管道操作符串联。这就是 LCEL（LangChain Expression Language）。

```javascript theme={null}
// LCEL：用 | 管道操作符串联 Runnable
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

const prompt = ChatPromptTemplate.fromTemplate("用一句话解释：{concept}");
const llm = new ChatOpenAI({ model: "gpt-4o" });
const parser = new StringOutputParser();

// 用 | 串联：prompt → llm → parser
const chain = prompt | llm | parser;

// 等价于 RunnableSequence.from([prompt, llm, parser])
const result = await chain.invoke({ concept: "Fiber" });
```

### ReAct 模式的工作原理

Agent 的核心是 ReAct（Reasoning + Acting）模式。LLM 先推理（Reasoning）应该调用什么工具，再行动（Action）调用工具，观察结果后继续推理，直到得出最终答案。

```text theme={null}
ReAct 循环：
用户问题 → LLM 推理："我需要搜索" → 调用搜索工具 → 观察结果
         → LLM 推理："信息不够，再搜索" → 调用搜索工具 → 观察结果
         → LLM 推理："信息足够了" → 生成最终答案

// 关键：LLM 在每一步都自主决定是继续调用工具还是给出答案
// 这就是 Agent 和 Chain 的本质区别
```

***

## Q6: LangGraph 的核心概念有哪些？

### 基础梳理

| 概念 | 说明 | 类比 |
| - | - | - |
| **StateGraph** | 定义有向图的容器 | 画板 |
| **Node** | 节点，执行具体逻辑的函数 | 积木块 |
| **Edge** | 边，定义节点之间的流转关系 | 连接线 |
| **State** | 节点之间传递的数据 | 管道里的水 |
| **Annotation** | 定义 State 的结构和合并规则 | 管道的规格说明 |
| **Reducer** | 多个节点并行写入同一字段时的合并策略 | 三通管的混合规则 |

### 专家视角深度剖析

很多人能说出"StateGraph 是图结构"，但实际更在意的是：**StateGraph 和普通的 DAG（有向无环图）有什么区别？为什么不用 Airflow 之类的 DAG 引擎？**

```javascript theme={null}
// StateGraph 的核心设计：State + Annotation + Reducer
import { Annotation, StateGraph, START, END } from "@langchain/langgraph";

// 1. 定义 State 结构（Annotation）
const GraphState = Annotation.Root({
  messages: Annotation({
    reducer: (a, b) => a.concat(b), // 消息追加
    default: () => [],
  }),
  context: Annotation({
    reducer: (a, b) => ({ ...a, ...b }), // 上下文合并
    default: () => ({}),
  }),
  step: Annotation, // 普通字段：覆盖写
});

// 2. 定义 Node（函数）
async function retrieveNode(state) {
  const docs = await retriever.invoke(state.messages.at(-1).content);
  return {
    context: { docs: docs.map((d) => d.pageContent) },
    step: "retrieve",
  };
}

async function generateNode(state) {
  const response = await llm.invoke(buildPrompt(state));
  return {
    messages: [response],
    step: "generate",
  };
}

// 3. 组装图（Edge 定义流转）
const graph = new StateGraph(GraphState)
  .addNode("retrieve", retrieveNode)
  .addNode("generate", generateNode)
  .addEdge(START, "retrieve")
  .addEdge("retrieve", "generate")
  .addConditionalEdges("generate", (state) => {
    if (state.needsMoreInfo) return "retrieve"; // 循环回去
    return END;
  })
  .compile();
```

StateGraph 和 DAG 引擎的核心区别：StateGraph 以 **State 为中心**——节点之间不直接传参，而是通过共享的 State 对象交互。这使得 StateGraph 天然支持循环（节点可以根据 State 决定是否回到之前的节点），而 DAG 引擎（如 Airflow）通常是无环的。

### StateGraph 与 Airflow DAG 的对比

| 维度 | LangGraph StateGraph | Airflow DAG |
| - | - | - |
| **核心抽象** | State（共享状态） | Task（独立任务） |
| **循环支持** | 天然支持（根据 State 决定） | 不支持（必须是无环的） |
| **暂停/恢复** | interrupt + Command | 不支持（任务跑完就结束） |
| **状态管理** | Annotation + Reducer | XCom（有限的状态传递） |
| **适用场景** | AI Agent（需要推理和决策） | 数据管道（ETL、定时任务） |

***

## Q7: RAG 的核心原理是什么？为什么需要它？

**1. LLM 的知识截止问题**

LLM 的训练数据有截止日期（如 2024 年 4 月）。问它"今天的天气"或"最新的 API 文档"，它要么编造（幻觉），要么说"我不知道"。

**2. 致命痛点: LLM 无法访问私有数据**

即使 LLM 知道公开知识，它也无法访问你的私有数据——公司内部文档、项目代码、用户数据。你不能把这些数据都塞进训练集。

**3. RAG 的引入: 检索 + 生成**

RAG（Retrieval-Augmented Generation）的核心思想：先从你的知识库中检索相关文档，再把文档作为上下文给 LLM 回答。LLM 不需要"记住"所有知识，只需要"阅读"检索到的文档。

**4. 核心流程: 切片 → 向量化 → 检索 → 生成**

文档切片（把长文档拆成小块）→ 向量化（用 Embedding 模型把文本转为向量）→ 存入向量数据库 → 用户提问时，把问题向量化，在数据库中找到最相似的文档片段 → 把片段作为上下文给 LLM 生成答案。

**RAG 解决的核心问题：**

```text theme={null}
问题：LLM 知识截止 + 无法访问私有数据 → 幻觉或拒绝回答

没有 RAG：LLM 根据训练数据猜测 → 编造不存在的信息

解决：先检索真实文档，再让 LLM 基于文档回答

有 RAG：检索相关文档 → 文档作为上下文 → LLM 基于文档生成答案
```

```javascript theme={null}
// RAG 的核心原理（简化版，5 步）

// 第 1 步：文档切片
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000, // 每个切片 1000 字符
  chunkOverlap: 200, // 相邻切片重叠 200 字符（保证语义连贯）
});
const chunks = await splitter.splitDocuments(documents);

// 第 2 步：向量化（Embedding）
const embeddings = new OpenAIEmbeddings();
// 把文本转为 1536 维的向量
const vectors = await embeddings.embedDocuments(
  chunks.map((c) => c.pageContent),
);

// 第 3 步：存入向量数据库
const vectorStore = await MemoryVectorStore.fromDocuments(chunks, embeddings);

// 第 4 步：检索（相似度搜索）
const retriever = vectorStore.asRetriever({ k: 3 }); // 取最相似的 3 个片段
const relevantDocs = await retriever.invoke("什么是 Fiber 架构？");

// 第 5 步：生成（LLM 基于文档回答）
const prompt = `根据以下文档回答问题。如果文档中没有相关信息，说"我不确定"。

文档：
${relevantDocs.map((d) => d.pageContent).join("\n---\n")}

问题：什么是 Fiber 架构？`;

const answer = await llm.invoke(prompt);
```

> [!WARNING]
>
> RAG 的质量瓶颈不在 LLM，而在**检索**。检索不到正确的文档，LLM 再强也答不对。
>
> * **chunkSize 太小**（100 字符）：语义不完整，检索到的片段没用
> * **chunkSize 太大**（5000 字符）：包含太多无关信息，LLM 注意力分散
> * **推荐 500-1500 字符**：平衡语义完整性和精确性
> * **overlap 10-20%**：保证相邻切片的语义连贯
>
> 切片策略：按语义切 > 按段落切 > 按字符切。语义切分用 Embedding 计算相邻句子的相似度，在相似度低的地方切分。

> [!TIP]
>
> RAG 就像开卷考试。闭卷考试（没有 RAG）要求你记住所有知识，记不住就只能瞎猜（幻觉）。开卷考试（有 RAG）允许你翻书——先根据题目找到相关的章节（检索），再根据章节内容回答问题（生成）。开卷考试的关键不是你有多聪明（LLM 的能力），而是你翻书的速度和准确性（检索质量）。

### Embedding 模型的选择

Embedding 模型把文本转为向量，决定了检索的语义理解能力。选择时需要在效果、成本、延迟之间做权衡：

| Embedding 模型 | 维度 | 效果 | 成本 | 适用场景 |
| - | - | - | - | - |
| **text-embedding-3-large**（OpenAI） | 3072 | 最好 | 高 | 高质量检索 |
| **text-embedding-3-small**（OpenAI） | 1536 | 好 | 低 | 性价比之选 |
| **BGE-large**（BAAI） | 1024 | 好 | 免费 | 开源方案 |
| **E5-large**（Microsoft） | 1024 | 好 | 免费 | 开源方案 |
| **Cohere embed v3** | 1024 | 好 | 中 | 多语言场景 |

### RAG 与 Fine-tuning 的选择

RAG 和微调（Fine-tuning）都是让 LLM 适应特定领域的手段，但适用场景不同：

| 维度 | RAG | Fine-tuning |
| - | - | - |
| **知识更新** | 实时（换文档就行） | 需要重新训练 |
| **成本** | 低（只需向量数据库） | 高（需要 GPU 和训练数据） |
| **私有数据** | 天然支持 | 需要脱敏处理 |
| **幻觉控制** | 好（基于真实文档） | 一般（仍可能幻觉） |
| **适用场景** | 知识问答、文档检索 | 风格迁移、特定格式输出 |

***

## Q8: RAG 的原理深度解析——从索引到生成的完整流程

### RAG 的三个阶段

RAG 的完整流程分为三个阶段：**索引（Indexing）→ 检索（Retrieval）→ 生成（Generation）**。每个阶段的质量都直接影响最终结果，但检索阶段是最关键的瓶颈——检索不到正确的文档，LLM 再强也答不对。

```text theme={null}
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   索引阶段    │     │   检索阶段    │     │   生成阶段    │
│              │     │              │     │              │
│ 文档加载     │     │ 用户提问     │     │ 拼接 Prompt  │
│    ↓        │     │    ↓        │     │    ↓        │
│ 文档切片     │     │ 问题向量化   │     │ 调用 LLM     │
│    ↓        │     │    ↓        │     │    ↓        │
│ 向量化       │     │ 相似度检索   │     │ 生成答案     │
│    ↓        │     │    ↓        │     │              │
│ 存入向量库   │     │ 返回 Top-K   │     │              │
└──────────────┘     └──────────────┘     └──────────────┘
```

### 向量化原理（Embedding 模型）

向量化是把文本转为高维向量的过程。核心思想：**语义相似的文本，向量距离相近**。Embedding 模型通过大规模语料训练，学会了"理解"文本的语义，把语义信息压缩到一个固定维度的向量中。

```text theme={null}
文本 → Embedding 模型 → 向量（如 1536 维）

"什么是 Fiber？"  →  [0.023, -0.156, 0.892, ..., 0.041]
"Fiber 架构介绍"  →  [0.025, -0.153, 0.889, ..., 0.039]   ← 向量距离近（语义相似）
"今天天气不错"    →  [0.782, 0.341, -0.125, ..., 0.567]   ← 向量距离远（语义无关）
```

**Embedding 模型选型对比：**

| 模型 | 维度 | MTEB 排名 | 成本 | 延迟 | 适用场景 |
| - | - | - | - | - | - |
| **text-embedding-3-large**（OpenAI） | 3072 | Top 5 | \$0.13/1M tokens | 中 | 高质量英文检索 |
| **text-embedding-3-small**（OpenAI） | 1536 | Top 20 | \$0.02/1M tokens | 低 | 性价比之选 |
| **BGE-large-zh**（BAAI） | 1024 | 中文 Top 1 | 免费（自部署） | 高 | 中文场景首选 |
| **Jina-embeddings-v3** | 1024 | Top 10 | \$0.02/1M tokens | 低 | 多语言场景 |
| **Cohere embed-v3** | 1024 | Top 5 | \$0.1/1M tokens | 中 | 多语言 + 混合检索 |

> [!NOTE]
>
> Embedding 模型的选择决定了检索的"语义理解能力"。中文场景首选 BGE，英文场景首选 OpenAI，多语言场景选 Jina 或 Cohere。

### 检索策略：向量检索 vs BM25 vs 混合检索

| 检索策略 | 原理 | 优势 | 劣势 |
| - | - | - | - |
| **向量检索** | 计算问题向量与文档向量的余弦相似度 | 理解语义（"汽车"能匹配"轿车"） | 对精确关键词不敏感 |
| **BM25** | 基于词频（TF）和逆文档频率（IDF）的关键词匹配 | 精确匹配能力强 | 无法理解同义词 |
| **混合检索** | 向量检索 + BM25 加权融合 | 兼顾语义和关键词 | 实现复杂度更高 |

```javascript theme={null}
// LangChain 混合检索实现
import { EnsembleRetriever } from "langchain/retrievers/ensemble";
import { BM25Retriever } from "langchain/retrievers/bm25";
import { MemoryVectorStore } from "langchain/vectorstores/memory";

// 向量检索器
const vectorRetriever = vectorStore.asRetriever({ k: 10 });

// BM25 检索器
const bm25Retriever = BM25Retriever.fromDocuments(docs, { k: 10 });

// 混合检索（权重：向量 0.6 + BM25 0.4）
const ensembleRetriever = new EnsembleRetriever({
  retrievers: [vectorRetriever, bm25Retriever],
  weights: [0.6, 0.4],
});

const results = await ensembleRetriever.invoke("什么是 Fiber 架构？");
```

> [!WARNING]
>
> 纯向量检索的缺陷：当用户搜索"React 18 的 concurrent 模式"时，向量检索可能返回"Vue 3 的响应式"——因为它们在语义空间中距离很近（都是"前端框架的核心特性"），但关键词完全不同。
>
> BM25 的缺陷：当用户搜索"怎么优化首屏加载"时，BM25 无法匹配"如何提升 FCP 指标"——因为关键词完全不同，但语义完全一致。
>
> 混合检索 = 向量检索（捕获语义）+ BM25（捕获关键词），两者的权重可以通过实验调优。LangChain 的 `EnsembleRetriever` 默认权重是 0.5:0.5，实际项目中推荐 0.6:0.4（偏向语义检索）。

### 切片策略详解

切片是把长文档拆成小块的过程。切片质量直接决定检索质量——切片太大，包含太多无关信息；切片太小，语义不完整。

| 切片策略 | 原理 | 适用场景 | LangChain 实现 |
| - | - | - | - |
| **递归切片** | 先按段落切，再按句子切，最后按字符切 | 通用场景 | `RecursiveCharacterTextSplitter` |
| **语义切片** | 用 Embedding 计算相邻句子的相似度，在相似度低的地方切分 | 高质量检索 | `SemanticChunker` |
| **按段落切** | 按 Markdown 标题或 HTML 标签切分 | 结构化文档 | `MarkdownHeaderTextSplitter` |
| **按 Token 切** | 按 LLM 的 Token 数切分 | 控制 Token 预算 | `TokenTextSplitter` |

```javascript theme={null}
// 1. 递归切片（最通用）
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";

const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000, // 每个切片最大 1000 字符
  chunkOverlap: 200, // 相邻切片重叠 200 字符
  separators: ["\n\n", "\n", "。", "！", "？", "，", " "], // 切分优先级
});

// 2. 语义切片（效果最好但最慢）
import { SemanticChunker } from "langchain/text_splitter";

const semanticSplitter = new SemanticChunker(new OpenAIEmbeddings(), {
  breakpointThresholdType: "percentile", // 在相似度低于百分位数的地方切分
  breakpointThresholdAmount: 75, // 低于 75 分位数就切
});

// 3. 按 Markdown 标题切分
import { MarkdownHeaderTextSplitter } from "langchain/text_splitter";

const mdSplitter = new MarkdownHeaderTextSplitter({
  headersToSplitOn: [
    ["#", "title"],
    ["##", "section"],
    ["###", "subsection"],
  ],
});
```

> [!NOTE]
>
> 切片策略的选择优先级：语义切片 > 递归切片 > 按段落切 > 按字符切。但语义切片需要调用 Embedding API，成本和延迟更高。实际项目中，递归切片（chunkSize 500-1500，overlap 10-20%）是最常用的方案。

### 重排序（Reranking）原理

重排序是 RAG 的关键优化环节。流程：**先粗检索 Top-K 个候选（如 20 个），再用 Reranker 模型精排，取 Top-N（如 3 个）给 LLM**。

```text theme={null}
用户提问
   ↓
向量检索 Top-20（粗筛：速度快，精度一般）
   ↓
Reranker 精排（Cross-Encoder 逐对计算相关性分数）
   ↓
取 Top-3（精筛：速度慢，精度高）
   ↓
拼接 Prompt → LLM 生成
```

```javascript theme={null}
// LangChain Reranker 实现
import { CohereRerank } from "@langchain/cohere";

const reranker = new CohereRerank({
  model: "rerank-v3.5",
  topN: 3,
});

// 先粗检索 20 个候选
const retriever = vectorStore.asRetriever({ k: 20 });

// 组装带 Reranker 的检索链
const retrievalChain = RunnableSequence.from([retriever, reranker]);
```

> [!WARNING]
>
> 向量检索用的是 Bi-Encoder（双编码器）——问题和文档分别编码为向量，再计算相似度。这种方式速度快，但"分别编码"会丢失问题和文档之间的细粒度交互信息。
>
> Reranker 用的是 Cross-Encoder（交叉编码器）——把问题和文档拼接在一起，输入同一个模型计算相关性分数。这种方式能捕获更细粒度的语义交互，但速度慢（每对都要单独计算）。
>
> 所以实际架构是：Bi-Encoder 粗筛（快）→ Cross-Encoder 精排（准）。这是一个经典的"召回-精排"两阶段架构。

***

## Q9: 如何自主搭建企业级 RAG

### 企业级 RAG 的完整架构

```text theme={null}
┌─────────────────────────────────────────────────────────────────┐
│                     企业级 RAG 架构                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  文档层：PDF / Word / 网页 / API 文档 / 数据库                   │
│    ↓                                                            │
│  加载层：DocumentLoader（Unstructured / LlamaParse）             │
│    ↓                                                            │
│  切片层：TextSplitter（递归 / 语义 / 按段落）                    │
│    ↓                                                            │
│  向量化层：Embedding Model（OpenAI / BGE / Jina）                │
│    ↓                                                            │
│  存储层：Vector Database（Milvus / Pinecone / Weaviate）        │
│    ↓                                                            │
│  检索层：Retriever（向量 / BM25 / 混合检索 + Reranker）          │
│    ↓                                                            │
│  生成层：LLM（GPT-4o / Claude / 开源模型）                      │
│    ↓                                                            │
│  评估层：Eval（忠实度 / 相关性 / 准确性）                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

### 向量数据库选型

| 向量数据库 | 部署方式 | 混合检索 | 特点 | 适用场景 |
| - | - | - | - | - |
| **Milvus** | 自部署 / Zilliz Cloud | 支持 | 高性能，支持亿级向量 | 大规模生产环境 |
| **Pinecone** | 全托管 | 支持 | 开箱即用，无需运维 | 快速原型、小团队 |
| **Weaviate** | 自部署 / Cloud | 原生支持 | 内置 BM25 + 向量混合检索 | 需要精确检索的场景 |
| **Chroma** | 嵌入式 | 不支持 | 轻量级，零配置 | 本地开发、小型项目 |
| **Qdrant** | 自部署 / Cloud | 支持 | Rust 实现，高性能 | 高性能生产环境 |
| **pgvector** | PostgreSQL 扩展 | 需配合 | 已有 PG 的团队零成本引入 | 已有 PostgreSQL 的团队 |

> [!NOTE]
>
> 向量数据库选型：小项目用 Chroma（零配置），中等项目用 Pinecone（全托管），大规模生产用 Milvus 或 Qdrant（高性能），已有 PostgreSQL 的团队用 pgvector（零成本引入）。

### Embedding 模型选型

| 场景 | 推荐模型 | 理由 |
| - | - | - |
| **中文场景** | BGE-large-zh（BAAI） | MTEB 中文排名第一，开源免费 |
| **英文场景** | text-embedding-3-small（OpenAI） | 性价比最高，MTEB 英文 Top 20 |
| **多语言场景** | Jina-embeddings-v3 | 支持 89 种语言，成本低 |
| **高质量检索** | text-embedding-3-large（OpenAI） | MTEB 英文 Top 5，效果最好 |
| **本地部署** | BGE-large / E5-large | 开源免费，无需调用外部 API |

### 质量优化：查询改写、HyDE、多路召回

**1. 查询改写（Query Rewriting）**

用 LLM 改写用户问题，提高检索命中率。例如用户问"怎么让页面快一点"，改写为"前端性能优化策略：减少 FCP、LCP、CLS"。

```javascript theme={null}
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";

const rewritePrompt = ChatPromptTemplate.fromTemplate(
  `你是一个搜索查询改写专家。请把以下用户问题改写为更适合向量检索的形式，保持原意但增加关键词。
  
  用户问题：{question}
  改写后的查询：`,
);

const rewriter =
  rewritePrompt |
  new ChatOpenAI({ model: "gpt-4o-mini" }) |
  new StringOutputParser();
const rewrittenQuery = await rewriter.invoke({ question: "怎么让页面快一点" });
// 输出："前端页面加载性能优化策略，减少首屏渲染时间（FCP），提升最大内容绘制（LCP），降低累积布局偏移（CLS）"
```

**2. HyDE（Hypothetical Document Embeddings）**

让 LLM 先生成一个"假设性答案"，再用这个答案去检索。假设性答案虽然可能不准确，但它的语义和真实答案更接近，检索效果更好。

```text theme={null}
传统 RAG：用户问题 → 向量检索 → 生成答案
HyDE：    用户问题 → LLM 生成假设性答案 → 用假设性答案向量检索 → 生成最终答案
```

**3. 多路召回（Multi-Query Retrieval）**

用 LLM 把一个问题拆成多个子问题，分别检索，合并去重。覆盖更多角度，提高召回率。

```javascript theme={null}
import { MultiQueryRetriever } from "langchain/retrievers/multi_query";

const multiQueryRetriever = MultiQueryRetriever.fromLLM({
  llm: new ChatOpenAI({ model: "gpt-4o-mini" }),
  retriever: vectorStore.asRetriever({ k: 5 }),
  // LLM 会自动生成 3-5 个不同角度的查询
});
```

> [!WARNING]
>
> RAG 质量优化的投入产出比排序：
>
> 1. **切片策略**（投入低，收益高）——调整 chunkSize 和 overlap 就能显著提升效果
> 2. **混合检索 + Reranker**（投入中，收益高）——混合检索 + Reranker 的组合通常能提升 20-30% 的准确率
> 3. **查询改写 / HyDE**（投入中，收益中）——对模糊问题效果明显，对精确问题提升有限
> 4. **Embedding 模型升级**（投入高，收益中）——从 text-embedding-3-small 升级到 text-embedding-3-large，效果提升有限但成本翻倍
> 5. **Fine-tuning Embedding**（投入最高，收益看场景）——只有当通用模型在特定领域表现差时才考虑

***

## Q10: LangChain + RAG 集成——Chain 设计与 Retriever 原理

### LangChain RAG Chain 设计

LangChain 的 RAG Chain 本质是一个 `RunnableSequence`：检索器（Retriever）+ 提示词模板（Prompt）+ LLM + 输出解析器（Parser）。

```javascript theme={null}
import {
  RunnableSequence,
  RunnablePassthrough,
} from "@langchain/core/runnables";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

// 1. 定义 Prompt 模板
const prompt = ChatPromptTemplate.fromTemplate(
  `根据以下上下文回答问题。如果上下文中没有相关信息，说"我不确定"。

上下文：
{context}

问题：{question}

回答：`,
);

// 2. 定义格式化函数（把 Document[] 转为字符串）
const formatDocs = (docs) => docs.map((d) => d.pageContent).join("\n---\n");

// 3. 组装 RAG Chain
const ragChain = RunnableSequence.from([
  {
    context: retriever.pipe(formatDocs), // 检索 + 格式化
    question: new RunnablePassthrough(), // 原样传递问题
  },
  prompt, // 拼接 Prompt
  new ChatOpenAI({ model: "gpt-4o" }), // 调用 LLM
  new StringOutputParser(), // 解析输出
]);

const answer = await ragChain.invoke("什么是 Fiber 架构？");
```

### Retriever 原理

Retriever 是 LangChain 的检索抽象。核心接口只有一个方法：`invoke(query) → Document[]`。所有检索器（向量检索、BM25、混合检索、Reranker）都实现这个接口，可以自由组合。

```text theme={null}
Retriever 接口：
┌─────────────────────────────────────────┐
│  invoke(query: string) → Document[]     │
└─────────────────────────────────────────┘

实现方式：
├── VectorStoreRetriever（向量检索）
├── BM25Retriever（关键词检索）
├── EnsembleRetriever（混合检索）
├── MultiQueryRetriever（多路召回）
└── ContextualCompressionRetriever（带 Reranker 的检索）
```

### Chain 类型：Stuff vs MapReduce vs Refine vs Map-Rerank

当检索到的文档太多，超出 LLM 上下文窗口时，需要用不同的 Chain 策略处理：

| Chain 类型 | 原理 | 优势 | 劣势 | 适用场景 |
| - | - | - | - | - |
| **Stuff** | 把所有文档拼接成一个 Prompt | 最简单，一次调用 | 文档太多会超出上下文窗口 | 文档少（\< 5 个） |
| **MapReduce** | 每个文档单独摘要，再合并摘要 | 支持大量文档 | 多次调用 LLM，成本高 | 文档多且需要全局视角 |
| **Refine** | 先用第一个文档生成答案，再用后续文档逐个修正 | 答案逐步精炼 | 后续文档的影响递减 | 需要逐步深入的场景 |
| **Map-Rerank** | 每个文档单独评分，选最高分的答案 | 忽略无关文档 | 可能丢失跨文档信息 | 有明确答案的场景 |

```javascript theme={null}
import { loadQAStuffChain, loadQAMapReduceChain } from "langchain/chains";

// Stuff Chain：所有文档拼接成一个 Prompt
const stuffChain = loadQAStuffChain(llm);
const result = await stuffChain.call({
  input_documents: docs.slice(0, 3), // 最多 3 个文档
  question: "什么是 Fiber？",
});

// MapReduce Chain：每个文档单独摘要，再合并
const mapReduceChain = loadQAMapReduceChain(llm);
const result = await mapReduceChain.call({
  input_documents: docs, // 可以处理大量文档
  question: "总结所有文档的核心观点",
});
```

### 自定义 RAG Pipeline

实际项目中，基础的 RAG Chain 往往不够用。你需要自定义 Pipeline，加入查询改写、混合检索、Reranker、答案验证等环节。

```javascript theme={null}
import { RunnableSequence, RunnableBranch } from "@langchain/core/runnables";

// 自定义 RAG Pipeline：查询改写 → 混合检索 → Reranker → 生成 → 验证
const customRAGPipeline = RunnableSequence.from([
  // Step 1: 查询改写
  async (question) => {
    const rewritten = await rewriter.invoke({ question });
    return { original: question, rewritten };
  },

  // Step 2: 混合检索（向量 + BM25）
  async ({ original, rewritten }) => {
    const docs = await ensembleRetriever.invoke(rewritten);
    return { question: original, docs };
  },

  // Step 3: Reranker 精排
  async ({ question, docs }) => {
    const rerankedDocs = await reranker.invoke({
      query: question,
      documents: docs,
    });
    return { question, docs: rerankedDocs.slice(0, 3) };
  },

  // Step 4: 生成答案
  {
    answer: ragChain,
    context: (input) => input.docs,
  },

  // Step 5: 答案验证（可选）
  async ({ answer, context, question }) => {
    const verification = await verifier.invoke({ answer, context, question });
    return { answer, isGrounded: verification.isGrounded };
  },
]);
```

> [!NOTE]
>
> LangChain 的 RAG Chain 设计核心是"可组合性"——Retriever、Prompt、LLM、Parser 都是 Runnable，可以用 `|` 管道操作符自由串联。实际项目中，不要用默认的 Stuff Chain，而是根据文档量和质量需求选择合适的 Chain 类型，再加入查询改写和 Reranker 等优化环节。

***

## Q11: LangGraph 编排详解——从 StateGraph 到人机协作

### StateGraph 原理

StateGraph 是 LangGraph 的核心数据结构。它不是一个普通的 DAG（有向无环图），而是一个**有状态的有向图**——节点之间不直接传参，而是通过共享的 State 对象交互。

```text theme={null}
StateGraph 的核心设计：

State（共享状态）← 所有节点读写同一个 State
    ↓
Node（节点）    ← 函数，接收 State，返回部分 State
    ↓
Edge（边）      ← 定义节点之间的流转关系（静态 / 条件 / 动态）
    ↓
Annotation + Reducer ← 定义 State 的结构和合并规则
```

### 节点（Node）与边（Edge）

```javascript theme={null}
import { Annotation, StateGraph, START, END } from "@langchain/langgraph";

// 1. 定义 State 结构
const RAGState = Annotation.Root({
  question: Annotation,
  rewrittenQuery: Annotation,
  documents: Annotation({
    reducer: (a, b) => [...a, ...b],
    default: () => [],
  }),
  answer: Annotation,
  needsMoreInfo: Annotation,
});

// 2. 定义节点（函数）
async function rewriteNode(state) {
  const rewritten = await rewriter.invoke({ question: state.question });
  return { rewrittenQuery: rewritten };
}

async function retrieveNode(state) {
  const docs = await retriever.invoke(state.rewrittenQuery);
  return { documents: docs };
}

async function generateNode(state) {
  const answer = await llm.invoke(buildPrompt(state));
  const needsMore = await checkAnswer(answer, state.documents);
  return { answer, needsMoreInfo: needsMore };
}

// 3. 组装图
const graph = new StateGraph(RAGState)
  .addNode("rewrite", rewriteNode)
  .addNode("retrieve", retrieveNode)
  .addNode("generate", generateNode)

  // 静态边：固定流转
  .addEdge(START, "rewrite")
  .addEdge("rewrite", "retrieve")
  .addEdge("retrieve", "generate")

  // 条件边：根据状态决定下一步
  .addConditionalEdges("generate", (state) => {
    if (state.needsMoreInfo) return "retrieve"; // 信息不够，重新检索
    return END; // 信息足够，结束
  })
  .compile();
```

### 条件路由（Conditional Routing）

条件路由是 LangGraph 区别于线性 Chain 的核心能力。它允许图根据运行时的状态动态决定下一步走向。

```javascript theme={null}
// 条件路由的三种模式：

// 1. 简单条件：二选一
graph.addConditionalEdges("analyze", (state) => {
  return state.confidence > 0.8 ? "answer" : "fallback";
});

// 2. 多路条件：多选一
graph.addConditionalEdges("router", (state) => {
  if (state.intent === "search") return "searchNode";
  if (state.intent === "calculate") return "calcNode";
  if (state.intent === "chat") return "chatNode";
  return "fallbackNode";
});

// 3. 动态路由：Send API（并行多路）
import { Send } from "@langchain/langgraph";

function fanOut(state) {
  return state.items.map((item) => new Send("processNode", { item }));
}
graph.addConditionalEdges("dispatcher", fanOut);
```

### 人机协作（Human-in-the-Loop）

人机协作是 LangGraph 的杀手级特性。通过 `interrupt` 机制，图可以在关键节点暂停，等待人类审批后再继续。

```javascript theme={null}
import { interrupt, Command } from "@langchain/langgraph";

// 节点中使用 interrupt 暂停
async function humanApprovalNode(state) {
  // interrupt 暂停图执行，把 value 返回给人类
  const humanResponse = interrupt({
    question: "是否批准这个方案？",
    plan: state.plan,
    risk: state.riskLevel,
  });

  // 人类回复后，interrupt 返回人类的回复
  return {
    approved: humanResponse === "approve",
    humanFeedback: humanResponse,
  };
}

// 编译图
const graph = new StateGraph(State)
  .addNode("analyze", analyzeNode)
  .addNode("humanApproval", humanApprovalNode)
  .addNode("execute", executeNode)
  .addEdge(START, "analyze")
  .addEdge("analyze", "humanApproval")
  .addConditionalEdges("humanApproval", (state) => {
    return state.approved ? "execute" : "analyze"; // 不批准则重新分析
  })
  .addEdge("execute", END)
  .compile();

// 执行图（会暂停在 humanApproval 节点）
const result = await graph.invoke({ input: "执行部署" });
// result 包含 interrupt 的 value，展示给人类

// 人类审批后，用 Command 恢复执行
const finalResult = await graph.invoke(
  new Command({ resume: "approve" }),
  config,
);
```

> [!WARNING]
>
> `interrupt(value)` 的底层实现：
>
> 1. 节点执行到 `interrupt()` 时，把当前状态保存到 Checkpoint
> 2. 抛出 `GraphInterrupt` 异常，暂停图的执行
> 3. 外部代码拿到 `value`，展示给人类
> 4. 人类回复后，调用 `Command(resume=...)` 恢复
> 5. 从 Checkpoint 恢复状态，`interrupt()` 返回人类的回复
>
> 关键：Checkpoint 是 interrupt 的基础——没有 Checkpoint，就无法暂停和恢复。

### 检查点（Checkpoint）与状态恢复

Checkpoint 是 LangGraph 的状态持久化机制。每个节点执行后，自动保存状态快照，支持暂停/恢复、错误重试、时间旅行。

```javascript theme={null}
import { MemorySaver, PostgresSaver } from "@langchain/langgraph-checkpoint";

// 开发环境：内存存储
const memoryCheckpointer = new MemorySaver();

// 生产环境：PostgreSQL 存储
const postgresCheckpointer = new PostgresSaver({
  connectionString: "postgresql://user:pass@localhost:5432/langgraph",
});

// 编译图时指定 Checkpointer
const graph = new StateGraph(State)
  .addNode("step1", step1Node)
  .addNode("step2", step2Node)
  .addEdge(START, "step1")
  .addEdge("step1", "step2")
  .addEdge("step2", END)
  .compile({ checkpointer: postgresCheckpointer });

// 执行时传入 thread_id（用于标识同一次执行）
const config = { configurable: { thread_id: "user-123" } };
const result = await graph.invoke({ input: "开始" }, config);

// 时间旅行：回到任意 checkpoint 查看历史状态
const history = await graph.getStateHistory(config);
for (const state of history) {
  console.log(state.values); // 每个 checkpoint 的状态
}
```

> [!NOTE]
>
> LangGraph 的核心价值不是"图结构"本身，而是把 LLM 调用从"一次性请求"变成"有状态的工作流"——Checkpoint 支持暂停/恢复和时间旅行，interrupt 支持人机协作，条件路由支持动态决策。这三个能力让 LangGraph 成为构建企业级 Agent 的首选框架。

***

## Q12: Agent Skills 与 RAG 的区别

### 核心区别

RAG 和 Skills 是 Agent 的两种核心能力，解决的是完全不同的问题：

| 维度 | RAG（检索增强生成） | Skills（工具调用） |
| - | - | - |
| **本质** | 知识注入——让 Agent "知道"更多 | 能力扩展——让 Agent "做到"更多 |
| **数据流** | 知识库 → 检索 → LLM 上下文 | LLM → 决策 → 调用外部工具 |
| **核心组件** | 向量数据库 + Embedding + Retriever | Tool 定义 + Function Calling |
| **典型场景** | "公司的请假政策是什么？" | "帮我查一下今天的天气" |
| **知识来源** | 静态文档（PDF、网页、数据库） | 动态数据（API、数据库、搜索引擎） |
| **更新方式** | 更新文档 → 重新索引 | 更新 Tool 定义 → 重新部署 |

```text theme={null}
RAG 的数据流：
用户问题 → 向量检索知识库 → 检索到的文档作为上下文 → LLM 基于上下文回答

Skills 的数据流：
用户问题 → LLM 推理需要调用什么工具 → 执行工具 → 工具返回结果 → LLM 基于结果回答
```

### 适用场景对比

```javascript theme={null}
// RAG 场景：知识问答
// 用户问："公司的年假政策是什么？"
// 流程：检索公司制度文档 → LLM 基于文档回答
const ragAnswer = await ragChain.invoke("公司的年假政策是什么？");

// Skills 场景：工具调用
// 用户问："今天北京天气怎么样？"
// 流程：LLM 决定调用天气 API → 执行 API → LLM 基于结果回答
const agent = createReactAgent({
  llm: new ChatOpenAI({ model: "gpt-4o" }),
  tools: [weatherTool, searchTool, calculatorTool],
});
const skillAnswer = await agent.invoke({ messages: [userMessage] });

// 组合场景：RAG + Skills
// 用户问："根据公司制度，我还能请几天年假？帮我查一下 HR 系统。"
// 流程：RAG 检索年假制度 → Skills 调用 HR API 查剩余天数 → LLM 综合回答
```

> [!NOTE]
>
> RAG 解决的是"Agent 不知道"的问题（知识注入），Skills 解决的是"Agent 做不到"的问题（能力扩展）。实际项目中，企业级 Agent 通常两者兼备——RAG 提供领域知识，Skills 提供执行能力。

***

## Q13: Vercel AI SDK 是什么？和 LangChain 有什么区别？

### 基础梳理

| 维度 | Vercel AI SDK | LangChain |
| - | - | - |
| **定位** | 前端优先的 AI SDK | 全功能 LLM 应用框架 |
| **核心能力** | 流式响应、工具调用、多模态支持 | 链式编排、Agent、RAG、Memory |
| **核心 API** | `useChat`、`useCompletion`、`useObject` | `RunnableSequence`、`StateGraph` |
| **包体积** | 轻量（\~50KB） | 重量级（数 MB，含大量依赖） |
| **适用场景** | 前端快速集成 AI 对话和流式 UI | 复杂多步骤 AI 工作流 |
| **流式支持** | 原生支持，内置 React/Vue/Svelte hooks | 需手动处理流式响应 |
| **TypeScript** | 端到端类型安全 | 类型支持较弱 |

### 专家视角深度剖析

很多人只知道 LangChain，但实际更在意的是：**为什么 Vercel AI SDK 在前端场景下比 LangChain 更合适？**

Vercel AI SDK 由 Vercel 团队（Guillermo Rauch、Jared Palmer 等）开发，设计哲学是**前端优先**——把 AI 能力以 React hooks 的形式暴露给前端开发者，不需要理解后端的链式编排和 Agent 概念。

核心差异在于**抽象层级**不同：

```text theme={null}
LangChain 的抽象层级（后端视角）：
  Prompt → Model → Parser → Tool → Agent → Graph
  关注：工作流编排、多步骤推理、状态管理

Vercel AI SDK 的抽象层级（前端视角）：
  useChat → 流式消息渲染 → useCompletion → 文本补全 → useObject → 结构化输出
  关注：UI 交互、流式渲染、类型安全
```

```javascript theme={null}
// Vercel AI SDK 的核心 API

// 1. useChat：对话式 AI（最常用）
import { useChat } from "ai/react";

function Chat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } =
    useChat({
      api: "/api/chat", // 后端 API 路由
    });

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          {m.role}: {m.content}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit" disabled={isLoading}>
          发送
        </button>
      </form>
    </div>
  );
}

// 2. useCompletion：文本补全
import { useCompletion } from "ai/react";

function Completion() {
  const { completion, input, handleInputChange, handleSubmit } = useCompletion({
    api: "/api/completion",
  });

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">补全</button>
      </form>
      <p>{completion}</p>
    </div>
  );
}

// 3. useObject：结构化输出（JSON Schema 约束）
import { useObject } from "ai/react";
import { z } from "zod";

const recipeSchema = z.object({
  name: z.string(),
  ingredients: z.array(z.string()),
  steps: z.array(z.string()),
});

function RecipeGenerator() {
  const { object, submit } = useObject({
    api: "/api/recipe",
    schema: recipeSchema,
  });

  return (
    <div>
      <button onClick={() => submit("宫保鸡丁")}>生成菜谱</button>
      {object && (
        <div>
          <h2>{object.name}</h2>
          <ul>
            {object.ingredients?.map((i) => (
              <li key={i}>{i}</li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}
```

### 后端 API 路由的实现

Vercel AI SDK 的前端 hooks 需要配合后端 API 路由使用。后端用 `streamText` 或 `streamObject` 生成流式响应：

```javascript theme={null}
// app/api/chat/route.ts（Next.js App Router）
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    system: '你是一个有帮助的助手。',
    messages,
    maxTokens: 1024,
  });

  return result.toDataStreamResponse();  // 返回流式响应
}

// app/api/recipe/route.ts（结构化输出）
import { streamObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const recipeSchema = z.object({
  name: z.string().describe('菜名'),
  ingredients: z.array(z.string()).describe('食材列表'),
  steps: z.array(z.string()).describe('步骤列表'),
});

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const result = streamObject({
    model: openai('gpt-4o'),
    schema: recipeSchema,
    prompt: `生成一个菜谱：${prompt}`,
  });

  return result.toTextStreamResponse();
}
```

### 工具调用（Tool Calling）

Vercel AI SDK 也支持工具调用，但实现方式比 LangChain 更轻量：

```javascript theme={null}
// 后端：定义工具并调用
import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    messages,
    tools: {
      weather: tool({
        description: '获取指定城市的天气',
        parameters: z.object({
          city: z.string().describe('城市名'),
        }),
        execute: async ({ city }) => {
          const weather = await fetchWeather(city);
          return weather;
        },
      }),
    },
    maxSteps: 3,  // 允许最多 3 步工具调用
  });

  return result.toDataStreamResponse();
}
```

> [!NOTE]
>
> Vercel AI SDK 是前端优先的 AI 集成方案——用 React hooks（`useChat`、`useCompletion`、`useObject`）把 AI 能力直接暴露给前端，内置流式响应和类型安全。LangChain 是后端优先的 AI 工作流框架——用链式编排和图结构处理复杂的多步骤推理。选择标准：前端 UI 集成用 Vercel AI SDK，后端复杂工作流用 LangChain。

### Vercel AI SDK 的适用场景

| 场景 | 推荐方案 | 理由 |
| - | - | - |
| **前端 AI 对话界面** | Vercel AI SDK | `useChat` 内置流式渲染、消息管理、loading 状态 |
| **AI 文本补全** | Vercel AI SDK | `useCompletion` 一行代码实现流式补全 |
| **结构化数据生成** | Vercel AI SDK | `useObject` + Zod schema，端到端类型安全 |
| **RAG 知识问答** | LangChain | 需要向量数据库、检索器、文档切片等复杂组件 |
| **多 Agent 协作** | LangGraph | 需要图结构编排、状态管理、人机协作 |
| **复杂工具编排** | LangChain | 多工具串联、条件分支、错误重试 |

> [!TIP]
>
> 回答时先说"两者定位不同"（Vercel AI SDK 是前端集成层，LangChain 是后端编排层），再说"选择标准"（前端 UI 用 Vercel AI SDK，复杂工作流用 LangChain）。想听的是你对两者抽象层级差异的理解，而不是比较 API 数量。

### AI SDK Provider 的统一抽象

Vercel AI SDK 的核心设计之一是 **Provider 抽象**——用统一的接口对接不同的 LLM 提供商：

```javascript theme={null}
// 统一接口，切换模型只改一行
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google';

// OpenAI
streamText({ model: openai('gpt-4o'), ... });

// Anthropic
streamText({ model: anthropic('claude-3-5-sonnet'), ... });

// Google
streamText({ model: google('gemini-1.5-pro'), ... });
```

这种设计让前端开发者可以在不修改业务代码的情况下切换 LLM 提供商，和 LangChain 的 `ChatOpenAI` / `ChatAnthropic` 抽象类似，但更轻量。

***

## 🔍 底层源码：LangGraph 的 interrupt 机制

LangGraph 的 interrupt 是怎么实现暂停和恢复的？

`interrupt(value)` 在节点内部暂停图执行，把 `value` 返回给人类，等待 `Command(resume=...)` 恢复（详见 [LangGraph 源码：libs/langgraph/langgraph/types.py](https://github.com/langchain-ai/langgraph/blob/main/libs/langgraph/langgraph/types.py)）：

```python theme={null}
# interrupt 的底层原理：
# 1. 图执行到 interrupt() 时，保存当前状态到 Checkpoint
# 2. 抛出 GraphInterrupt 异常，暂停执行
# 3. 外部代码拿到 value，展示给人类
# 4. 人类回复后，调用 Command(resume=...) 恢复
# 5. 从 Checkpoint 恢复状态，interrupt() 返回人类的回复

# 节点函数
def human_approval(state):
    human_response = interrupt({
        "question": "是否批准这个方案？",
        "plan": state["plan"]
    })
    return {"approved": human_response == "yes"}

# 恢复执行
from langgraph.types import Command
graph.invoke(Command(resume="yes"), config)
```

Checkpoint 是 interrupt 的基础——每个节点执行后保存状态快照，支持暂停/恢复、错误重试、时间旅行（回到任意 checkpoint 查看历史状态）。

***

## Q14: 及格回答 vs 满分回答

### 问题："说说 LangChain 和 LangGraph 的区别"

**及格回答（60-70分）：**

> "LangChain 是 LLM 应用框架，LangGraph 是多 Agent 编排框架。LangGraph 用图结构定义 Agent 之间的流转。"

问题出在：**知道名字但不理解设计**。

**满分回答（90-100分）：**

> "LangChain 的核心是**链式调用**——把 LLM、工具、提示词抽象为 Runnable，用 `RunnableSequence` 串联成 Pipeline。每个步骤的输出是下一个步骤的输入。适合简单的线性流程（如 RAG：检索 → 增强 → 生成）。<sup>\[1]</sup>"

> "LangGraph 的核心是**有状态的图结构**——用 `StateGraph` 定义节点（函数）和边（条件跳转）。支持循环、分支、并行（Send API）、暂停/恢复（interrupt + Command + Checkpoint）。`Annotation` 和 `Reducer` 定义状态结构和合并规则，解决并行节点的状态冲突问题。<sup>\[2]</sup>"

> "选择标准：简单的 RAG/Chain 用 LangChain，复杂的多 Agent 用 LangGraph。LangGraph 的核心价值不是'图结构'本身，而是把 LLM 调用从'一次性请求'变成'有状态的工作流'——支持暂停/恢复（人机协作）、错误重试（从 checkpoint 恢复）、状态持久化（时间旅行）。<sup>\[3]</sup>"

### 常见追问

> [accordion] LangGraph 的 Send API 是什么?和 Promise.all 有什么区别?
>
> **答题要点：**
> 说出 Send 的动态并行能力和相比 Promise.all 的优势。
>
> **高分回答：**
> "Send 用于动态并行——在图的执行过程中动态创建多个并行节点。例如根据用户输入动态决定需要调用哪些工具，每个工具调用是一个 Send，并行执行后通过 Reducer 汇总结果。
>
> 和 Promise.all 的区别：1) 状态自动管理——Reducer 自动合并并行结果，不用手动传参；2) 条件分支——可以根据结果决定下一步；3) 可中断——支持 interrupt 等待人类审批；4) 错误处理——节点失败可以走备选路径；5) 可视化——LangGraph Studio 可以看到图的执行过程。"

> [accordion] RAG 的切片策略怎么选?
>
> **答题要点：**
> 从语义完整性、检索精确性、性能三个维度分析。
>
> **高分回答：**
> "切片策略的选择取决于文档类型和检索需求：
>
> 1. **按字符切**（RecursiveCharacterTextSplitter）：最通用，先按段落切，再按句子切，最后按字符切。chunkSize 推荐 500-1500 字符，overlap 10-20%。
> 2. **按语义切**（SemanticChunker）：用 Embedding 计算相邻句子的相似度，在相似度低的地方切分。效果最好，但最慢（需要调用 Embedding API）。
> 3. **按段落切**：适合 Markdown 文档，保留文档结构。
>
> chunkSize 的选择：太小（100 字符）语义不完整，太大（5000 字符）包含太多无关信息。推荐 500-1500 字符。关键原则：每个切片应该是一个完整的语义单元，能独立回答一个问题。"

> [accordion] Function Calling 的参数是怎么生成的?
>
> **答题要点：**
> 说出 LLM 的推理能力和 schema 的关系。
>
> **高分回答：**
> "LLM 在推理时，根据用户的问题和工具的 schema（JSON Schema），生成符合 schema 的参数。这不是模板匹配，而是 LLM 的推理能力——它理解了工具的描述和用户意图后，自主决定调用什么工具、传什么参数。
>
> 例如，用户说'北京天气怎么样'，工具 schema 定义了 `get_weather(city: string)`。LLM 理解了'北京'是城市名，生成 `{ city: '北京' }`。关键：schema 的 `description` 写得越精确，LLM 的参数生成越准确。"

> [accordion] LangChain 的 Chain 和 Agent 该怎么选?
>
> **答题要点：**
> 从可预测性、灵活性、调试难度三个维度对比。
>
> **高分回答：**
> "Chain 和 Agent 的核心区别是决策权——Chain 由开发者决定流程，Agent 由 LLM 决定流程。
>
> 选择标准：
>
> 1. **用 Chain**：流程固定、步骤明确、不需要动态决策。例如 RAG（检索 → 增强 → 生成）、数据提取（输入 → 解析 → 格式化）。Chain 的行为可预测，容易调试。
> 2. **用 Agent**：流程不确定、需要根据中间结果动态决策。例如多工具协作（搜索 → 分析 → 决定是否再搜索）、复杂推理（需要多步思考）。Agent 更灵活但也更不可控。
>
> 实际项目中，80% 的场景用 Chain 就够了。只有真正需要 LLM 自主决策的场景才用 Agent。过度使用 Agent 会导致行为不可预测、调试困难、成本更高（Agent 可能循环调用工具多次）。"

> [accordion] RAG 的检索质量怎么优化?
>
> **答题要点：**
> 从切片策略、Embedding 模型、检索策略三个维度回答。
>
> **高分回答：**
> "RAG 的质量瓶颈在检索，优化有三个维度：
>
> 1. **切片策略**：按语义切 > 按段落切 > 按字符切。推荐 RecursiveCharacterTextSplitter，chunkSize 500-1500 字符，overlap 10-20%。关键是每个切片应该是一个完整的语义单元，能独立回答一个问题。
>
> 2. **Embedding 模型**：OpenAI 的 text-embedding-3-large 效果最好但成本高，开源模型（如 BGE、E5）性价比更高。选择时看 MTEB 排行榜。
>
> 3. **检索策略**：纯向量检索（语义相似）+ BM25（关键词匹配）的混合检索效果最好。LangChain 的 `EnsembleRetriever` 可以组合两种检索器。还可以加 Reranker（重排序）——先粗检索 20 个候选，再用 Cross-Encoder 精排取 Top 3。"

> [accordion] Memory 在 LangChain 中有哪些类型?
>
> **答题要点：**
> 区分短期记忆和长期记忆，说出各自的实现方式。
>
> **高分回答：**
> "LangChain 的 Memory 分为短期记忆和长期记忆：
>
> 1. **短期记忆（对话历史）**：BufferMemory 保存完整对话历史，BufferWindowMemory 只保留最近 N 轮。适合聊天场景。问题是上下文窗口有限，对话太长会溢出。
>
> 2. **摘要记忆**：ConversationSummaryMemory 用 LLM 把对话历史压缩成摘要。适合长对话——保留关键信息，丢弃细节。
>
> 3. **长期记忆（向量数据库）**：VectorStoreRetrieverMemory 把对话存入向量数据库，需要时检索相关历史。适合需要记住大量历史信息的场景（如个人助手）。
>
> 实际项目中，短期记忆用 BufferWindowMemory（保留最近 10 轮），长期记忆用向量数据库（存储重要事实）。"

> [accordion] LangGraph 的 Checkpoint 机制有什么用?
>
> **答题要点：**
> 说出 Checkpoint 支持的三个核心能力。
>
> **高分回答：**
> "Checkpoint 在每个节点执行后保存状态快照，支持三个核心能力：
>
> 1. **暂停/恢复**：interrupt + Command 实现人机协作。图执行到 interrupt() 时保存 checkpoint，暂停执行。人类审批后，从 checkpoint 恢复继续执行。
>
> 2. **错误重试**：节点失败后，从最近成功的 checkpoint 恢复，而不是从头开始。LangGraph 的 `retry_policy` 可以配置重试次数和退避策略。
>
> 3. **时间旅行**：可以回到任意 checkpoint 查看历史状态，重放执行过程。LangGraph Studio 支持可视化查看每个 checkpoint 的状态和图的执行流。
>
> Checkpoint 的存储后端可以是内存（MemorySaver，开发用）、SQLite、PostgreSQL。生产环境推荐 PostgreSQL，支持持久化和多实例共享。"

> [accordion] 🔥 RAG 和 Fine-tuning 该怎么选?
>
> **答题要点：**
> 从知识更新频率、成本、幻觉控制三个维度对比。
>
> **高分回答：**
> "RAG 和 Fine-tuning 的选择取决于三个维度：
>
> 1. **知识更新频率**：如果知识经常变化（如产品文档、API 文档），选 RAG——换文档就行，不需要重新训练。如果知识相对稳定（如行业术语、写作风格），Fine-tuning 更合适。
>
> 2. **成本**：RAG 的成本低（只需向量数据库 + Embedding API），Fine-tuning 的成本高（需要 GPU、训练数据、标注成本）。
>
> 3. **幻觉控制**：RAG 天然控制幻觉——AI 只能基于检索到的文档回答。Fine-tuning 仍然可能幻觉（模型可能'记住'错误的信息）。
>
> 实际项目中，80% 的场景用 RAG 就够了。Fine-tuning 适合两种场景：1) 风格迁移（让 AI 用特定的语气写作）；2) 特定格式输出（让 AI 输出特定结构的数据，如 SQL、正则表达式）。两者也可以组合使用——Fine-tuning 让模型理解领域术语，RAG 提供最新的知识。"

> [accordion] 🔥 Reranking 的原理是什么?为什么需要两阶段检索?
>
> **答题要点：**
> 说出 Bi-Encoder 和 Cross-Encoder 的区别，以及两阶段检索架构的设计动机。
>
> **高分回答：**
> "Reranking 的核心是两阶段检索架构：第一阶段用 Bi-Encoder 粗筛（快但不精确），第二阶段用 Cross-Encoder 精排（慢但精确）。
>
> Bi-Encoder 的工作方式：问题和文档分别编码为向量，再计算余弦相似度。这种方式速度快（向量可以预计算），但'分别编码'会丢失问题和文档之间的细粒度交互信息。
>
> Cross-Encoder 的工作方式：把问题和文档拼接在一起，输入同一个模型计算相关性分数。这种方式能捕获更细粒度的语义交互（比如问题中的否定词和文档中的肯定词的矛盾），但速度慢（每对都要单独计算，无法预计算）。
>
> 实际架构：向量检索 Top-20（Bi-Encoder，毫秒级）→ Reranker 精排取 Top-3（Cross-Encoder，百毫秒级）。LangChain 的 `CohereRerank` 或 `ContextualCompressionRetriever` 可以实现这个流程。"

> [accordion] 如何搭建一个生产级的 RAG 系统?
>
> **答题要点：**
> 从架构设计、向量数据库选型、质量优化三个维度回答。
>
> **高分回答：**
> "生产级 RAG 的架构设计需要考虑 5 个层面：
>
> 1. **文档层**：支持多种格式（PDF、Word、网页、数据库），用 Unstructured 或 LlamaParse 解析。关键是处理好表格、图片、代码块等复杂格式。
>
> 2. **切片层**：推荐递归切片（chunkSize 500-1500，overlap 10-20%）。Markdown 文档用 MarkdownHeaderTextSplitter 保留结构。高质量场景用 SemanticChunker。
>
> 3. **存储层**：小项目用 Chroma（零配置），中等项目用 Pinecone（全托管），大规模生产用 Milvus 或 Qdrant（高性能）。已有 PostgreSQL 的团队用 pgvector。
>
> 4. **检索层**：混合检索（向量 + BM25，权重 0.6:0.4）+ Reranker 精排。加入查询改写（Query Rewriting）和多路召回（Multi-Query Retrieval）。
>
> 5. **评估层**：用 RAGAS 框架评估忠实度（Faithfulness）、相关性（Relevancy）、准确性（Accuracy）。建立自动化评估流水线，每次优化后回归测试。"

> [accordion] LangChain 的 Chain 类型该怎么选?
>
> **答题要点：**
> 从文档量、上下文窗口、成本三个维度对比 Stuff、MapReduce、Refine、Map-Rerank。
>
> **高分回答：**
> "Chain 类型的选择取决于文档量和上下文窗口的限制：
>
> 1. **Stuff**：把所有文档拼接成一个 Prompt。最简单，一次 LLM 调用。但文档太多会超出上下文窗口。适合文档少（\< 5 个）的场景。
>
> 2. **MapReduce**：每个文档单独生成摘要（Map），再合并摘要生成最终答案（Reduce）。支持大量文档，但需要多次 LLM 调用，成本高。适合需要全局视角的场景。
>
> 3. **Refine**：先用第一个文档生成答案，再用后续文档逐个修正。答案逐步精炼，但后续文档的影响递减。适合需要逐步深入的场景。
>
> 4. **Map-Rerank**：每个文档单独评分，选最高分的答案。忽略无关文档，但可能丢失跨文档信息。适合有明确答案的场景。
>
> 实际项目中，90% 的场景用 Stuff 就够了（限制检索数量为 3-5 个）。只有文档量特别大时才用 MapReduce。"

> [accordion] LangGraph 的 Human-in-the-Loop 怎么实现?
>
> **答题要点：**
> 说出 interrupt + Command + Checkpoint 三者的协作关系。
>
> **高分回答：**
> "LangGraph 的人机协作通过三个组件实现：interrupt（暂停）、Command（恢复）、Checkpoint（状态持久化）。
>
> 流程：1) 节点执行到 `interrupt(value)` 时，把当前状态保存到 Checkpoint，抛出 GraphInterrupt 异常暂停图执行；2) 外部代码拿到 value（如审批请求），展示给人类；3) 人类审批后，调用 `Command(resume=reply)` 恢复执行；4) 图从 Checkpoint 恢复状态，`interrupt()` 返回人类的回复。
>
> 关键设计：Checkpoint 是基础——没有 Checkpoint，就无法暂停和恢复。Checkpoint 的存储后端可以是 MemorySaver（开发用）、SQLite、PostgreSQL（生产用）。
>
> 实际应用场景：代码部署审批（Agent 生成部署方案 → interrupt 暂停 → 人类审批 → 继续执行）、内容审核（Agent 生成文章 → interrupt 暂停 → 人类审核 → 发布或修改）。"

> [accordion] RAG 和 Skills 的区别是什么?什么时候用哪个?
>
> **答题要点：**
> 从"知识"和"能力"两个维度区分，结合实际场景说明。
>
> **高分回答：**
> "RAG 和 Skills 解决的是完全不同的问题：
>
> RAG 解决的是'Agent 不知道'的问题——知识注入。典型场景：公司制度问答、产品文档查询、技术知识库。数据流：知识库 → 检索 → LLM 上下文 → 生成答案。
>
> Skills 解决的是'Agent 做不到'的问题——能力扩展。典型场景：查天气、发邮件、操作数据库、调用第三方 API。数据流：LLM 推理 → 决策调用什么工具 → 执行工具 → 基于结果回答。
>
> 实际项目中，企业级 Agent 通常两者兼备。例如：HR 助手需要 RAG（公司制度知识库）+ Skills（查剩余年假、提交请假申请）。选择标准：如果问题是'是什么'（知识类），用 RAG；如果问题是'做什么'（操作类），用 Skills；如果两者都需要，组合使用。"

> [accordion] 🔥 HyDE 是什么?为什么假设性答案能提升检索效果?
>
> **答题要点：**
> 说出 HyDE 的核心思想和适用场景。
>
> **高分回答：**
> "HyDE（Hypothetical Document Embeddings）的核心思想：让 LLM 先生成一个'假设性答案'，再用这个答案的向量去检索。假设性答案虽然可能不准确，但它的语义和真实答案更接近，所以检索效果更好。
>
> 原理：用户问题通常是短句（如'怎么优化首屏'），而真实文档通常是长段落（如'首屏优化策略包括：1. 减少 HTTP 请求...2. 使用 CDN...'）。短句和长段落的向量表示差异较大，直接用问题向量检索可能匹配不到。但假设性答案是 LLM 生成的长段落，和真实文档的向量表示更接近。
>
> 适用场景：用户问题模糊、口语化、或和文档的表述差异大的场景。不适用场景：用户问题已经很精确（和文档表述一致），或 LLM 对该领域知识不足（生成的假设性答案质量差）。
>
> 实现：1) LLM 生成假设性答案；2) 用假设性答案的向量检索；3) 用检索到的真实文档作为上下文，LLM 生成最终答案。"

***

## 📚 延伸阅读与引用参考

* 🔗 [LangChain 官方文档](https://js.langchain.com/docs/)：*LangChain 的完整文档，包含 Runnable、Chain、Tool 的 API 说明。* <sup>\[1]</sup>
* 📖 [LangGraph 官方文档](https://langchain-ai.github.io/langgraphjs/)：*LangGraph 的完整文档，包含 StateGraph、Send、interrupt 的详细说明。* <sup>\[2]</sup>
* 🟢 [LangGraph 源码](https://github.com/langchain-ai/langgraph)：*LangGraph 的完整源码，理解 Checkpoint、interrupt、Reducer 的底层实现。* <sup>\[3]</sup>
* 📖 [RAG 论文原文](https://arxiv.org/abs/2005.11401)：*Meta AI 的 RAG 原始论文，理解检索增强生成的理论基础。* <sup>\[4]</sup>
* 📖 [Lilian Weng - LLM Powered Autonomous Agents](https://lilianweng.github.io/posts/2023-06-23-agent/)：*Lilian Weng 对 LLM 驱动的 Agent 体系的系统梳理，包含规划、记忆、工具使用等环节的理论分析。* <sup>\[5]</sup>
* 🔗 [MTEB Leaderboard](https://huggingface.co/spaces/mteb/leaderboard)：*Embedding 模型的权威评测排行榜，选型时的参考标准。* <sup>\[6]</sup>
* 🔗 [LangChain RAG 教程](https://js.langchain.com/docs/tutorials/rag/)：*LangChain 官方的 RAG 实战教程，包含从文档加载到检索生成的完整流程。* <sup>\[7]</sup>
* 🔗 [LangGraph Human-in-the-Loop](https://langchain-ai.github.io/langgraphjs/concepts/human_in_the_loop/)：*LangGraph 人机协作的官方文档，理解 interrupt + Command + Checkpoint 的协作机制。* <sup>\[8]</sup>
* 📖 [Harrison Chase Blog](https://blog.langchain.dev/)：*LangChain 创始人 Harrison Chase 的博客，包含 RAG 架构设计、Agent 编排等前沿实践。* <sup>\[9]</sup>
* 🔗 [Vercel AI SDK 官方文档](https://sdk.vercel.ai/docs)：*Vercel AI SDK 的完整文档，包含 useChat、useCompletion、useObject 的 API 说明和示例。* <sup>\[10]</sup>
* 🔗 [Vercel AI SDK GitHub](https://github.com/vercel/ai)：*Vercel AI SDK 的源码仓库，理解流式响应、Provider 抽象、工具调用的底层实现。* <sup>\[11]</sup>
* 📖 [Guillermo Rauch Blog](https://rauchg.com/)：*Vercel CEO Guillermo Rauch 的博客，包含 AI 前端架构、流式 UI 设计等前沿思考。* <sup>\[12]</sup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.