Skip to main content

17 · 实战:给真实政策文件做问答(把所有学过的用在一份真文件上)

开头的现象

老王把两份文件发到小林的 data 目录里,说:“这两份是国家’十五五’规划的真实文件——一份就业、一份教育。你学了两个月,别老拿员工手册那 3000 字玩具练手。用这两份真文件,把你学的切块、向量化、检索全走一遍。” 小林打开文件,眼睛直了:《十五五就业优先规划》7448 字,《十五五教育规划》7682 字——每一份都比员工手册长一倍多。 他咽了口口水,心里既有压力也有兴奋:“终于要拿真家伙练手了。” 他定的目标是:让机器人能回答这两份政策文件里的问题——“就业目标是什么?""立德树人怎么落实?“——而且两份文件要能区分,问就业的问题不能答成教育的。

第一幕:小林发现第一个坑——文件名会变,代码别写死

小林第一版代码是这么写的:
他运行——FileNotFoundError。 他愣了一下,去 data 目录一看:文件名根本不是”就业优先战略”,是”就业优先”,而且还多了一份”教育规划”。他这才想起第 7 篇的教训,当时老王说过:“文档会更新。” 他改成了”自动发现”——用 glob 匹配”十五五”开头的所有 txt:
小林点点头:“写死文件名 = 埋雷。自动发现 = 以后老王丢进第三份文件,我代码都不用碰。” 他心想:“这跟我第 8 篇说的’一个库多个 collection’是一个道理——结构要能容纳变化,别写死。”
想验证”自动发现”吗?往 data 目录丢一个 十五五XXX.txt,重跑 07_load_split.py——它自动多处理一份文件,代码一行没改。文件多了,glob 自动发现。

第二幕:小林决定”一个库、两个 collection”——政策文件各归各的

小林第 8 篇学过”一个库 = 一个目录 = 多个 collection”。他决定不另开目录,把两份政策文件塞进同一个向量库 data/chroma_db,用不同 collection 区分
小林特别强调了那行 delete_collection()——他第 8 篇踩过 shutil.rmtree 整个库的坑,这次只删自己的 collection,员工手册的库动都不动。“合租房的自觉:只扔自己房间的垃圾。” 他验证了一下最终结构——用一个 sqlite 查询看这个库里有几个 collection:
小林盯着这行输出,心里踏实了:“一个库装三份文档,各检索各的,互不干扰。这就是第 8 篇说的’一个库多个 collection’的实战版。”
想验证”一个库三个 collection”吗?跑 08_embed_search.py——它会自动发现两份政策文件、各自入库。然后用 Python 查 data/chroma_db/chroma.sqlite3 的 collections 表,你会看到三个 collection 平起平坐。

第三幕:分 collection 检索——问就业的不会答成教育的

小林开始测试检索。他问了两个问题,各进各的 collection:
小林又故意试了”串门”——用教育的问题去查就业的库:
他倒吸一口凉气:“collection 隔离是’物理的’——不隔离的话,问教育的问题,就业库会硬凑一段最像的出来。分 collection 就是给每份文档一个’专属抽屉’,别混着放。” 他还对比了关键词搜索和向量检索(第 8 篇的结论重现)——搜”人工智能对就业有什么影响”,关键词找”人工智能”命中 4 块(靠字面猜),向量检索精确命中”适应人工智能发展促进就业创业”那一块(靠意思):
想验证”分抽屉”吗?跑 08_embed_search.py 的第三部分——就业问题查 employment_15_5、教育问题查 education_15_5,各答各的。你把 collection 名互换,立刻答非所问。

第四幕:边界——政策文件的检索,跟员工手册有什么不一样

小林用真实文件跑完,发现了几个”员工手册时代没遇到”的边界: 边界一:政策文件是”长段落 + 序号结构”(一、(一)、1.),普通切分器会切得有点碎。 他数了数:就业 54 块、教育 62 块,很多块只有一两句话。政策文件的自然单位是”条目”,而不是”句子”——理想的做法是按”(一)(二)(三)“这种条目边界切,但 chunk_size=200 的通用切法也能用,只是块更碎。 边界二:两份文件用语高度相似(都是”深入贯彻""推动""促进”),向量容易混淆。 小林发现”就业”和”教育”的开头段落用词很像,如果检索时不指定 collection,可能串。分 collection 就是应对这个的。 边界三:政策文件的问题通常是”总结性提问”(“就业目标是什么”),需要检索到”总体要求”那一整段。 单块 200 字符可能只覆盖一半,k=2k=3 更稳。 边界四:真实文件的 metadata 很重要。 他给每个块打了 col_name 前缀的 id,这样检索到任何一块,都能知道它来自哪份文件——“回答标出处”就靠这个。
想验证边界吗?问”人工智能对就业的影响”(就业库)和”人工智能对教育的影响”(教育库)——注意,政策文件里两处都提到人工智能,但如果 collection 不分,检索会串到另一份文件去。

🔧 技术细节:实战用到的全部签名汇总(前文各章 + 本章新增)

这一章是把前面所有章串起来实战,小林把全程用到的签名汇总成一张”最终兵器谱”: ① 文件自动发现(glob)——本章新增
② 切分(第 07 篇)
③ 向量库:一个库多个 collection(第 08 篇)
④ 分 collection 检索(本章核心用法)
⑤ 从文件名推断 collection 名(id 命名溯源)
想验证吗?跑 08_embed_search.py——自动发现两份政策文件、各自入库(就业 54 块 / 教育 62 块)、分 collection 检索互不串门。查 data/chroma_db/chroma.sqlite3 的 collections 表,能看到三个 collection 平起平坐。

结论(小林用一天换来的)

给真实文件做问答 = 把学过的东西串起来:glob 自动发现文件(别写死文件名)→ 切块(第 7 篇的刀)→ 一个库多个 collection 区分文档(第 8 篇,只删自己的 collection)→ 分 collection 检索(第 06/07 篇)。 真实文件的边界:政策文件是”条目结构”(切得碎)、两份文件用语相似(必须分 collection 隔离)、总结性提问要 k 大一点、metadata 记好出处。员工手册学的是”机制”,政策文件练的是”实战”——机制没变,但真实文件会逼你想清楚”怎么组织”(文件名、collection、边界)。

复现信号:什么时候你会想起这一章

  1. 文档会更新/新增——文件名用 glob 自动发现,别写死。
  2. 多份文档要共存——一个库多个 collection,各检索各的,别混。
  3. 要清理重建——只删自己的 collection(delete_collection()),别 rmtree 整个库。
  4. 检索结果答非所问——先查 collection 是不是选错了,再查 embedding 模型是不是换了。
  5. 真实文档结构特殊(政策/合同/报告)——通用切分可能碎,考虑按文档自己的条目结构切。

后记:小林学到了什么(整个系列的收尾)

小林合上电脑,望着窗外。他想起了老板最初的需求——“基于你的文档回答问题”。现在他能说清楚这整条路是怎么走的了: 第 01~04 篇:会说话。 模型统一插口(invoke/stream/with_structured_output/thinking)——换供应商只改三行。 第 05~07 篇:有资料。 Document 切块、Embedding 向量化、Chroma 检索、RAG 流水线——模型”翻手册”回答你的文档。 第 08~09 篇:会记住、会干活、会转弯。 记忆(历史 + session_id)、工具(@tool)、Agent(create_agent 自动循环)、LangGraph(自己搭图、checkpointer 持久化记忆)。 第 10~12 篇:懂地基。 AIMessage 统一信封、模板管结构不管内容、langchain_core 三层架构。 第 13 篇:能实战。 真实政策文件走全流程。 他想起老王最后说的话:“小林,你现在不是’会用 LangChain 了’,是’知道 LangChain 为什么长这样了’。这两者的区别,就是以后遇到新问题,你是去查文档,还是能自己推断出答案。” 小林笑了笑,把最后一句话写在工位贴纸上:
LangChain 三层架构:协议层(langchain_core 定义接口)→ 实现层(集成包连具体服务)→ 编排层(langgraph 管流程/状态/记忆)。 你学的是这三层怎么配合——换模型改实现层,加能力改协议层,改流程改编排层。这就是全部。

验证清单(整个系列的收官测试)

  1. 换模型供应商,你的代码要改哪几行?(提示:第 1 篇)
  2. 模型为什么”失忆”?怎么让它记住?(提示:第 4 篇)
  3. 用户问”工作满一年能休几天”,关键词搜索为什么漏?(提示:第 8 篇)
  4. RAG 和”把文档塞进 prompt”的本质区别?(提示:第 9 篇)
  5. create_agent 返回的是什么类型?内部长什么样?(提示:第 11 篇)
  6. 三份协议(OpenAI/Anthropic/Responses)的取文本方式分别是什么?(提示:第 2 篇)
  7. create_agent 内部用 ChatPromptTemplate 吗?为什么?(提示:第 3 篇)
  8. langchain_core 的 29 个模块里,你天天用的 10 个是哪些?(提示:第 12 篇)
  9. 多份文档共存一个向量库,怎么组织?(提示:第 13 篇)
小林的故事到这里还差一步:老板看了演示,眼睛一亮——“做成接口,让全公司的人都能问。” 小林愣住了。他是 Java 后端出身,写 Spring Boot 时从没操心过线程池——那是 Tomcat 的事。现在用 Python 暴露 API,他得从头想:Python 后端自带线程池吗?langchain_core 自己会发 HTTP 请求,怎么和后端框架配合?——翻到第 18 篇:后端接口,把链变成 API