Qwen-Agent 文档解析与分块机制:3 个参数调出可检索的大文档知识库
【免费下载链接】Qwen-AgentAgent framework and applications built upon Qwen>=3.0, featuring Function Calling, MCP, Code Interpreter, RAG, Chrome extension, etc.项目地址: https://gitcode.com/GitHub_Trending/qw/Qwen-Agent
把 60 页 PDF 丢给 AI 问答,回答却只有第一页的内容:上下文窗口装不下全文,硬塞进去又是幻觉重灾区。Qwen-Agent 的文档解析与分块链路解决的就是这件事——把任意格式的文件变成一组大小可控的 chunk 落盘缓存,检索时按需取用。文章按数据经过的顺序,拆解文档分块策略、文件解析缓存机制与分块参数调优点。
为什么 60 页 PDF 只答得对第一页
把全文直接喂给模型有两条死路:要么窗口放不下,要么塞进去之后注意力被稀释,回答变成“正确但没用”——只复述前几页的细节。Qwen-Agent 的解法不是读更多,而是切更细:先把文件解析成结构化段落,再装进约 500 token 的块里,提问时由检索层只取相关的几块。知识库构建流程与提问从此解耦:文件只处理一次,问题问多少次都行。
数据链路:从文件到被检索的 chunk
文件进系统时只是一个url参数,本地路径或 http(s) 链接皆可。DocParser先查这个 url 有没有分块缓存;没有才调SimpleDocParser得到结构化文档(页 → 段落/表格,每段都带 token 数);再按总 token 决定整篇直出还是走split_doc_to_chunk;结果序列化成 json,经Storage以普通文本文件落盘。提问时,keyword_search用 BM25 对所有 chunk 打分,命中片段拼进 system prompt 交给模型。
核心机制:格式收敛、装箱分块、哈希落盘
输入侧:9 种格式统一成“段落 + token 数”
问题:PDF 版式乱、Excel 是表格、PPT 一页一帧,各格式单独解析的话下游没法通用。方案:SimpleDocParser把 pdf/docx/pptx/txt/html/csv/tsv/xlsx/xls 九种格式收敛成同一种结构——页列表,每页内含text段落和table表格,见 qwen_agent/tools/simple_doc_parser.py。表格统一转成 Markdown 管道表,每段解析时就用count_tokens标好 token 数,后面装箱不用再算。PDF 另有后处理:字号差小于 2 的相邻行合并回一个段落,落在表格区域内的重复文字被剔除。边界:Word 和 txt 没有页的概念,整份都算“第 1 页”;PDF 里的图片不会抽取(extract_image传 true 会直接抛错),所以扫描件几乎拿不到东西——这条链路只处理数字原生文本。
处理侧:阈值直出、句级切分与 150 字符重叠
问题:明明能塞进窗口的文档硬去切块,只会白白引入断裂点。方案:先统计全部段落 token,不超过max_ref_token(默认 20000)就整篇直出为一个 chunk;超了才进split_doc_to_chunk装箱——段落按顺序装进当前块,撑满parser_page_size(默认 500)就封一块、开新箱;单个段落装不下时按re.split(r'\. |。')切成句子,句子还放不下就由 tokenizer 按剩余 token 数硬切。每个 chunk 头部带[page: N]标记,回答时可以指到页。
真正的设计点在重叠窗口。⚠️ 封块时_get_last_part会从这个块末尾取最多 150 字符,接到下一个块的开头——且只取同一页的,跨页即停:
need_page = chunk[-1][1] # Only need this page to prepend available_len = 150 for i in range(len(chunk) - 1, -1, -1): if chunk[i][1] != need_page: return overlap代价:同一段内容会出现在相邻两个 chunk 里,BM25 打分时命中两遍、占两份引用窗口。150 字符是写死的预算(约等于 500 token 块的三成),没有配置项,想加大重叠只能改代码。
输出侧:哈希命名、两级缓存与 get/put 接口
问题:同一文件反复解析;内容没变、参数一变又重新切块,时间都花在重复劳动上。方案:缓存键 = url 哈希 + 参数,命中判断就是一次try/except,见 qwen_agent/tools/doc_parser.py:
cached_name_chunking = f'{hash_sha256(url)}_{str(parser_page_size)}' try: record = self.db.get(cached_name_chunking) record = json.loads(record) return record except KeyNotExistsError: doc = self.doc_extractor.call({'url': url})Storage本质是“key 即文件名”的映射:put把一个 value 写成单个文本文件,get不存在就抛KeyNotExistsError,scan可以遍历目录看全部键值,见 qwen_agent/tools/storage.py。缓存分两级:SimpleDocParser先把解析结果缓存为hash(url)_ori,只改分块参数时不必重新解析 PDF;DocParser再缓存分块结果,不切块的文档键名会换成hash(url)_without_chunking。边界:缓存键只看 url 字符串,本地文件原地更新后旧缓存照样命中——得删掉存储目录里的对应文件或换路径,没有内容哈希校验。
分块参数调优:3 个配置决策点
parser_page_size:默认 500 够不够
大多数文档够用。它是每个 chunk 的“装箱容量”:越大块数越少、单块语义越完整,但检索命中时带入的无关内容也越多。长段论述、跨段引用的文档可以调大(800~1000);条款型、参数表多的文档调小(200~300),定位更准。注意这个值参与缓存键,改了立刻生效、新旧结果不冲突,但会多占一份磁盘。
max_ref_token:默认 20000 的取舍
一个阈值两处生效:既是整篇直出的 token 上限,也是检索引用的总长度约束。模型窗口 32k 时,默认值给问题和回答留足了余量;换 128k 模型、希望模型看更多内容时,可以提到 40000~80000,原本被强制切块的大文档会直接整篇输出,连分块成本都省了。反过来调低,更多文档会走进分块分支,回答质量就更依赖检索层的表现。
存储路径:workspace 下的 tools 目录
⚠️doc_parser与simple_doc_parser默认落在workspace/tools/<工具名>(DEFAULT_WORKSPACE,可用环境变量QWEN_AGENT_DEFAULT_WORKSPACE改)。配置项上,doc_parser认path,Storage认storage_root_path。单机跑 demo 默认值就够;多个 Agent 共用一台服务器时建议指到快的盘,并给不同工作空间分开建目录。缓存文件都是人能读的 JSON,整目录删除即重建,排查 chunk 质量也能直接翻文件。
上手入口:示例与文档
分块解决“喂得下”,缓存解决“存得住、找得到”,检索解决“取得准”,三层各干一件事,参数集中在doc_parser配置与三个环境变量里。想动手的话,examples/assistant_rag.py 演示单文件问答的接法,examples/parallel_doc_qa.py 演示多文档并行问答,细节看 RAG 模块文档。
【免费下载链接】Qwen-AgentAgent framework and applications built upon Qwen>=3.0, featuring Function Calling, MCP, Code Interpreter, RAG, Chrome extension, etc.项目地址: https://gitcode.com/GitHub_Trending/qw/Qwen-Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考