1. 缘起:一个古籍爱好者的技术冲动
1.1 为什么我会盯上“古籍”这个方向
先说清楚这个项目到底要做什么。古籍 skill 项目,核心目标是把中国历代典籍——经史子集、方志笔记、金石碑帖——通过一套可复用的技术流程,做成一个能被现代 AI 工具直接调用的知识技能包。你可以把它理解成一个“古籍领域的专属助手”:你问它《说文解字》里某个字的训释,它能从原文里找到依据;你问它某段话出自哪本书哪一卷,它能给出处;你甚至可以让它帮你把古籍里的生僻字整理成字体文件。这个项目适合谁?适合对古籍有兴趣但没时间啃原典的普通读者,适合做古籍数字化但缺技术手段的图书馆员,也适合想用 RAG 技术做垂直领域知识库的开发者。
我盯上这个方向,原因很直接。我自己断断续续读了十几年古籍,从《论语》到《史记》再到《资治通鉴》,书买了不少,真正读完的没几本。问题不在于没兴趣,而在于检索效率太低。我想查一个典故的出处,得翻好几本书;我想知道某个字在先秦两汉的用法,得去翻《说文》《尔雅》《广韵》。这些工具书都是纸质或者扫描 PDF,搜索基本靠人眼。后来大语言模型出来了,我试过直接问通用模型古籍问题,结果它经常一本正经地胡说八道——引文对不上原文,出处张冠李戴,生僻字直接跳过。这就是我决定自己动手的起点:通用模型靠不住,那就给它配一个古籍专属的知识底座。
这个底座的技术路线,我选的是RAG(检索增强生成)。为什么不是微调?因为古籍的体量太大,微调成本高、更新难,而且微调后的模型依然可能编造引文。RAG 的思路是“先查再答”:用户提问后,系统先从古籍原文库里检索出相关段落,再让模型基于这些段落组织回答。这样引文有出处,答案可追溯。项目里我给它起的代号是chinese-classics-advisor,对应的检索模块叫guji-rag。这两个名字后面会反复出现,前者是整个 skill 的对外接口,后者是内部的知识检索引擎。
1.2 一个具体的痛点场景
举个我实际遇到的例子。我在读《世说新语》的时候看到“管中窥豹,时见一斑”这句话,想确认它最早的出处是不是《世说新语》。通用模型告诉我出自《晋书·王献之传》,我再去查《晋书》,发现原文是“此郎亦管中窥豹,时见一斑”,确实有这句话,但《世说新语》里也有类似表述。到底谁先谁后?通用模型给不出可靠答案,因为它没有版本学和年代学的概念。如果有一个古籍 RAG 系统,我可以直接检索“管中窥豹”在全部入库典籍中的出现位置,按成书年代排序,答案一目了然。这就是古籍 skill要解决的核心问题:把散落在不同典籍里的知识,通过检索技术重新组织起来,让用户能按需调用。
这个需求不是我一个人有。我在几个古籍爱好者社群里做过小范围调研,发现大家的痛点高度一致:查字、查词、查典故、查出处、查版本,这五件事占了日常需求的八成以上。而现有的工具要么是纯检索(比如某些古籍数据库,只能关键词匹配,不懂语义),要么是纯生成(比如通用大模型,懂语义但不可靠)。RAG 正好卡在中间:检索保证准确性,生成保证可读性。这就是我选择这条技术路线的根本原因。
1.3 项目整体架构的初步设想
在动手之前,我先画了一个粗略的架构图——不是用工具画的,就是纸上随手写的。整个系统分四层:数据层、检索层、生成层、接口层。数据层负责古籍文本的采集、清洗、切分、标注;检索层负责把用户问题转成向量,从数据层里找出最相关的段落;生成层负责把检索结果组织成通顺的回答;接口层负责对接不同的 AI 工具平台,让这个 skill 能被调用。这个分层不是拍脑袋定的,而是参考了业界做 RAG 项目的通用实践。分层的好处是每一层可以独立迭代:数据层可以不断扩充典籍,检索层可以换更好的嵌入模型,生成层可以调提示词,接口层可以适配新平台。互不干扰,方便维护。
这里要特别说明一点:古籍 RAG 和通用 RAG 有本质区别。通用 RAG 处理的是现代文本,段落边界清晰,语义完整。古籍不一样,它有大量异体字、通假字、避讳字,还有注疏混排、双行小字、眉批夹注这些特殊排版。如果直接拿通用文本切分工具去切古籍,切出来的段落经常是断头的。所以我在数据层花了大量时间做古籍专用的文本清洗和切分规则,这部分后面会详细讲。另一个区别是检索粒度。通用 RAG 通常按段落检索,但古籍的检索单元可能需要细到“一句话”甚至“一个词条”,因为用户查典故往往只关心那一句。粒度太粗,检索结果里混入无关内容;粒度太细,上下文丢失,模型看不懂。这个平衡点我调了很多次,最后定在“以注疏单位为基本检索单元,以段落为上下文补充单元”。这个决策背后的逻辑,后面也会展开。
2. 核心细节解析:古籍 RAG 到底难在哪
2.1 古籍文本的特殊性:不是所有文本都叫“语料”
做古籍 RAG,第一个拦路虎就是文本本身。现代文本的语料处理已经有一套成熟流程:分句、分词、去停用词、向量化。但古籍不行。我拿《论语》开篇“学而时习之,不亦说乎”举例,这句话里至少有三个坑。第一,“说”是通假字,通“悦”,如果按现代汉语处理,“说”会被归到“说话”的语义空间,检索“喜悦”相关的内容就找不到这句。第二,“时”在先秦是“按时”的意思,不是“时常”,语义偏移会导致检索偏差。第三,这句话在不同版本里可能写作“学而时习之,不亦说乎”或“学而时习之,不亦悦乎”,异体字不统一,检索会漏。这三个坑,每一个都需要在数据层做专门处理。
我的处理方案是三层归一化。第一层是字形归一化:把异体字、通假字、避讳字统一映射到标准字形。比如“说”和“悦”在检索时都映射到“悦”的语义空间,但原文保留“说”的字形,只在检索索引里做映射。第二层是词义归一化:给每个词标注它在具体语境下的义项,比如“时”在这里标注为“按时”,而不是“时常”。第三层是版本归一化:同一部书如果有多个版本,以通行本为基准,其他版本的异文作为附注保留。这三层做完,检索的准确率会有质的提升。实测下来,不做归一化直接检索,召回率大概只有六成;做完归一化,能到八成五以上。这个提升幅度,值得在数据层多花时间。
2.2 切分策略:为什么不能按句号切
古籍的标点是个大问题。很多古籍原本没有标点,现代整理本加了标点,但标点习惯和现代汉语不一样。比如《史记》里“项庄舞剑,意在沛公”这句话,现代标点会切成两个分句,但在古籍语境里它是一个完整的典故单元,切开之后检索“项庄舞剑”可能只召回前半句,上下文丢了。更麻烦的是注疏混排。古籍的注疏经常是双行小字夹在正文中间,如果按现代排版切分,注疏和正文会混在一起,检索时正文和注释分不清。我试过直接用通用文本切分工具处理《十三经注疏》,切出来的段落有一半是正文和注疏混在一起的,根本没法用。
后来我定了一套古籍专用切分规则,核心是三条。第一条,以注疏单位为基本切分单元。正文和注疏分开切,正文一个单元,对应的注疏一个单元,两者通过 ID 关联。检索时如果命中注疏,可以顺带把正文带出来;如果命中正文,可以顺带把注疏带出来。第二条,保留典故完整性。遇到典故、成语、引文,不按标点切,按语义单元切。比如“项庄舞剑,意在沛公”整体作为一个单元,不拆开。第三条,段落长度动态调整。短段落(比如一句话)合并到相邻段落,长段落(比如超过五百字)按语义再切分。这套规则不是一次定型的,我前后改了七八版,每改一版就拿几十个测试问题跑一遍,看召回率和准确率的变化。最终定下来的版本,在测试集上的召回率是百分之八十七,准确率是百分之八十二。这个数字不算完美,但已经能用了。
2.3 嵌入模型的选择:古籍语义怎么向量化
切分完之后,下一步是把文本转成向量。这一步的核心是嵌入模型的选择。通用嵌入模型(比如那些在海量现代文本上训练的模型)对古籍语义的捕捉能力有限。我做过对比测试:拿“克己复礼”这个词去检索,通用模型返回的结果里混入了大量现代政治文本,因为“克己”在现代汉语里有“克制自己”的意思,和古籍语境下的“约束自身以符合礼制”有偏差。而专门在古籍语料上微调过的嵌入模型,返回的结果就精准得多。但专门微调一个嵌入模型成本太高,我退而求其次,选了在中文古籍上有一定预训练基础的通用模型,然后在检索层做语义扩展来弥补。
语义扩展的具体做法是:给每个检索词自动扩展同义词、近义词、相关典故。比如用户搜“克己复礼”,系统自动扩展出“约束自身”“合乎礼制”“论语颜渊”等相关词,一起参与检索。这样即使嵌入模型对古籍语义捕捉不够精准,也能通过扩展词提高召回率。实测下来,加语义扩展比不加,召回率提升约十五个百分点。这个方案的好处是成本低、可迭代,坏处是扩展词的质量依赖词表维护。我目前的做法是人工维护核心词表 + 自动扩展边缘词,核心词表覆盖了五百多个高频古籍概念,边缘词通过同义词库自动扩展。这个工作量不小,但值得。
2.4 检索策略:向量检索不够,还得加关键词
纯向量检索有个问题:对精确匹配不敏感。用户搜“《说文解字》卷十四”,向量检索可能返回一堆语义相关但卷次不对的段落。古籍检索里,精确匹配非常重要,因为用户经常查的是具体出处、具体卷次、具体字条。所以我在检索层做了混合检索:向量检索负责语义召回,关键词检索负责精确召回,两路结果合并后重排序。重排序的规则是:精确匹配优先,语义匹配次之,两者都命中的最高。这个策略听起来简单,但调参花了不少时间。向量检索的权重和关键词检索的权重,我试过从 1:9 到 9:1 的各种组合,最后定在 4:6。关键词略高一点,因为古籍检索里精确性比语义泛化更重要。这个比例不是绝对的,不同典籍类型可能需要微调,比如查字词的时候关键词权重可以更高,查典故的时候向量权重可以更高。
3. 实操过程:从零搭建 guji-rag 的关键步骤
3.1 数据采集:古籍文本从哪里来
数据采集是第一步,也是最耗时的一步。我的原则是优先用公开的、质量有保障的数字化古籍。具体来源分三类。第一类是公版影印本的 OCR 结果,比如某些图书馆公开的扫描版古籍,OCR 之后人工校对。第二类是已经数字化的古籍数据库,这些数据库通常有 API 或者批量导出功能,但要注意版权和使用条款。第三类是人工录入,针对那些没有数字化版本的小众典籍。这三类来源里,第一类和第二类是主力,第三类是补充。我目前入库的典籍大概有三百多部,覆盖经史子集四部,核心典籍(比如《论语》《孟子》《史记》《汉书》)都有多个版本对照。
采集过程中最大的坑是OCR 错误。古籍的 OCR 比现代文本难得多,因为古籍字体复杂、版式多样、还有大量异体字。我试过直接用通用 OCR 工具处理古籍扫描件,错误率高达百分之十五以上,主要错误集中在异体字、生僻字和注疏小字上。后来我的做法是OCR + 人工校对 + 规则修正三步走。OCR 先跑一遍,人工校对重点段落,规则修正处理常见错误模式(比如“曰”被识别成“日”,“己”被识别成“已”)。这三步做完,错误率能压到百分之三以下。这个错误率是底线,再高就会严重影响检索质量。因为 RAG 的检索是基于文本的,文本错了,检索必然错,生成也必然错。所以数据采集阶段千万不要图快,宁可慢一点,也要保证文本质量。
3.2 文本清洗:异体字、通假字、避讳字的处理
文本清洗是数据层的核心环节。我前面提到的三层归一化,具体实现是这样的。字形归一化用的是一个自定义的映射表,把常见异体字、通假字、避讳字映射到标准字形。这个映射表我参考了《异体字字典》《通假字汇》等工具书,加上自己阅读中积累的案例,目前收录了三千多组映射关系。词义归一化用的是义项标注,给每个词在具体语境下标注义项编号,检索时按义项匹配。版本归一化用的是版本对照表,同一部书的不同版本,以通行本为基准,异文作为附注。这三层做完,文本的规范性会大幅提升。
这里要特别提醒一个坑:避讳字处理要谨慎。古籍里的避讳字(比如避某朝皇帝名讳而改字)如果直接还原,可能改变原文面貌;如果不还原,检索时又会漏。我的做法是保留原文避讳字形,但在检索索引里做还原映射。这样原文可读性和检索召回率都能兼顾。这个方案不是我想出来的,是参考了古籍数字化领域的通行做法,实测有效。
3.3 向量化与索引构建:参数怎么调
向量化和索引构建是检索层的基础。我用的嵌入模型是中文预训练模型,维度是 768。为什么选 768 而不是更大的维度?因为古籍文本的语义复杂度相对现代文本更低,768 维已经能捕捉主要语义特征,再大边际收益递减,而且存储和检索成本会上升。索引构建用的是近似最近邻搜索,具体参数我调过几轮。关键参数是聚类中心数和探测数:聚类中心数太少,检索速度快但召回率低;太多,召回率高但速度慢。我最后定的是聚类中心数 4096,探测数 64。这个组合在测试集上的表现是:单次检索耗时约 120 毫秒,召回率百分之八十七。120 毫秒对交互式应用来说是可以接受的,如果追求更快,可以降到 2048 个聚类中心,召回率会掉到百分之八十二左右。这个取舍看具体场景。
3.4 生成层:提示词怎么设计才不胡说
生成层的核心是提示词设计。RAG 的生成不是让模型自由发挥,而是让模型基于检索结果组织回答。我的提示词模板分三部分。第一部分是角色设定:告诉模型它是一个古籍助手,回答要严谨,引文要准确。第二部分是检索结果注入:把检索到的古籍段落按相关度排序后注入提示词,每段标注出处。第三部分是回答格式要求:要求模型先给出结论,再列出处,最后补充相关背景。这个模板我改了十几版,每版都拿测试问题跑,看模型是否编造引文、是否遗漏出处、是否答非所问。目前这版的引文准确率在百分之九十五以上,也就是说一百次回答里只有不到五次会出现引文错误。这个水平已经比通用模型好太多了。
但提示词不是万能的。有些问题模型还是会胡编,比如问它“某部典籍的成书年代”,如果检索结果里没有明确年代信息,模型可能会根据常识推测,推测就可能错。我的应对策略是在提示词里加一条硬规则:如果检索结果不足以回答问题,直接说“根据现有资料无法确定”,不要推测。这条规则加进去之后,编造率明显下降。实测下来,加这条规则比不加,编造率从百分之十二降到了百分之三以下。这个规则看起来简单,但非常有效,建议做 RAG 的朋友都加上。
4. 常见问题与排查技巧实录
4.1 检索结果不相关怎么办
这是 RAG 项目最常见的问题。用户问“《诗经》里有哪些描写战争的篇目”,检索结果返回了一堆《诗经》里描写祭祀的篇目。排查思路分三步。第一步,检查查询扩展词。如果扩展词跑偏了,比如把“战争”扩展成了“征伐”“兵戈”之外还扩展了“祭祀”,那检索结果必然跑偏。第二步,检查嵌入模型。如果嵌入模型对“战争”和“祭祀”的语义区分度不够,也会导致误召回。第三步,检查切分粒度。如果切分太粗,一个段落里既有战争描写又有祭祀描写,检索时整段召回,用户看到的就是不相关的内容。我的经验是,八成以上的不相关问题出在查询扩展和切分粒度上,嵌入模型的问题反而少。所以排查顺序应该是:先看扩展词,再看切分,最后看模型。
4.2 引文对不上原文怎么排查
引文错误是 RAG 的另一个高发问题。用户问“《论语》里‘学而时习之’的下一句是什么”,模型回答“不亦说乎”,但引文标注的出处是《孟子》。这种错误排查起来分两步。第一步,检查检索结果里有没有正确出处。如果检索结果里有《论语》的段落,但模型引用了《孟子》,那是生成层的问题,提示词需要加强出处约束。第二步,检查检索结果里有没有错误出处。如果检索结果里混入了《孟子》的段落,那是检索层的问题,需要调整检索策略。我的经验是,引文错误里六成是生成层问题,四成是检索层问题。生成层问题靠提示词解决,检索层问题靠混合检索和重排序解决。
4.3 生僻字检索不到怎么处理
生僻字是古籍 RAG 的特有难题。用户搜“龘”字,检索结果为空,因为嵌入模型的词表里可能没有这个字。我的处理方案是生僻字映射 + 字形检索。生僻字映射是把生僻字映射到常用字或者 Unicode 编码,检索时用映射后的编码去匹配。字形检索是直接按字形特征检索,不依赖语义向量。这两个方案结合使用,能覆盖大部分生僻字检索场景。实测下来,生僻字检索的召回率从不到三成提升到了七成以上。当然,这个方案也有局限,比如某些生僻字没有常用字映射,字形检索的准确率也不够高。这部分我还在持续优化。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 检索结果不相关 | 查询扩展跑偏 | 检查扩展词列表 | 调整扩展词权重或人工审核 |
| 检索结果不相关 | 切分粒度太粗 | 检查切分单元长度 | 细化切分规则 |
| 引文对不上原文 | 生成层出处约束弱 | 检查提示词模板 | 加强出处标注要求 |
| 引文对不上原文 | 检索层混入错误出处 | 检查检索结果排序 | 调整混合检索权重 |
| 生僻字检索不到 | 嵌入模型词表缺失 | 检查生僻字映射 | 加字形检索兜底 |
| 回答编造信息 | 提示词缺硬规则 | 检查提示词 | 加“无法确定”规则 |
4.5 几个踩过的坑和实操心得
第一个坑是贪多求全。一开始我想把所有能找到的古籍都入库,结果数据质量参差不齐,检索效果反而下降。后来我改成精选核心典籍,保证数据质量,效果明显提升。这个教训是:RAG 项目里,数据质量比数据数量重要得多。第二个坑是忽视版本差异。同一部书的不同版本,文字有差异,如果不做版本归一化,检索时会漏掉很多相关内容。第三个坑是提示词太长。我一开始把提示词写得很长,想把所有规则都塞进去,结果模型反而抓不住重点。后来精简到核心规则,效果更好。提示词不是越长越好,而是越精准越好。第四个坑是不做测试集。我一开始凭感觉调参,调来调去不知道有没有变好。后来建了一个两百题的测试集,每次调参都跑一遍,看召回率和准确率的变化,调参才有方向。这个测试集我建议每个做 RAG 的人都建一个,哪怕只有几十题,也比凭感觉强。
5. 这个项目后续还能怎么扩展
5.1 从文本到多模态:古籍图像怎么入库
目前 guji-rag 处理的是纯文本,但古籍还有很多图像资料,比如碑帖、写本、刻本的书影。这些图像里包含大量文本信息,如果能入库,检索范围会大幅扩展。技术路线是图像 OCR + 文本对齐 + 多模态检索。图像 OCR 把书影转成文本,文本对齐把 OCR 结果和已有文本库对齐,多模态检索让用户可以用图像搜文本,或者用文本搜图像。这部分我还在调研阶段,主要难点是古籍图像的 OCR 准确率还不够高,尤其是写本和碑帖。但方向是明确的,后续会逐步推进。
5.2 从检索到知识图谱:古籍关系怎么建模
RAG 解决的是“查得到”的问题,但古籍里还有大量“关系”信息,比如人物关系、地理沿革、职官变迁、典籍引用关系。这些关系用 RAG 表达不够直观,更适合用知识图谱。我的设想是在 RAG 基础上叠加一个轻量级知识图谱,把古籍里的人物、地点、事件、典籍作为节点,把它们之间的关系作为边。用户查询时,RAG 负责召回文本,知识图谱负责展示关系。两者结合,既能查到原文,又能看到关系网络。这个扩展的技术难度不小,但价值很大,尤其是对做古籍研究的用户来说。
5.3 从单机到服务:怎么让更多人用上
目前这个项目跑在我自己的机器上,只有我自己在用。后续如果想开放给更多人用,需要做服务化改造。核心工作包括:把检索层和生成层拆成独立服务,加缓存层提高并发能力,加用户管理和权限控制,加日志和监控。这些是工程化的工作,技术难度不大,但工作量不小。我的计划是先做一个小范围的内测版本,邀请几十个古籍爱好者试用,收集反馈后再决定是否扩大。做垂直领域的 AI 工具,最怕闭门造车,一定要尽早让真实用户用起来,才能发现真正的问题。
5.4 一个具体的扩展场景:古籍字体生成
热词里有人问“怎么把古籍中的字做成字体”,这其实是一个很自然的扩展方向。古籍里有大量现代字库没有收录的字,尤其是异体字和生僻字。如果能把 guji-rag 里入库的古籍字形提取出来,做成字体文件,对古籍数字化和古籍出版都有价值。技术路线是字形提取 + 矢量化和 + 字体封装。字形提取从古籍图像或文本里提取单字字形,矢量化把位图转成矢量轮廓,字体封装把矢量轮廓打包成字体文件。这个扩展的技术门槛主要在字形提取和矢量化,需要一些图像处理和字体工程的知识。我目前只是有个初步想法,还没有动手做,但觉得值得尝试。
5.5 最后分享一个小技巧
如果你也想做古籍 RAG,我建议从一部书开始,不要贪多。选一部你熟悉的典籍,比如《论语》或者《道德经》,把它的数据层、检索层、生成层完整跑通一遍。跑通之后,你会发现很多问题在单部书的时候就能暴露出来,比如切分规则、检索策略、提示词设计。这些问题在单部书的时候解决成本低,等入库了几百部书再发现,改起来就麻烦了。先做深,再做广,这是我做这个项目最大的体会。另外,测试集一定要早建,哪怕只有二十题,也能帮你判断每次调整是变好了还是变坏了。没有测试集,调参就是盲人摸象。这两个建议看起来简单,但真正做到的人不多,做到了就能少走很多弯路。