SmartWriter v0.3:带研究的写作 — 文档加载与基础 RAG 检索链实战前言核心痛点:v0.2 的 SmartWriter 已经能生成结构化的文章大纲和正文,但它有一个致命缺陷——所有知识完全来自 LLM 的训练数据。当你需要写一篇关于「2026 年最新 React Server Components 最佳实践」的文章时,模型训练数据截止日期之前的旧知识无法满足需求,而模型幻觉又可能编造出不存在的 API 和概念。本文解决的核心问题:如何让 SmartWriter 具备研究能力,从外部文档中检索相关知识,写出有据可依的文章。前置知识:需要掌握 LangChain 基础(ChatModel、PromptTemplate、Chain)、Python 基础编程能力、理解 LLM 的基本工作原理。如果你已经完成了 v0.1 和 v0.2 的实战,可以直接开始。系列阶段:入门篇第 3 篇(共 4 篇入门,全系列共 24 篇)收获能力:读完本文你将掌握 RAG(检索增强生成)的完整底层原理、文档加载→文本分割→向量嵌入→向量存储→相似检索→上下文注入全链路实战能力,能够独立搭建一个可用的带研究功能的写作系统。目录技术背景与演进逻辑核心原理深度解析文档加载器体系文本分割策略Embedding 模型与向量空间VectorStore 向量数据库实战基础 RAG Chain 完整构建检索结果注入与 Prompt 策略技术优缺点与适用场景实战落地:SmartWriter v0.3 完整实现全文总结本期专栏更新说明参考资料技术背景与演进逻辑LLM 的两大原生局限要理解 RAG 为什么诞生,需要先理解大语言模型的两个根本性限制。第一,知识截止日期(Knowledge Cutoff)。任何 LLM 的训练数据都有截止时间。即使是最新发布的模型,其训练数据也至少落后于现实世界几个月。当你问模型「最新的 Python 版本引入了什么特性」,它可能回答 Python 3.12 的内容,而实际上 Python 3.14 甚至 3.15 已经发布。这种信息滞后在生产场景中是不可接受的——一份技术文档如果引用已废弃的 API,不仅无益,反而有害。第二,有限上下文窗口(Finite Context Window)。虽然现代模型已经支持 128K 甚至 1M Token 的上下文,但这并不意味着你可以直接把整个知识库塞进 Prompt。且不说成本问题——每次请求都把整本技术手册附在 Prompt 里,Token 消耗会高到无法承受——更重要的是,过长的上下文会导致模型注意力稀释(Attention Dilution),对中间段落的信息提取能力显著下降。这就是著名的「Lost in the Middle」现象。从 Fine-tuning 到 RAG:知识注入的范式转移在 RAG 流行之前,让模型习得特定领域知识的首选方案是微调(Fine-tuning):传统方案:Fine-tuning ├── 收集领域数据 → 标注 → 微调训练 → 部署模型 │ 优点:推理时无额外延迟 │ 缺点: │ ├── 训练成本高(GPU 算力、时间) │ ├── 知识更新需要重新训练 │ ├── 无法追溯到原始文档来源 │ └── 可能导致灾难性遗忘RAG 用完全不同的思路解决同一问题:RAG 方案 ├── 检索阶段(运行时) │ ├── 用户提出问题 │ ├── 从知识库中检索相关文档片段 │ └── 将检索结果注入 Prompt ├── 生成阶段 │ └── LLM 基于检索到的上下文生成回答 │ 优点: │ ├── 知识可随时更新(只需更新向量库) │ ├── 可追溯来源(每个回答都能引用原文) │ ├── 无需训练(零微调成本) │ └── 避免灾难性遗忘SmartWriter 为什么需要研究能力回到我们的 SmartWriter 产品。v0.2 的写作流程是:用户输入主题 → Prompt 模板 → LLM 直接生成 → 输出解析 → 文章这个流程的瓶颈显而易见:LLM 只能基于训练数据「回忆」信息,无法「查找」新信息。SmartWriter v0.3 引入研究能力后,流程变为:用户输入主题 → 检索知识库 → 获取相关文档 → 注入 Prompt → LLM 基于素材写作 → 文章一个具体的例子:用户要求 SmartWriter 写一篇关于「2026 年 AI Agent 协议标准化进展」的文章。v0.2 只能依靠模型训练数据中的知识(可能是 2024 年甚至更早的信息),而 v0.3 会先从本地知识库(已导入的 arXiv 论文、技术博客、官方文档)中检索相关资料,再基于最新素材组织文章。这样的文章不仅时效性强,而且有据可查。RAG 技术演进简史阶段时间核心技术代表实现萌芽期2020-2021Dense Passage Retrieval (DPR)、朴素检索-阅读架构RAG 原始论文(Lewis et al.)工程化2022-2023文档分块策略、多种向量库、ReRank 重排序LangChain、LlamaIndex成熟期2024-2025多路混合检索、Self-RAG、CRAG 自纠错LangChain/LlamaIndex 生态智能体化2025-2026Agentic RAG、多步推理检索、GraphRAGLangGraph Agent + MCP 工具本文聚焦于工程化阶段的核心技术栈——也就是构建一个可靠、可维护的基础 RAG 系统所需的所有组件。这是后续进阶篇中多跳检索、Agentic RAG、GraphRAG 等高级技术的基石。核心原理深度解析RAG 的数学本质:从概率视角理解检索增强从概率论的角度,标准 LLM 生成是在最大化条件概率:P ( m a t h r m a n s w e r m i d m a t h r m q u e r y ) P(mathrm{answer} mid mathrm{query})P(mathrmanswermidmathrmquery)而 RAG 引入了一个隐变量——检索到的文档集合 D——将生成过程分解为两个阶段:P ( m a t h r m a n s w e r m i d m a t h r m q u e r y ) = s u m D P ( m a t h r m a n s w e r m i d m a t h r m q u e r y , D ) c d o t P ( D m i d m a t h r m q u e r y ) P(mathrm{answer} mid mathrm{query}) = sum_{D} P(mathrm{answer} mid mathrm{query}, D) cdot P(D mid mathrm{query})P(mathrmanswermidmathrmquery)=sumDP(mathrmanswermidmathrmquery,D)cdotP(Dmidmathrmquery)其中:P(D | query)是检索阶段:给定查询,找到最相关的文档集合P(answer | query, D)是生成阶段:给定查询和相关文档,生成答案在实践中,我们不计算所有可能的 D 的求和,而是用近似方法选择 top-K 最相关的文档:D ∗ = a r g m a x D m a t h r m s i m ( m a t h r m e m b e d ( q ) , m a t h r m e m b e d ( d ) ) D^* = argmax_{D} mathrm{sim}(mathrm{embed}(q), mathrm{embed}(d))D∗=argmaxDmathrmsim(mathrmembed(q),mathrmembed(d))然后条件化生成:m a t h r m a n s w e r = m a t h r m L L M ( m a t h r m q u e r y , D ∗ ) mathrm{answer} = mathrm{LLM}(mathrm{query}, D^*)mathrmanswer=mathrmLLM(mathrmquery,D∗)这个公式虽然简化,但揭示了 RAG 的核心设计空间:检索质量决定了 P(D|query) 的准确度——如果检索不到相关文档,再好的 LLM 也无能为力上下文融合决定了 P(answer|query, D) 的质量——如何将检索结果有效地注入 PromptRAG 五阶段流水线RAG 的工程实现包含五个独立但紧密耦合的阶段,每个阶段都有可替换的组件:文档加载 → 文本分割 → 向量嵌入 → 向量存储 → 相似检索 → 上下文注入 → 生成 ↑ │ └──────────────── 离线索引阶段 ──────────────────────┘ │ 在线查询阶段 ──────────────────────┘离线索引阶段(做一次,定期更新):文档加载:从各种来源读取原始文档文本分割:将长文档切分为适当大小的 Chunk向量嵌入:将每个 Chunk 转换为向量表示向量存储:将向量持久化到向量数据库在线查询阶段(每次请求执行):5. 相似检索:将用户查询向量化,检索最相似的 Chunk6. 上下文注入:将检索结果填入 Prompt 模板7. 生成:LLM 基于增强后的 Prompt 生成输出向量嵌入与语义相似度向量嵌入是 RAG 能够工作的数学基础。嵌入模型将文本映射到一个高维向量空间(通常是 768、1024 或 1536 维),在这个空间中,语义相似的文本距离更近。两个文本的相似度通常用余弦相似度度量:m a t h r m c o s s i m ( a , b ) = d f r a c a c d o t b ∣ ∣ a ∣ ∣ c d o t ∣ ∣ b ∣ ∣ = d f r a c s u m i = 1 n a i b i s q r t s u m i = 1 n a i 2 c d o t s q r t s u m i = 1 n b i 2 mathrm{cos_sim}(a, b) = dfrac{a cdot b}{||a|| cdot ||b||} = dfrac{sum_{i=1}^{n} a_i b_i}{sqrt{sum_{i=1}^{n} a_i^2} cdot sqrt{sum_{i=1}^{n} b_i^2}}mathrmcossim(a,b)=dfracacdotb∣∣a∣∣cdot∣∣b∣∣=dfracsumi=1naibisqrtsumi=1nai2cdotsqrtsumi=1nbi2余弦相似度的取值范围是 [-1, 1],其中 1 表示完全相同方向(语义高度相似),0 表示正交(无关),-1 表示完全相反。在实际的嵌入模型中,由于向量各维度都是非负值,相似度一般在 [0, 1] 范围内。检索质量的核心矛盾RAG 系统面临一个核心矛盾:chunk_size 的 Goldilocks 问题。Chunk 太大(如 2000 Token):包含更多上下文,但检索精度下降——一个 Chunk 中可能包含多个主题,导致检索到的内容只有一小部分真正相关Chunk 太小(如 100 Token):检索更精准,但缺乏足够上下文——模型看到的内容太碎片化,无法理解完整语义这个矛盾的解决方案不是单一的,而是一个组合策略:适中的 Chunk 大小 + 重叠窗口 + 重排序。我们在后面的文本分割策略中会详细展开。文档加载器体系LangChain Document 对象在深入各种加载器之前,需要理解所有加载器共同的「输出协议」:Document对象。fromlangchain_core.documentsimportDocument# Document 是最核心的数据抽象doc=Document(page_content="文档的正文内容...",metadata={"source":"data/report.pdf","page":3,"author":"Zhang Wei",})Document只有两个字段:page_content(str):文档的文本内容,这是后续分割和嵌入的输入metadata(dict):文档的元信息,包括来源、页码、作者、日期等。Metadata 在检索结果的溯源和过滤中至关重要这个设计的精髓在于统一接口。无论你的数据来自 PDF、网页、数据库还是 Slack 消息,最终都转换为统一的Document对象,后续的文本分割器、嵌入模型、向量库都不需要关心数据来源。文档加载器全景图LangChain 内置了 200+ 种文档加载器,覆盖了几乎所有常见的数据来源。对于 SmartWriter 写作助手来说,最核心的场景是:SmartWriter 研究素材来源 ├── 本地文件 │ ├── TextLoader — 纯文本文件 (.txt) │ ├── PyPDFLoader — PDF 文件 (.pdf) │ ├── UnstructuredMarkdownLoader — Markdown 文件 (.md) │ └── Docx2txtLoader — Word 文档 (.docx) ├── 网页内容 │ ├── WebBaseLoader — 通用网页抓取 │ ├── RecursiveUrlLoader — 递归抓取网站 │ └── FireCrawlLoader — 专业网页抓取(处理 JS 渲染) ├── 批量加载 │ ├── DirectoryLoader — 加载整个目录 │ └── CSVLoader / JSONLoader — 结构化数据核心加载器实战TextLoader — 最基础的加载器fromlangchain_community.document_loadersimportTextLoader# 加载单个文本文件loader=TextLoader("research_notes.txt",encoding="utf-8")docs=loader.load
SmartWriter v0.3:带研究的写作 — 文档加载与基础 RAG 检索链实战
发布时间:2026/6/13 4:39:03
SmartWriter v0.3:带研究的写作 — 文档加载与基础 RAG 检索链实战前言核心痛点:v0.2 的 SmartWriter 已经能生成结构化的文章大纲和正文,但它有一个致命缺陷——所有知识完全来自 LLM 的训练数据。当你需要写一篇关于「2026 年最新 React Server Components 最佳实践」的文章时,模型训练数据截止日期之前的旧知识无法满足需求,而模型幻觉又可能编造出不存在的 API 和概念。本文解决的核心问题:如何让 SmartWriter 具备研究能力,从外部文档中检索相关知识,写出有据可依的文章。前置知识:需要掌握 LangChain 基础(ChatModel、PromptTemplate、Chain)、Python 基础编程能力、理解 LLM 的基本工作原理。如果你已经完成了 v0.1 和 v0.2 的实战,可以直接开始。系列阶段:入门篇第 3 篇(共 4 篇入门,全系列共 24 篇)收获能力:读完本文你将掌握 RAG(检索增强生成)的完整底层原理、文档加载→文本分割→向量嵌入→向量存储→相似检索→上下文注入全链路实战能力,能够独立搭建一个可用的带研究功能的写作系统。目录技术背景与演进逻辑核心原理深度解析文档加载器体系文本分割策略Embedding 模型与向量空间VectorStore 向量数据库实战基础 RAG Chain 完整构建检索结果注入与 Prompt 策略技术优缺点与适用场景实战落地:SmartWriter v0.3 完整实现全文总结本期专栏更新说明参考资料技术背景与演进逻辑LLM 的两大原生局限要理解 RAG 为什么诞生,需要先理解大语言模型的两个根本性限制。第一,知识截止日期(Knowledge Cutoff)。任何 LLM 的训练数据都有截止时间。即使是最新发布的模型,其训练数据也至少落后于现实世界几个月。当你问模型「最新的 Python 版本引入了什么特性」,它可能回答 Python 3.12 的内容,而实际上 Python 3.14 甚至 3.15 已经发布。这种信息滞后在生产场景中是不可接受的——一份技术文档如果引用已废弃的 API,不仅无益,反而有害。第二,有限上下文窗口(Finite Context Window)。虽然现代模型已经支持 128K 甚至 1M Token 的上下文,但这并不意味着你可以直接把整个知识库塞进 Prompt。且不说成本问题——每次请求都把整本技术手册附在 Prompt 里,Token 消耗会高到无法承受——更重要的是,过长的上下文会导致模型注意力稀释(Attention Dilution),对中间段落的信息提取能力显著下降。这就是著名的「Lost in the Middle」现象。从 Fine-tuning 到 RAG:知识注入的范式转移在 RAG 流行之前,让模型习得特定领域知识的首选方案是微调(Fine-tuning):传统方案:Fine-tuning ├── 收集领域数据 → 标注 → 微调训练 → 部署模型 │ 优点:推理时无额外延迟 │ 缺点: │ ├── 训练成本高(GPU 算力、时间) │ ├── 知识更新需要重新训练 │ ├── 无法追溯到原始文档来源 │ └── 可能导致灾难性遗忘RAG 用完全不同的思路解决同一问题:RAG 方案 ├── 检索阶段(运行时) │ ├── 用户提出问题 │ ├── 从知识库中检索相关文档片段 │ └── 将检索结果注入 Prompt ├── 生成阶段 │ └── LLM 基于检索到的上下文生成回答 │ 优点: │ ├── 知识可随时更新(只需更新向量库) │ ├── 可追溯来源(每个回答都能引用原文) │ ├── 无需训练(零微调成本) │ └── 避免灾难性遗忘SmartWriter 为什么需要研究能力回到我们的 SmartWriter 产品。v0.2 的写作流程是:用户输入主题 → Prompt 模板 → LLM 直接生成 → 输出解析 → 文章这个流程的瓶颈显而易见:LLM 只能基于训练数据「回忆」信息,无法「查找」新信息。SmartWriter v0.3 引入研究能力后,流程变为:用户输入主题 → 检索知识库 → 获取相关文档 → 注入 Prompt → LLM 基于素材写作 → 文章一个具体的例子:用户要求 SmartWriter 写一篇关于「2026 年 AI Agent 协议标准化进展」的文章。v0.2 只能依靠模型训练数据中的知识(可能是 2024 年甚至更早的信息),而 v0.3 会先从本地知识库(已导入的 arXiv 论文、技术博客、官方文档)中检索相关资料,再基于最新素材组织文章。这样的文章不仅时效性强,而且有据可查。RAG 技术演进简史阶段时间核心技术代表实现萌芽期2020-2021Dense Passage Retrieval (DPR)、朴素检索-阅读架构RAG 原始论文(Lewis et al.)工程化2022-2023文档分块策略、多种向量库、ReRank 重排序LangChain、LlamaIndex成熟期2024-2025多路混合检索、Self-RAG、CRAG 自纠错LangChain/LlamaIndex 生态智能体化2025-2026Agentic RAG、多步推理检索、GraphRAGLangGraph Agent + MCP 工具本文聚焦于工程化阶段的核心技术栈——也就是构建一个可靠、可维护的基础 RAG 系统所需的所有组件。这是后续进阶篇中多跳检索、Agentic RAG、GraphRAG 等高级技术的基石。核心原理深度解析RAG 的数学本质:从概率视角理解检索增强从概率论的角度,标准 LLM 生成是在最大化条件概率:P ( m a t h r m a n s w e r m i d m a t h r m q u e r y ) P(mathrm{answer} mid mathrm{query})P(mathrmanswermidmathrmquery)而 RAG 引入了一个隐变量——检索到的文档集合 D——将生成过程分解为两个阶段:P ( m a t h r m a n s w e r m i d m a t h r m q u e r y ) = s u m D P ( m a t h r m a n s w e r m i d m a t h r m q u e r y , D ) c d o t P ( D m i d m a t h r m q u e r y ) P(mathrm{answer} mid mathrm{query}) = sum_{D} P(mathrm{answer} mid mathrm{query}, D) cdot P(D mid mathrm{query})P(mathrmanswermidmathrmquery)=sumDP(mathrmanswermidmathrmquery,D)cdotP(Dmidmathrmquery)其中:P(D | query)是检索阶段:给定查询,找到最相关的文档集合P(answer | query, D)是生成阶段:给定查询和相关文档,生成答案在实践中,我们不计算所有可能的 D 的求和,而是用近似方法选择 top-K 最相关的文档:D ∗ = a r g m a x D m a t h r m s i m ( m a t h r m e m b e d ( q ) , m a t h r m e m b e d ( d ) ) D^* = argmax_{D} mathrm{sim}(mathrm{embed}(q), mathrm{embed}(d))D∗=argmaxDmathrmsim(mathrmembed(q),mathrmembed(d))然后条件化生成:m a t h r m a n s w e r = m a t h r m L L M ( m a t h r m q u e r y , D ∗ ) mathrm{answer} = mathrm{LLM}(mathrm{query}, D^*)mathrmanswer=mathrmLLM(mathrmquery,D∗)这个公式虽然简化,但揭示了 RAG 的核心设计空间:检索质量决定了 P(D|query) 的准确度——如果检索不到相关文档,再好的 LLM 也无能为力上下文融合决定了 P(answer|query, D) 的质量——如何将检索结果有效地注入 PromptRAG 五阶段流水线RAG 的工程实现包含五个独立但紧密耦合的阶段,每个阶段都有可替换的组件:文档加载 → 文本分割 → 向量嵌入 → 向量存储 → 相似检索 → 上下文注入 → 生成 ↑ │ └──────────────── 离线索引阶段 ──────────────────────┘ │ 在线查询阶段 ──────────────────────┘离线索引阶段(做一次,定期更新):文档加载:从各种来源读取原始文档文本分割:将长文档切分为适当大小的 Chunk向量嵌入:将每个 Chunk 转换为向量表示向量存储:将向量持久化到向量数据库在线查询阶段(每次请求执行):5. 相似检索:将用户查询向量化,检索最相似的 Chunk6. 上下文注入:将检索结果填入 Prompt 模板7. 生成:LLM 基于增强后的 Prompt 生成输出向量嵌入与语义相似度向量嵌入是 RAG 能够工作的数学基础。嵌入模型将文本映射到一个高维向量空间(通常是 768、1024 或 1536 维),在这个空间中,语义相似的文本距离更近。两个文本的相似度通常用余弦相似度度量:m a t h r m c o s s i m ( a , b ) = d f r a c a c d o t b ∣ ∣ a ∣ ∣ c d o t ∣ ∣ b ∣ ∣ = d f r a c s u m i = 1 n a i b i s q r t s u m i = 1 n a i 2 c d o t s q r t s u m i = 1 n b i 2 mathrm{cos_sim}(a, b) = dfrac{a cdot b}{||a|| cdot ||b||} = dfrac{sum_{i=1}^{n} a_i b_i}{sqrt{sum_{i=1}^{n} a_i^2} cdot sqrt{sum_{i=1}^{n} b_i^2}}mathrmcossim(a,b)=dfracacdotb∣∣a∣∣cdot∣∣b∣∣=dfracsumi=1naibisqrtsumi=1nai2cdotsqrtsumi=1nbi2余弦相似度的取值范围是 [-1, 1],其中 1 表示完全相同方向(语义高度相似),0 表示正交(无关),-1 表示完全相反。在实际的嵌入模型中,由于向量各维度都是非负值,相似度一般在 [0, 1] 范围内。检索质量的核心矛盾RAG 系统面临一个核心矛盾:chunk_size 的 Goldilocks 问题。Chunk 太大(如 2000 Token):包含更多上下文,但检索精度下降——一个 Chunk 中可能包含多个主题,导致检索到的内容只有一小部分真正相关Chunk 太小(如 100 Token):检索更精准,但缺乏足够上下文——模型看到的内容太碎片化,无法理解完整语义这个矛盾的解决方案不是单一的,而是一个组合策略:适中的 Chunk 大小 + 重叠窗口 + 重排序。我们在后面的文本分割策略中会详细展开。文档加载器体系LangChain Document 对象在深入各种加载器之前,需要理解所有加载器共同的「输出协议」:Document对象。fromlangchain_core.documentsimportDocument# Document 是最核心的数据抽象doc=Document(page_content="文档的正文内容...",metadata={"source":"data/report.pdf","page":3,"author":"Zhang Wei",})Document只有两个字段:page_content(str):文档的文本内容,这是后续分割和嵌入的输入metadata(dict):文档的元信息,包括来源、页码、作者、日期等。Metadata 在检索结果的溯源和过滤中至关重要这个设计的精髓在于统一接口。无论你的数据来自 PDF、网页、数据库还是 Slack 消息,最终都转换为统一的Document对象,后续的文本分割器、嵌入模型、向量库都不需要关心数据来源。文档加载器全景图LangChain 内置了 200+ 种文档加载器,覆盖了几乎所有常见的数据来源。对于 SmartWriter 写作助手来说,最核心的场景是:SmartWriter 研究素材来源 ├── 本地文件 │ ├── TextLoader — 纯文本文件 (.txt) │ ├── PyPDFLoader — PDF 文件 (.pdf) │ ├── UnstructuredMarkdownLoader — Markdown 文件 (.md) │ └── Docx2txtLoader — Word 文档 (.docx) ├── 网页内容 │ ├── WebBaseLoader — 通用网页抓取 │ ├── RecursiveUrlLoader — 递归抓取网站 │ └── FireCrawlLoader — 专业网页抓取(处理 JS 渲染) ├── 批量加载 │ ├── DirectoryLoader — 加载整个目录 │ └── CSVLoader / JSONLoader — 结构化数据核心加载器实战TextLoader — 最基础的加载器fromlangchain_community.document_loadersimportTextLoader# 加载单个文本文件loader=TextLoader("research_notes.txt",encoding="utf-8")docs=loader.load