
做RAG项目做得多了你会发现一个特别有意思的现象很多团队把精力全砸在向量模型选型、检索策略调优、提示词工程上结果上线之后检索效果一塌糊涂TopK召回的内容驴唇不对马嘴。排查来排查去问题往往不是出在检索环节而是出在最前面那道根本没人重视的工序——文档加载解析。这个阶段决定了你的知识库“看到”的是什么。解析得干净后面的切分、向量化、检索都是顺水推舟解析得稀碎后面再怎么调都是给垃圾喂数据。我见过太多项目栽在PDF表格乱掉、扫描件全篇乱码、多栏排版文字串行这类基础问题上最后不得不返工重做数据管线。所以今天这篇我就围绕RAG检索增强生成里的文档加载解析环节把方案选型、实操细节、踩坑经验一次讲透给正在搭知识库的朋友一份能直接抄作业的参考。这篇东西适合谁看一种是正在从零搭企业知识库的技术负责人需要搞清楚文档解析在整个RAG链路里的定位和分工另一种是已经开始写代码、但被各种格式的文档折磨得头秃的开发者。看完你至少能搞清楚三件事不同格式的文档分别用什么工具解、解析到什么程度才算合格、以及怎么用代码把一套可复用的加载解析管线搭起来。1. RAG项目的第一道坎文档加载解析在整条链路里的真实位置很多教程把RAG画成一条从左到右的流水线文档加载 - 文本切分 - 向量化 - 存储 - 检索 - 生成。画得挺清爽但真正动手的人才会意识到文档加载这一步远没有看上去那么轻描淡写。它是整个知识库的入口漏斗漏斗的口径开了多大、滤网细不细直接决定了后面所有环节的天花板。1.1 先理解RAG为什么“成也文档败也文档”RAG检索增强生成的核心逻辑说穿了就是“先查资料、再写答案”。大模型本身的知识截止时间和训练语料有限面对企业内部资料、私有文档、实时更新的内容时靠模型记忆根本不可行。所以我们要把文档切碎转成向量存进数据库用户提问时先检索出最相关的片段再把片段和问题一起交给大模型让模型基于这些片段作答。问题在于检索的质量强依赖切分的质量而切分的质量强依赖加载解析的质量。如果PDF里的表格被解析成一段毫无结构的纯文本序列“2023年营收 1280万 2022年 980万”这种信息就会被切得支离破碎检索时既匹配不到完整的业务语义喂给大模型之后它也无法理解对应关系。一句话总结RAG回答得准不准一半的命是文档解析给的。从我的实践经验来看文档解析在RAG项目里通常占据整个数据管线开发量的三成到四成。别觉得夸张那些看起来“很简单”的需求——比如“把我们公司几千个Word和PDF导进去就可以了”——一旦落到真实数据上你会遇到各种幺蛾子既有扫描版PDF、也有加密文件、还有排版极其花哨的市场报告。如果一开始没把解析这层做扎实后面返工的成本会指数级上升。1.2 文档解析并不只是“把文字抠出来”很多人对文档解析的理解是用工具把PDF或Word里的文字提取出来哪怕是乱七八糟的纯文本也行反正后面要做切分和向量化。这是新手最容易犯的错误。合格的文档解析至少要考虑三件事第一结构信息。文档不是字符的随机排列段落、标题、列表、表格这些结构本身就是语义的一部分。解析时把标题层级丢掉检索时就无法区分“概述”和“详细条款”的重要性把表格拍扁成一行行文字行列对应关系就全丢了。第二元数据。文件名、页码、章节路径、创建时间、作者信息等元数据在RAG系统里承担着很重要的溯源职责。用户问“这个数据是哪份报告里的”如果解析结果里没保留来源信息就无法做引用追溯。第三清洗策略。页眉页脚、水印、重复的导航文本、代码块里的空行这些噪声如果不清洗会被切分成大量无意义片段不仅浪费向量库空间还会在检索时引入干扰。所以在RAG项目里文档加载解析的正确姿势是“结构化提取 元数据保留 针对性清洗”而不是简单的文本抽取。1.3 动手之前先给文档分个类我拿到一批待入库文档后第一件事不是写加载代码而是先做文档盘点。把文档按格式、质量、用途分类再决定每种类型用什么策略去解。一般我会分成这几类数字化原生文档Word、Markdown、HTML、电子版PDF。这类文档内部有文本层解析相对容易重点在于结构保留和清洗。扫描件或图片型PDF本质是图片没有文本层必须走OCR。解析重点在OCR准确率以及版面还原。表格密集型文档年报、财报、统计报表。普通文本抽取会彻底破坏行列关系需要专门的表格解析方案。网页内容动态渲染页面需要无头浏览器抓取静态页面可以直接走HTML解析。分类的逻辑其实很简单不同文档的“物理构成”完全不同你不可能指望同一个解析函数通吃所有格式。先分类、再对症下药是文档加载解析效率最高的做法。2. 加载解析工具选型没有银弹只有最合适的组合文档解析工具链的花样非常多从底层PDF库到一站式解析框架各有各的适用场景。很多初学者上来直接装一个大而全的框架结果遇到具体格式问题反而不知道怎么处理。我自己的经验是先理解各工具的能力边界再按需组合。2.1 主流PDF解析库的能力对比PDF是RAG项目里最常见的文档格式所以先把PDF解析方案讲透。市面上常用的开源方案有PyMuPDF、pdfplumber、PyPDF2、PDFMiner等我整理了一个简单的对比表工具优点缺点适用场景PyMuPDFfitz速度快、解析文本质量高、支持渲染图片、可获取文字位置信息对复杂表格的结构识别能力弱大批量文本型PDF的快速提取pdfplumber对表格和坐标信息支持很好可提取表格线速度比PyMuPDF慢需要保留表格结构的PDFPyPDF2 / pypdf轻量、API简单解析复杂版面时效果一般简单文本PDF或PDF合并拆分PDFMiner对布局分析有一定支持使用繁琐、性能一般需要细粒度布局信息的场景我实际用下来日常项目的主力是PyMuPDF加pdfplumber的组合。PyMuPDF用来做快速的全文提取和大批量处理遇到表格关键文档再单独交给pdfplumber精提。这里有个选型逻辑工具不是越贵越好也不是功能越多越好而是要和你的文档特征对齐。如果你的知识库里全是扫描件那重点要选的是OCR方案而不是纠结PDF文本提取哪个更快。2.2 从底层库到一站式框架怎么选底层库解决的是“单个文件怎么解析”而框架层解决的是“一批文件怎么管理”。现在LangChain、LlamaIndex这类编排框架都内置了大量文档加载器比如LangChain的PyPDFLoader、PDFMinerLoader、UnstructuredPDFLoader等。这里我给出自己的选型判断如果你的项目需要快速验证RAG链路直接用LangChain或LlamaIndex的内置Loader是最高效的它们的封装能让你十几行代码就把一批文档加载成统一的Document对象。但如果你做的是企业级知识库文档格式复杂、质量参差不齐我不建议完全依赖框架内置Loader。原因很简单框架为了做到通用对单格式的解析深度往往不够。比如LangChain的PyPDFLoader底层就是pypdf对复杂表格几乎无能为力。我的做法是自建一套解析服务把底层库的解析逻辑做细再输出成框架能直接消费的格式。说到底层逻辑Unstructured是一个值得单独拎出来说的项目。它不是一个解析库而是一整套文档处理框架针对非结构化数据提供了各种分区器partitioners能自动识别文档类型并提取结构元素。它最强的点是能把PDF、Word、HTML统一抽象成“元素列表”——标题是一类元素、段落是一类元素、表格又是单独的一类元素——这对接RAG的切分策略非常友好。不过Unstructured的部署和依赖较重有些功能需要调用API或者安装一堆系统依赖小项目直接用会有点臃肿。还有一个选项是LlamaIndex的LlamaParse表格解析和复杂文档处理能力非常强但托管服务的形式意味着文档要传到云端对数据敏感的私有化项目要先评估合规风险。2.3 我的组合方案分层处理、按需调度基于上面这些工具的特性我推荐一套可落地的组合方案第一层格式识别。先根据扩展名或文件头判断文档类型决定走哪条解析路线。第二层文本型PDF用PyMuPDF做快速抽取表格密集的PDF交给pdfplumber扫描件交给OCR服务。第三层对复杂文档做版面分析和结构识别输出带坐标、带类型标记的元素列表。第四层把解析结果统一成标准格式带着元数据交给下游处理。这套方案的思路是“分层解耦按需调度”。每一层只负责一件事替换和升级都不至于影响整条链路。如果你后期发现OCR效果不行只需要替换第二层的扫描件处理模块其他模块完全不用动。3. 解析细节实操搞懂这几个核心问题你就超过八成从业者工具选好之后真正的硬骨头在细节。下面挑几个我在实际项目中反复踩、也反复被问的问题展开讲讲。3.1 PDF解析的三个层次文本、版面、语义PDF的复杂性在于它本质上是一种“版面描述语言”记录的是一堆图形对象的绘制指令不像Word那样天然存在段落和标题的语义结构。所以PDF解析必须分层次来谈。第一层是文本提取。最简单的方式是按页提取所有文字PyMuPDF的page.get_text()就能做到。但这样提取出来的文本没有段落边界顺序也可能和人类阅读顺序不一致。这个层次的解析结果“能看但不敢直接用”。第二层是版面分析。需要获取每个文本块的坐标信息然后通过排序、聚类去还原阅读顺序识别标题和正文的层级关系。PyMuPDF可以用page.get_text(dict)拿到每个文本块的bbox坐标再按坐标排序。这里有个很关键的经验PDF的坐标原点在页面左上角y轴向下排序时按“y坐标为主、x坐标为辅”的方式排基本能恢复阅读顺序。第三层是语义还原。这是最难也最值钱的一层。比如识别出一个文本块是表格标题、一组文本块构成了表格的某一行、某些文本是页脚需要跳过。这一层目前没有完美的开源方案实际工程中往往要结合规则和版面分析模型比如OCR类模型的版面输出来做。很多项目只做到第一层就流到下游了效果差真不怪检索怪解析根本没到位。3.2 表格解析RAG项目里最容易被低估的难点表格在RAG项目里是个尴尬的存在。文本切分器是按字符或语义切片的表格一旦被“拍扁”成线性文本行列对应关系瞬时土崩瓦解。比如产品 A 地区 华东 销售额 1280万这种文本切碎之后模型很可能把“产品A”和“销售额”对应错。解决表格问题有两种主流路线。路线一表格转Markdown。像pdfplumber这类工具能提取单元格坐标你可以在解析时重建表格结构转成Markdown格式输出| 产品 | 地区 | 销售额 | |------|------|--------| | A | 华东 | 1280万 |Markdown表格对LLM来说是一种非常容易理解的结构化表达切分时只要控制好单元格数量检索质量会大幅提升。路线二表格单独存储。把表格从文档中分离出来单独作为一条结构化记录存储甚至可以和原文本片段建立引用关系。检索时如果问题涉及具体数值可以直接命中表格记录。我在项目中更倾向于路线一因为它保留了上下文不需要额外维护引用关系。但如果是年报这种整页都是表的文档路线二往往效果更好因为一个大表转成超长Markdown后切分窗口根本放不下还是会被拆坏。3.3 扫描件OCR识别准比识别多更重要数字化原生的PDF解析可以走文本提取但扫描件只能靠OCR。OCR选型上Tesseract是开源里最常见的但中文识别效果只能说一般PaddleOCR的中文版面识别能力明显更强尤其是表格结构还原方面在中文场景的工程效果非常明显在线服务如各云厂商的OCR API准确率更高但涉及数据外传适合对隐私要求不高的场景。用OCR时要记住识别准比识别多更重要。宁可漏掉一些边缘字符也别让OCR产生大量“幻觉文本”——也就是图片里根本没有、但模型脑补出来的字。因此OCR之后最好加一道置信度过滤对低置信度的文本宁可丢弃也不入库。另一个OCR特有的问题是版面顺序。OCR引擎输出的文字块并不总是按阅读顺序排列需要按坐标做后处理排序否则“从上到下读”的内容会被排成“从下到上”。3.4 保留元数据这是你后期溯源和优化的底气很多人做文档解析时只关注“提取出了多少字”却不关心“这些字是从哪儿来的”。这是大忌。一份完整的解析输出每个文本块至少应该带上这些元数据来源文件名和文件路径页码文本块在页面上的坐标文本块类型标题、正文、表格、页脚等文档级元数据作者、创建时间、主题等这些元数据在下游至少有三个用途第一检索结果可以带引用来源用户点开就能定位到原文第二排障时可以快速定位是哪一页哪个位置的文本出了问题第三在做切分时可以根据标题类型调整切分粒度比如标题下面的正文优先保持完整。在LangChain里Document对象本身就带一个metadata字段就是干这个用的。但加载器默认不会帮你填全你得自己在解析层主动把这些信息塞进去。4. 实操一套可落地的文档加载解析管线讲了这么多理论下面直接上一套能跑的代码。我用Python写一个轻量级的文档加载解析管线覆盖PDF、Word、Markdown、TXT这几种最常见的格式按扩展名分发解析策略输出统一的Document结构。这个示例可以作为你自己项目的基础模板按需扩充。4.1 环境和目录设计建议用虚拟环境管理依赖需要安装的库就这几个pip install pymupdf pdfplumber pypdf python-docx unstructured目录结构我习惯这样组织document_loader/ ├── main.py # 入口遍历文件目录 ├── parsers/ │ ├── __init__.py │ ├── pdf_parser.py # PDF解析 │ ├── docx_parser.py # Word解析 │ └── text_parser.py # Markdown/TXT解析 ├── schemas.py # 统一数据结构 └── output/ └── parsed_docs.json # 解析结果输出4.2 统一数据结构定义不管什么格式最终我都统一成下面的Document结构# schemas.py from dataclasses import dataclass, field from typing import Optional dataclass class DocumentChunk: text: str # 提取出的文本内容 metadata: dict field(default_factorydict) # 元数据 dataclass class ParsedDocument: source: str # 来源文件路径 doc_type: str # 文档类型pdf/docx/md/txt chunks: list[DocumentChunk] # 解析出的文本块列表 total_chars: int # 总字符数用来做质量评估4.3 PDF解析器双工具组合PDF解析我用PyMuPDF做主体提取同时判断是否需要走表格精提或OCR# parsers/pdf_parser.py import fitz # PyMuPDF def parse_pdf(path: str) - ParsedDocument: doc fitz.open(path) result ParsedDocument(sourcepath, doc_typepdf, chunks[]) total_chars 0 for page_idx, page in enumerate(doc): # 用dict模式获取文本块及坐标信息 blocks page.get_text(dict, flagsfitz.TEXT_PRESERVE_WHITESPACE) text_blocks [] for block in blocks.get(blocks, []): if block[type] ! 0: # type 0 表示文本块图片块是1 continue # 提取每个span的文本 block_text .join( span.get(text, ) for line in block.get(lines, []) for span in line.get(spans, []) ).strip() if not block_text: continue if _is_noise(block_text): # 过滤页眉页脚等噪声 continue text_blocks.append({ text: block_text, bbox: block.get(bbox), page: page_idx 1, }) # 按坐标排序以y坐标为主x坐标为辅 text_blocks.sort(keylambda b: (round(b[bbox][1], 1), b[bbox][0])) for tb in text_blocks: metadata { source: path, page: tb[page], bbox: tb[bbox], } result.chunks.append(DocumentChunk(texttb[text], metadatametadata)) total_chars len(tb[text]) result.total_chars total_chars doc.close() return result def _is_noise(text: str) - bool: 过滤页眉页脚和页码等噪声 noise_patterns [第, 页, 目录] if len(text) 10 and any(p in text for p in noise_patterns): return True return False这段代码里有几个值得说道的点。一是get_text(dict)而不是简单的get_text()目的是拿到每个文本块的坐标信息这是后面排序和清洗的基础。二是我做了按坐标排序这一步对多栏PDF尤其重要否则左栏文字后面会跟着右栏文字导致上下文完全错乱。三是_is_noise函数它在真实项目里会越写越长因为不同来源的文档噪声模式完全不同但思路是一样的——先过滤再入库。如果你要处理表格密集的PDF在循环里判断一下当前页是否包含表格包含的话再额外调pdfplumber提取表格转成Markdown# parsers/pdf_parser.py 继续 import pdfplumber def extract_tables_as_markdown(path: str, page_num: int) - str: 把指定页的表格提取成Markdown格式 md_lines [] with pdfplumber.open(path) as pdf: page pdf.pages[page_num] tables page.extract_tables() for table in tables: if not table: continue # 处理表头 header table[0] md_lines.append(| | .join([str(c).replace(\n, ) if c else for c in header]) |) md_lines.append(| ---| * len(header)) for row in table[1:]: md_lines.append(| | .join([str(c).replace(\n, ) if c else for c in row]) |) return \n.join(md_lines)4.4 Word和纯文本解析器Word文档用python-docx解析需要注意读取Word中的段落时也要遍历表格对象否则Word里嵌的表格会直接被丢失# parsers/docx_parser.py import docx def parse_docx(path: str) - ParsedDocument: d docx.Document(path) result ParsedDocument(sourcepath, doc_typedocx, chunks[]) total_chars 0 # 遍历段落 for para in d.paragraphs: text para.text.strip() if text: metadata { source: path, style: para.style.name if para.style else None, } result.chunks.append(DocumentChunk(texttext, metadatametadata)) total_chars len(text) # 遍历表格Word表格也要转成结构化文本 for tbl in d.tables: md_lines [] for row in tbl.rows: cells [cell.text.strip().replace(\n, ) for cell in row.cells] md_lines.append(| | .join(cells) |) table_text \n.join(md_lines) if table_text: metadata {source: path, style: table} result.chunks.append(DocumentChunk(texttable_text, metadatametadata)) total_chars len(table_text) result.total_chars total_chars return result纯文本类解析就简单得多# parsers/text_parser.py def parse_text(path: str, doc_type: str txt) - ParsedDocument: encodings [utf-8, gbk, gb18030, latin-1] content None for enc in encodings: try: with open(path, r, encodingenc) as f: content f.read() break except (UnicodeDecodeError, UnicodeError): continue if content is None: raise ValueError(f无法识别文件编码: {path}) result ParsedDocument(sourcepath, doc_typedoc_type, chunks[]) # 按空行分段落 paragraphs [p.strip() for p in content.split(\n\n) if p.strip()] for p in paragraphs: result.chunks.append(DocumentChunk(textp, metadata{source: path})) result.total_chars sum(len(p) for p in paragraphs) return result4.5 入口按格式分发最后写一个入口遍历整个文档目录自动按扩展名分发给对应解析器# main.py from pathlib import Path import json from schemas import ParsedDocument from parsers.pdf_parser import parse_pdf from parsers.docx_parser import parse_docx from parsers.text_parser import parse_text PARSER_MAP { .pdf: parse_pdf, .docx: parse_docx, .md: lambda p: parse_text(p, md), .txt: lambda p: parse_text(p, txt), } def load_documents(input_dir: str) - list[ParsedDocument]: seen set() results [] path Path(input_dir) for file_path in path.rglob(*): if not file_path.is_file(): continue suffix file_path.suffix.lower() if suffix not in PARSER_MAP: print(f[跳过] 不支持的格式: {file_path.suffix}) continue # 避免重复解析比如路径大小写或软链接 file_key str(file_path).lower() if file_key in seen: continue seen.add(file_key) parser PARSER_MAP[suffix] try: result parser(str(file_path)) results.append(result) print(f[成功] {file_path} 解析完成共 {result.total_chars} 字符) except Exception as e: print(f[失败] {file_path} 解析异常: {e}) return results if __name__ __main__: docs load_documents(./input_docs) # 输出到JSON方便下游切分和向量化模块读取 with open(./output/parsed_docs.json, w, encodingutf-8) as f: json.dump([ { source: d.source, doc_type: d.doc_type, total_chars: d.total_chars, chunks: [ {text: c.text, metadata: c.metadata} for c in d.chunks ], } for d in docs ], f, ensure_asciiFalse, indent2)这套管线跑完后你会得到一个统一的JSON输出文件。接下来的文本切分模块直接读这个文件就行完全不用关心原始文件是什么格式。顺带说一句这个JSON里如果你用LangChain做下游链路可以直接把每个chunk转成LangChain的Document对象metadata原样透传即可衔接非常丝滑。5. 实战排坑文档解析最常见的6类问题速查解析管线的代码写完只是万里长征第一步真正折磨人的是各种稀奇古怪的文档问题。我挑了实战中最常遇到的6类问题按“现象-原因-解法”给你整理成速查表。5.1 扫描版PDF永远提取不到文字这是最高频的问题。现象是代码跑完返回的字符数是0或者只有零星几个字。原因是PDF本身没有文本层内容是一张张图片。解法分两步。先做个快速判断import fitz doc fitz.open(path) text doc[0].get_text().strip() if len(text) 0: print(该页面可能是扫描件需要走OCR流程)确认是扫描件后方案有二一是直接把PDF每页渲染成图片走PaddleOCR识别二是用带OCR能力的解析工具比如Unstructured的partition_pdf(ocr_modefull)。我自己的经验是如果扫描件比例不高可以先把这批文件单独摘出来统一走OCR批处理避免拖慢主链路的解析速度。5.2 表格解析后结构全乱现象是表格数据提取出来后行和列完全对不上数字串行。原因往往是表格存在跨页、合并单元格或者表格本身没有明显的边框线。解法上先用pdfplumber对目标页单独做extract_tables()拿到单元格级别的数据而不是纯文本。如果pdfplumber也提取不出结构再考虑用OCR场景下的表格还原模型。另外我有个习惯表格解析完成后抽样人工抽查20%的表格确认Markdown结构没乱。5.3 中文PDF解析出来乱码和错字现象是英文和数字正常中文变成乱码、方块或者错字。原因常见于PDF使用的字体是自定义子集字体或者字体编码映射表缺失。这种问题在PyMuPDF中无法完全规避解决办法是多试几种底层库交叉验证。我的排查顺序是先PyMuPDF再pdfplumber再PDFMiner看看哪个库的解码结果更稳定。如果是复制出来的中文变成乱码大概率是字体的ToUnicode映射有问题这种文档建议直接转图片走OCR反而又快又准。5.4 多栏排版文字串行现象是解析出来的文本顺序是“左栏上半部 - 右栏上半部 - 左栏下半部”完全不符合人类阅读顺序。原因是文本按块输出顺序是PDF内部绘制顺序并不等于阅读顺序。解法就是代码里提到的“先按块坐标排序”。具体做法是拿到每个文本块的bbox对y坐标做聚类同一行的块y坐标相近归为一组组内再按x轴从做到右排。如果文档栏数固定比如学术论文普遍双栏也可以按页面的中轴切分左半边和右半边分别按从上到下排序再把两栏合并。5.5 内存爆炸和处理超时现象是一批大PDF几百MB甚至上GB解析到一半内存飙升或者单文件处理耗时太长。解法有两个思路。一是按页流式处理读一页、解析一页、释放一页不要一次性把整个文档的全文都load进内存。PyMuPDF本来就是按需读页的但要注意别把整本书的文本块都存到一个list里而不写盘。二是控制批处理的并发度别一上来就搞20个线程同时解析反而会被CPU和内存拖死。正常我会限制并发数为CPU核心数的一半。5.6 解析结果里有大量无意义文本现象是一些文档页眉、页脚、页码、水印、重复的免责声明全都被当成正文入库了。这些内容不仅浪费存储还会严重污染检索结果比如用户问“这个合同的有效期”结果TopK里返回的全是每页底部的“本文件受法律保护”这种声明。解法就是在_is_noise这类过滤函数里持续迭代。我的做法是先对一批真实文档做完整解析把噪音文本列出来观察规律然后写规则过滤。常见规律包括页码模式纯数字或第x页、页眉页脚固定词组、超短文本块少于15个字且独立成块以及跨页面重复出现的完全相同的段落。排查这6类问题我的经验可以浓缩成一句话先做抽样再做全量。任何解析管线写完后不要直接全量入库先抽10到20份有代表性的文档跑一遍人工检查解析结果的质量确认没有系统性问题后再放全量。结尾留给踩坑换来的体会文档加载解析在RAG项目里的地位很像盖房子打地基看不见摸不着但整栋楼稳不稳全看它。我个人做了好几个知识库项目之后的体会是与其后期花几周时间调向量检索参数、优化重排模型不如前期多花两三天把解析环节打磨扎实把样本质量抽查做到位。再分享一个可能很多教程不会提的小技巧解析完一批文档后我会把解析结果的纯文本版本打印出来自己当读者从头到尾读一遍。读起来通顺没有任何串行、乱码、缺字、表格错乱这批数据才敢放心进向量库。这个土办法朴素但比任何指标都管用。这套加载解析管线的下一步就是文本切分策略了——怎么在保留语义完整的前提下把长文档切成适合检索的切片这中间的门道同样不少。后面有机会我再单独写一篇把这部分的踩坑和方案补完。