ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

古籍RAG实战:用Skill打造AI古籍顾问,解决异体字与引文溯源难题

古籍RAG实战:用Skill打造AI古籍顾问,解决异体字与引文溯源难题 1. 缘起一个古籍爱好者的技术冲动1.1 为什么我会盯上“古籍”这个方向先说清楚这个项目到底要干什么。古籍 skill 项目目标是把中国历代古籍——经史子集、笔记杂谈、方志谱牒——做成一套可以被 AI 助手直接调用的技能包让一个普通人也能用自然语言去检索、理解、引用古籍原文。它解决的核心问题是古籍的阅读门槛太高而通用大模型对古籍的处理又太“飘”经常一本正经地胡说八道把《论语》的话安到《孟子》头上把明清的注疏当成先秦原文。我接触古籍这件事最早纯粹是个人兴趣。小时候家里有一套影印本的《史记》繁体竖排没有标点读起来像啃石头。后来读研期间做了一点文献学的边角工作才慢慢摸到门道原来古籍不是“难读”而是“缺工具”。你需要字典、需要索引、需要版本对照、需要知道这句话在哪个注本里被谁解释过。这些工作在过去要靠人肉翻检一套《佩文韵府》翻一下午就为找一个典故出处。现在有了大模型理论上这些事情可以自动化。但实际试下来通用模型在古籍场景下的表现非常不稳定。你问它“《说文解字》里‘示’部的字有哪些”它可能给你编几个不存在的字你问它“这句话出自《资治通鉴》哪一卷”它给的卷数经常对不上。原因很简单古籍是一个长尾、高密度、强引用的知识领域通用模型的训练数据里古籍占比极低而且古籍的引用格式、版本差异、异体字问题都不是通用语料能覆盖的。所以这个项目的缘起说白了就是一句话我想给 AI 装一个“古籍脑子”。不是让它变成古籍专家而是让它在我需要查古籍的时候能老老实实去检索、去引用、去标注出处而不是凭空生成。这个“脑子”的载体就是 skill。1.2 skill 到底是什么为什么不用普通 prompt这里要解释一下 skill 这个概念。如果你用过一些 AI 助手平台可能见过“技能”“插件”“工具”这些词。skill 本质上是一段结构化的、可被模型调用的能力描述它通常包含三部分触发条件什么时候用这个技能、执行逻辑具体怎么做、输出规范结果长什么样。它和普通 prompt 的区别在于prompt 是你每次都要手写的一段话而 skill 是一次写好、反复调用、可以组合的模块。我选择 skill 而不是纯 prompt有几个很实际的理由。第一古籍检索的流程是固定的先判断用户问的是哪类问题字词训诂、典故出处、版本比对、篇章大意再决定去哪个数据源查最后按统一格式输出。这个流程写成 skill比每次手写 prompt 稳定得多。第二skill 可以挂载外部工具比如 RAG 检索、字典 API、全文索引这些是纯 prompt 做不到的。第三skill 可以版本化管理我今天改一版明天回滚比在聊天框里改 prompt 靠谱。热词里出现的chinese-classics-advisor、guji-rag、book to skill其实都是这个思路的不同叫法。chinese-classics-advisor偏向“顾问型”技能强调问答和解释guji-rag偏向“检索型”技能强调从古籍库里捞原文book to skill则是更通用的方法论把一本书变成一个技能。我这个项目算是三者的混合体以 RAG 为检索底座以 advisor 为交互形态以 book to skill 为构建方法。1.3 这个项目适合谁看我把话说在前面这个项目不是给古籍专家看的。真正的文献学研究者不需要我这套东西他们有专业的数据库和几十年的训练。这个项目适合的是对古籍有兴趣但被门槛挡住的人比如想读《世说新语》但看不懂注的普通读者想写历史小说但需要查典章制度的作者想给孩子讲成语故事但怕讲错的家长以及像我这样半只脚踩在文献学边上、想用技术手段提高效率的爱好者。技术层面这个项目适合有基础 Python 能力、了解一点 RAG 概念、愿意折腾本地环境的人。如果你完全没写过代码也能看懂思路但实操部分可能需要先补一点基础。我在后面的章节里会尽量把每一步都写清楚包括参数为什么这么设、坑在哪里、怎么验证结果对不对。2. 整体设计古籍 skill 的架构思路2.1 为什么是 RAG而不是微调这是项目启动时第一个要做的决策要让 AI 懂古籍是微调一个模型还是用 RAG 检索增强我最后选了 RAG理由有三条每条都是踩过坑之后才想明白的。第一条是数据更新成本。古籍的整理成果是持续产出的今天出一本新的校注本明天补一批出土文献微调模型意味着每次都要重新训练成本高到不现实。RAG 只需要把新数据灌进知识库索引重建一下就行几个小时的事。第二条是引用可追溯。古籍场景最怕的就是“编”。微调模型的知识是揉在参数里的你问它出处它只能“回忆”回忆错了你也没法查。RAG 的每一条输出都来自检索到的原文片段可以精确到书名、卷次、页码这对古籍场景是刚需。第三条是成本。微调一个像样的模型显卡、时间、数据标注哪一样都是钱。RAG 用现成的 embedding 模型加一个向量库本地跑起来几乎零成本。热词里那个ollama 简易本地 rag 知识库【零基础可复制教程】就是这个思路的典型代表。当然 RAG 也有它的瓶颈热词里rag瓶颈这个词不是白出现的。后面我会专门讲古籍 RAG 特有的几个难点异体字、通假字、繁简转换、引文嵌套。这些在通用 RAG 里不是问题在古籍里全是问题。2.2 知识库的分层设计古籍知识库不能一锅炖。我把它分成三层每层的用途和检索策略都不一样。层级内容用途检索方式原文层古籍正文带标点、带版本信息精确引用、原文核对全文检索 向量检索注疏层历代注、疏、笺、正义解释疑难字词、典故向量检索为主工具层字典、韵书、人名地名索引查字、查人、查地结构化查询原文层是底座必须保证版本可靠、标点准确、异体字有对照。我用的底本以中华书局、上海古籍出版社的点校本为主这些本子质量有保障但要注意版权问题个人研究使用没问题公开分发要谨慎。注疏层是价值最高的部分因为古籍的难点几乎都在“这句话到底什么意思”上而注疏就是历代学者对这个问题的回答。工具层是辅助但缺了它很多查询会卡住比如你查一个生僻字没有字典层就只能干瞪眼。热词里有个ontology rag和kg知识库、rag知识库和结构知识库区分以及应用场景这其实点到了一个关键问题古籍知识天然是图结构的。一个人名出现在哪几本书里、一个典故被谁引用过、一个地名在哪个朝代叫什么这些都是实体和关系。纯向量 RAG 处理不了这种关系查询所以我在设计里预留了知识图谱的接口虽然第一版没做但架构上留了位置。2.3 skill 的触发与路由设计skill 的核心是什么时候触发、触发后走哪条路。古籍问题五花八门不能一个流程走到底。我设计了一个简单的路由逻辑用关键词和问题类型来判断。问题里出现“什么意思”“怎么解释”“训诂” → 走注疏层优先检索注疏问题里出现“出自”“哪本书”“哪一卷” → 走原文层精确检索出处问题里出现“有哪些”“列举”“统计” → 走结构化查询查索引问题里出现“对比”“版本”“异文” → 走多版本比对需要原文层有多个版本这个路由不是硬编码的 if-else而是写在 skill 的描述里让模型自己判断。实测下来模型对“什么意思”和“出自哪里”的区分准确率很高但对“对比”类问题的判断经常出错因为用户可能只是想知道两个词的区别而不是两个版本的差异。所以我在 skill 里加了一条规则不确定的时候先问用户一句而不是猜。热词里agent skill、codex skill、skill脚本这些词说的都是 skill 的实现形态。我的实现方式是一个主 skill 加若干子 skill主 skill 负责路由和兜底子 skill 负责具体任务。这样改起来方便加一个新功能就是加一个子 skill不影响主流程。3. 核心细节古籍 RAG 的几个硬骨头3.1 异体字与繁简转换的坑这是古籍 RAG 第一个绕不过去的坎。你检索“說”库里存的是“説”向量模型可能认为是两个字你检索“为”库里是“爲”直接搜不到。通用 RAG 教程里没人讲这个因为通用语料里异体字早就被规范化了但古籍不行异体字是原汁原味的一部分。我的处理方案是三层归一化。第一层是 Unicode 归一化用 NFKC 把兼容字符转成标准字符这一步能解决一部分问题。第二层是异体字映射表我整理了一份常用异体字对照把“説/說/说”“爲/為/为”这类映射到同一个规范字但保留原文的异体形式用于显示。第三层是繁简转换用 OpenCC 做但要注意 OpenCC 的默认配置是“简转繁”和“繁转简”不对称的古籍场景要用t2s和s2t两个方向都测一遍。注意繁简转换在古籍场景下不能无脑用。有些字简化后合并了比如“後”和“后”都变成“后”转换回去就分不清了。我的做法是只在检索时做繁简归一显示时永远用原文。实测下来加了异体字映射之后检索召回率大概提升了 15% 到 20%。这个数字看起来不大但在古籍场景下漏掉一条关键注疏可能就意味着整个回答跑偏。3.2 引文嵌套与出处追溯古籍里引文是常态。《史记》引《尚书》《汉书》引《史记》注疏里又引《汉书》。你检索一句话可能同时命中原文、引文、注疏里的引文三层嵌套。如果不做区分模型很容易把注疏里的引文当成原文把转引当成直引。我的处理方式是给每个文本块打来源标签。原文块标source: original注疏块标source: commentary注疏里的引文标source: commentary-quote。检索的时候按标签过滤回答的时候按标签组织。比如用户问“这句话出自哪里”我只在original里找用户问“这句话古人怎么解释”我在commentary里找但会注明“这是某某注里的说法”。热词里rag检索增强和rag实战说的就是这个层面的东西。检索增强不是把文本切块扔进向量库就完事了切块策略、标签体系、检索过滤每一步都影响最终效果。古籍的切块尤其麻烦因为一句话可能跨好几行一个注可能引好几本书切错了上下文就断了。3.3 向量模型的选型与古籍适配embedding 模型的选择直接决定检索质量。我试过几个方案最后用的是BGE 系列的中文模型具体是bge-large-zh的一个版本。选它的理由中文语料训练充分对文言文有一定覆盖而且本地部署方便不需要联网。但通用中文 embedding 模型在古籍上还是有短板。文言文的词汇分布和现代汉语差别很大很多词在现代汉语里是常用词在文言文里是专有名词。比如“消息”在文言文里是“消长、生灭”的意思和现代汉语的“信息”完全不同。通用模型会把这两个意思混在一起。我的应对办法是在检索时加一层关键词过滤。向量检索召回 top-k 之后再用 BM25 做一次关键词匹配两个结果加权融合。这个做法在 RAG 圈子里叫混合检索热词里rag框架提到的很多框架都支持。实测下来混合检索比纯向量检索在古籍场景下的准确率高不少尤其是查专有名词的时候。检索方式优点缺点适用场景纯向量语义匹配好专有名词易混问大意、问解释纯关键词精确匹配强语义泛化差查出处、查人名混合检索兼顾两者需要调权重大部分古籍查询3.4 输出格式的规范化古籍 skill 的输出不能像聊天一样随便。我定了一套格式规范核心是引用必须带出处解释必须标来源。引用原文用引号括起来后面跟书名·卷次引用注疏注明注者如汉·郑玄注自己的解释明确标按和古人说法区分开不确定的内容标待考不硬编这套规范看起来简单但执行起来需要 skill 里写清楚。我试过不写规范模型就会把注疏和原文混在一起把后人的解释当成原意。加了规范之后输出的可信度明显提升。4. 实操过程从零搭一个古籍 RAG 原型4.1 环境准备与依赖安装先说环境。我用的是Python 3.10 Ollama ChromaDB的组合全部本地跑不依赖外部服务。选 Ollama 是因为它部署模型简单一条命令就能拉一个模型下来选 ChromaDB 是因为它轻量适合原型阶段后面数据量大了可以换 Milvus 或 Qdrant。# 安装 Ollama以 Linux 为例 curl -fsSL https://ollama.com/install.sh | sh # 拉取 embedding 模型 ollama pull bge-large-zh # 拉取对话模型用于生成回答 ollama pull qwen2.5:7b # 安装 Python 依赖 pip install chromadb langchain langchain-community opencc-python-reimplemented jieba rank-bm25这里解释几个选型。对话模型选qwen2.5:7b是因为它在中文文言文上的表现相对好而且 7B 参数在消费级显卡上能跑。embedding 选bge-large-zh前面说过了。opencc做繁简转换jieba做中文分词rank-bm25做关键词检索。这些都是成熟工具不需要自己造轮子。提示如果你的机器没有独立显卡7B 模型跑起来会比较慢可以换成qwen2.5:3b或者用 CPU 推理但速度会明显下降。embedding 模型对显卡要求低CPU 也能跑。4.2 古籍数据的预处理流程数据预处理是整个项目最耗时的部分大概占了 70% 的时间。流程分五步收集、清洗、切块、标注、入库。第一步收集。我从公开的古籍文本库拿了一批底本主要是先秦到唐宋的核心典籍包括《论语》《孟子》《史记》《汉书》《世说新语》等。这些文本大部分是繁体带标点但质量参差不齐需要清洗。第二步清洗。主要做三件事去掉页眉页脚和校勘记里的杂讯、统一标点符号全角半角混用很常见、把异体字映射到规范字但保留原文。清洗脚本我写了一个 Python 文件核心逻辑是正则加映射表。import re from opencc import OpenCC cc OpenCC(t2s) # 繁转简用于检索归一 def clean_text(text): # 去掉多余空白 text re.sub(r\s, , text) # 统一标点 text text.replace(,, ).replace(., 。) # 去掉校勘记标记示例 text re.sub(r【.*?】, , text) return text def normalize_for_search(text): # 繁简归一用于检索 return cc.convert(text)第三步切块。古籍切块不能按固定字数切要按语义单元切。我的策略是原文按句切注疏按条切一条注就是一个块。句子的边界用标点判断遇到句号、问号、感叹号就切。但要注意古籍里的引号嵌套很多切的时候要保证引号闭合。第四步标注。每个块打上元数据书名、卷次、作者、版本、来源类型原文/注疏/工具。这些元数据在检索过滤和输出引用时都要用。第五步入库。用 ChromaDB 建两个 collection一个存原文一个存注疏。每个块先做 embedding再存进去。embedding 用 Ollama 的 API 调bge-large-zh。4.3 检索链路的搭建与调参检索链路是 RAG 的核心。我的链路是查询归一化 → 混合检索 → 重排序 → 上下文组装。查询归一化就是把用户的问题做繁简转换和异体字映射保证和库里的文本对得上。这一步很关键用户可能用简体问库里是繁体不归一化直接搜不到。混合检索就是前面说的向量加 BM25。向量检索取 top-20BM25 取 top-20然后加权融合。权重我设的是向量 0.7、BM25 0.3这个比例是试出来的古籍场景下向量稍微重要一点但关键词不能丢。重排序我用了一个简单的策略按来源类型加权。如果问题是问出处原文块的权重调高如果问题是问解释注疏块的权重调高。这个加权写在 skill 的路由逻辑里。上下文组装就是把检索到的块拼成一段给模型。这里要注意长度控制7B 模型的上下文窗口有限拼太多会截断。我的做法是取 top-5 块每块截断到 500 字总共不超过 2500 字。def retrieve(query, top_k5): # 归一化查询 q_norm normalize_for_search(query) # 向量检索 vec_results collection.query(query_texts[q_norm], n_results20) # BM25 检索 bm25_results bm25.get_top_n(jieba.lcut(q_norm), documents, n20) # 融合排序简化版 merged merge_and_rank(vec_results, bm25_results, vec_weight0.7) return merged[:top_k]4.4 skill 的编写与挂载skill 的编写是整个项目的“最后一公里”。我用的是 Markdown 格式的技能描述包含触发条件、执行步骤、输出规范三部分。触发条件写清楚什么情况下用这个 skill执行步骤写清楚调用哪些工具、按什么顺序输出规范写清楚格式要求。# 古籍顾问 skill ## 触发条件 用户询问中国古籍相关的问题包括字词解释、典故出处、版本对比、篇章大意。 ## 执行步骤 1. 判断问题类型训诂/出处/对比/大意 2. 调用 retrieve 工具检索相关文本块 3. 根据问题类型过滤来源原文/注疏 4. 组装上下文调用对话模型生成回答 5. 按输出规范格式化 ## 输出规范 - 引用原文用引号后跟书名·卷次 - 引用注疏注明注者 - 自己的解释标“按” - 不确定标“待考”挂载就是把 skill 描述放进模型的系统提示里或者用支持 skill 的框架加载。我用的是 LangChain 的 agent 机制把 skill 作为一个 tool 注册进去。实测下来skill 写得好不好直接决定回答质量。写得含糊模型就乱来写得清楚模型就规矩。5. 常见问题与排查技巧实录5.1 检索不到内容怎么办这是最常见的问题。用户问“《论语》里‘仁’字出现了多少次”检索不到因为库里没有统计信息。或者用户问一个生僻字库里没有对应的注疏。排查思路分三步。第一步确认查询有没有归一化。用户用简体问库里是繁体不归一化肯定搜不到。第二步确认库里有没有相关内容。用关键词直接搜一下如果库里没有那就是数据覆盖问题不是检索问题。第三步确认切块有没有切坏。有时候一句话被切成两半检索命中一半另一半丢了上下文就不完整。解决办法对于统计类问题走结构化查询不走 RAG对于生僻字挂一个字典 API 兜底对于切块问题调整切块策略保证语义完整。5.2 模型胡编出处怎么治这是古籍 RAG 最危险的问题。模型检索到了相关内容但生成的时候把出处编错了比如把《孟子》的话说成《论语》。这种错误比检索不到更可怕因为用户可能信以为真。我的治法有三条。第一强制引用格式要求模型输出时必须带出处不带出处的回答直接判为不合格。第二出处校验生成之后用正则提取出处去库里核对对不上就重新生成。第三降低温度生成时 temperature 设 0.1减少随机性。注意出处校验这一步不能省。我试过不校验模型编出处的概率大概有 5% 到 10%校验之后降到 1% 以下。虽然还有漏网的但已经好很多了。5.3 繁简混用导致的检索失败这个问题很隐蔽。用户用简体问库里是繁体向量模型可能能匹配上但 BM25 匹配不上因为字不一样。混合检索的时候BM25 那一路就废了。解决办法就是前面说的查询归一化。但要注意归一化只用于检索不用于显示。显示的时候还是用原文否则用户看到的引文和原书对不上。5.4 常见问题速查表问题现象可能原因排查方法解决办法检索不到查询未归一化检查繁简加归一化检索不到数据未覆盖关键词直搜补数据出处错误模型编造出处校验强制引用校验上下文断裂切块不当检查切块调整切块策略回答太慢模型太大看推理时间换小模型繁简混用未归一化检查查询检索归一显示原文5.5 几个踩过的坑第一个坑是标点符号。古籍的标点有全角有半角有中文有英文不统一。我一开始没注意检索的时候“。”和“.”对不上漏了一堆结果。后来统一转成中文全角才解决。第二个坑是引号嵌套。古籍里引号套引号很常见切块的时候如果按引号切会切得乱七八糟。我的做法是不按引号切按句号切引号只在显示的时候处理。第三个坑是版本差异。同一句话在不同版本里可能差一个字检索的时候如果只匹配一个版本另一个版本就漏了。我的做法是多版本都入库检索时都召回让模型自己判断。第四个坑是模型幻觉。即使有 RAG模型还是可能编。我的经验是问题越具体幻觉越少。问“这句话什么意思”比问“这本书讲了什么”靠谱得多。所以我在 skill 里加了一条引导用户问具体问题。6. 后续可以怎么扩展第一版跑通之后我列了几个扩展方向。一个是知识图谱层把人物、地点、事件做成实体和关系支持“谁在什么时候见过谁”这类查询。热词里ontology rag和kg知识库说的就是这个。另一个是多模态古籍里有大量插图、拓片、手稿热词里rag知识库能存储图片嘛问的就是这个技术上可行但需要图像 embedding 模型成本高一些。还有一个方向是版本比对自动化。把同一本书的不同版本入库自动标出异文这对校勘工作很有用。热词里book to skill的思路可以扩展成version to skill把版本差异也做成一个技能。我个人在实际操作中的体会是古籍 RAG 这件事数据质量比模型大小重要检索策略比生成技巧重要。你用一个 7B 模型加好的检索效果可能比 70B 模型加烂检索好得多。所以如果你也想做类似的项目先把数据整干净把检索调好再考虑换模型。最后分享一个小技巧建一个测试集。我整理了 50 个古籍问题有问出处的、有问字词的、有问版本的每次改完 skill 都跑一遍看准确率有没有提升。这个习惯帮我避免了很多“改了一个地方坏了另一个地方”的问题。测试集不用大但要覆盖各种问题类型而且要有人工标注的标准答案。
返回列表