📊 中华诗词知识库 · 第六章:人工智能应用集成

Knowledge Base of Chinese Poetry (KBCP) — Chapter 6: AI in Action

🌟GitHub 开源地址:https://github.com/liang1057/Knowledge-Base-of-Chinese-Poetry
🌟 如果资源对你有帮助,欢迎 Star 支持!
📥 JSON数据下载https://download.csdn.net/download/sdust_dx/92826598


🎯 前言

前五章我们完成了数据收集 → 数据清洗 → Schema 设计 → Web 系统 → AI 自动打标,知识库已经"能存、能看、能管、能标"。但"标好标签"只是起点——真正的价值在于:让使用者用自然语言与这 6.2 万首诗词对话

本章是系列的收官之作,聚焦"人工智能的应用层":一个用户问题从输入到回答,背后究竟经过了哪些智能处理?

💡 核心观点:真正的"智能"不是把数据一股脑丢给大模型,而是让大模型在确定的边界内"思考"。本章要讲的,正是一套"确定性 + 语义 + 推理"三者协作的混合智能架构。


一、整体架构:混合智能(Hybrid Intelligence)

一个常见的误区是:RAG 万能,把所有诗词塞进向量库、召回 top-k、丢给 LLM 生成,就完事了。这都是技术不成熟的人的表现,相当于 硬吃LLM, 完全不是正确使用人工智能的方式。
这种方式都是半吊子人工智能,完全没有弄明白LLM的能力:

向量库

距离计算召回

LLM 生成答案

比如,在诗词知识库这个场景里,纯 RAG 有三个致命软肋:

RAG 的软肋 具体表现 本项目的解法
算不了数 "李白写了多少首诗"需要 COUNT(*),向量相似度给不出精确数字 走确定性 SQL 计数快路径
比不了 "李白和杜甫谁写月亮的诗更多"需要 JOIN + 聚合 走 Text-to-SQL 生成聚合查询
容易幻觉 LLM 凭记忆补出"杜甫有 1500 首"之类错误 工具返回结构化事实,LLM 只负责"串讲"

正确的方法应该是:

向量库

LLM 辅助查询

召回答案

LLM 生成答案

本项目的智能化RAG架构是在已有数据库(知识库)的基础上,使用LLM提升智能理解能力:

作者实体

诗作实体 / 找诗

统计类

标签类 / 比较类

分析类

用户自然语言问题

意图分析
别名 / 实体消歧

意图归类
规则分类 7 类

SQL: 查作者信息

SQL: 标题 / 诗句溯源

确定性 COUNT
失败则 SQLAssist

SQLAssist
Text-to-SQL

Agent 中枢
LLM 调度工具

查询诗词 / get_author ...

semantic_search → RAG 向量

ResultFormatter
统一格式化

自然语言回答

一句话总结:确定性事实交给 SQL,语义联想交给向量检索,综合表达与多步推理交给 LLM——各司其职,互不越界。


二、第一关:别名映射与查询理解(实体消歧)

2.1 别名映射 AliasMapper

用户不会规规矩矩地输入"苏轼",他可能说"子瞻"“东坡”“苏东坡”。系统启动时从 author 表一次性加载 name / courtesy_name(字) / art_name(号) / other_names(别名),构建 {别名小写 → 标准名} 的哈希索引:

# KBCP_AliasMapper.py 核心逻辑(示意)
for a in authors:
    self._add_alias(a['name'], 'author', aid)          # 标准名
    for fld in ('courtesy_name', 'art_name', 'other_names'):
        for alias in self._split(a[fld]):
            self._add_alias(alias, 'author', aid, a['name'])  # 子瞻→苏轼

resolve("子瞻") 直接返回 {"matches": [("子瞻","苏轼","author","F0282")]},后续所有路径都基于标准名查询,从根本上消除"同名不同实"的歧义。
同时,通过数据库中的作者信息等,组成最好的答案。
在这里插入图片描述

2.2 查询分类 QueryClassifier

分类器完全基于规则、不调用 LLM,毫秒级、零成本、结果可复现:

优先级 类型 触发特征 路由去向
1 STATS 含"多少/几首/最多/总数" 确定性计数 / SQLAssist
2 COMPARE 含"与/和/对比/区别"且有两实体 SQLAssist 聚合
3 ENTITY_AUTHOR 含"是谁/介绍/生平" SQL 查作者
4 FIND_POEM 像诗句(含标点或 5/7 字句式) SQL 诗句溯源
5 ENTITY_POEM 含《》书名号标题 SQL 查作品
6 TAG_BASED 含"主题/风格/情感/意象" SQLAssist 标签检索
7 ANALYTICAL 其余(赏析、联想等) Agent + RAG

💡 为什么不用 LLM 做分类? 分类是高频、低熵、强模式匹配任务,规则引擎在准确率、延迟、成本上全面优于 LLM,且不会因为模型"心情"而抖动。把 LLM 留给真正需要语义理解的地方。


三、第二关:Text-to-SQL —— 让大模型写查询,但不许乱来

统计、对比、主题检索这类结构化查询,本质是"自然语言 → SQL"。这是 LLM 的强项,但也是幻觉重灾区:它常会编造字段名、写 SELECT *、甚至想 DELETE。本项目的 SQLAssist 用三重约束把这个能力关进笼子里。

3.1 元数据注入:给 LLM 一份"数据库说明书"

系统从第三章设计的 myschema 表读出字段中文名 → 键名 → 类型对照,连同外键 JOIN 路径、检索优先级、禁止事项,一起注入 Prompt:

数据库表结构(字段中文名 -> 字段键名):
  poem: 标题(title: text) | 正文(content: text) | 作者ID(author_id: text) ...
  vocab: 类目(key: text) | 标签(label: text) ...

外键关联规则(固定 JOIN 路径):
  poem.author_id = author.author_id
  poem_tag.poem_id = poem.poem_id
  poem_tag.vocab_id = vocab.vocab_id

禁止事项:
  - 只允许 SELECT,禁止 UPDATE/DELETE/INSERT/DROP/ALTER
  - 禁止 SELECT *(明确列出需要的字段)
  - 涉及标签必须 JOIN poem_tag + vocab

此外还会注入 vocab 表的全部标签取值(防止它写"月亮"而库里只有"月")和别名映射结果(“子瞻"→"苏轼”)。

3.2 生成后校验:把"野 SQL"挡在门外

LLM 生成的 SQL 不直接执行,而是先过校验器:

def _validate(self, sql: str) -> tuple:
    # 1. 是否含危险操作
    danger = ['DROP ','DELETE ','INSERT ','UPDATE ','ALTER ']
    if any(d in sql.upper() for d in danger):
        return False, f"禁止使用 {d.strip()} 操作"
    # 2. 禁止 SELECT *
    if re.search(r'SELECT\s+\*', sql, re.I):
        return False, "禁止 SELECT *,请明确列出字段"
    # 3. 引用的字段是否都在 myschema 中(防字段幻觉)
    valid_cols = {row['column_name'].lower() for row in self._schema}
    for _, col in re.findall(r'(\w+)\.(\w+)', sql):
        if col.lower() not in valid_cols:
            return False, f"字段 '{col}' 不在 myschema 中"
    return True, ''

校验失败时,把错误信息回灌给 LLM 重试(最多 3 次),让它自我修正。最终 SQL 由 execute_readonly_sql 只读执行,彻底杜绝写操作。

💡 这是全章最重要的工程经验:LLM 写 SQL 不可怕,可怕的是"直接执行"。用元数据约束 + 字段白名单 + 只读执行 + 错误重试四道防线,就能把幻觉控制在可接受范围。


四、第三关:RAG 向量检索与语义推荐

开放性问题(“写思乡的诗有哪些”“这首诗表达了什么情感”)不适合 SQL,需要语义检索。

4.1 向量是怎么来的

每首诗词用 作者《标题》正文 拼接成文本块,再用嵌入模型向量化,存进 poem_embedding 表:

方案 模型 特点
主方案 paraphrase-multilingual-MiniLM-L12-v2 多语言、对古诗+现代提问都友好,需下载
兜底 sklearn TF-IDF(字符级 n-gram 1~3) 零下载、纯本地,网络受限也能跑

💡 选多语言模型而非纯中文模型,是因为用户提问常是白话(“写想家的诗”),而诗词是文言,多语言模型在跨风格语义对齐上更稳。TF-IDF 用字符级 n-gram 而非词级,是因为古诗无空格、分词反而引入噪声。

4.2 多源检索与余弦相似度

RAGIndex 同时从诗词(向量)、作者(LIKE)、标签(向量语义匹配) 三个来源召回,合并去重后取 top-k。相似度用余弦:

[
\mathrm{sim}(q, d) = \frac{\vec q \cdot \vec d}{\lVert \vec q \rVert \lVert \vec d \rVert}
]

# KBCP_RAG_Index.py 余弦相似度(示意)
dot = np.dot(query_vec, emb_vec)
norm = np.linalg.norm(query_vec) * np.linalg.norm(emb_vec)
sim = dot / norm if norm > 0 else 0

同一套向量还能做语义推荐:给定一首诗,按余弦相似度排序召回"意境相近"的作品,实现"读这首诗的人也喜欢"。


五、第四关:Agent 中枢与工具调用(Function Calling)

以上三关偏"确定性"。最体现 AI 深度的,是把 LLM 当大脑、把工具当手脚的 Agent 中枢(KBCP_Agent)。

5.1 工具集:给大模型装上"手脚"

LLM 本身不会查数据库,但能"决定调用哪个工具、传什么参数"。系统向 LLM 暴露 7 个结构化工具:

工具 作用 内部确定性保障
search_poem 按诗句片段找出处 SQL LIKE 精确匹配
get_poem_full 返回诗词全文/译文/赏析 主键查询
get_author 查诗人生平字号 别名解析后查表
count_poems 统计收录诗数 COUNT(*) 精确计数
count_poems_by_tag 按主题精确计数 JOIN poem_tag+vocab
compare_by_tag 多诗人主题对比 同一标签口径聚合
semantic_search 语义向量检索 RAGIndex 多源召回
5.2 工具返回"结构化",而非"自然语言"

关键设计:工具返回的是 JSON 结构化字典,不是拼给人看的句子。

# KBCP_Tools.py:工具返回结构化数据供 LLM 综合
return {
    "found": True,
    "author": "李白",
    "total_count": 472,           # 数字由 SQL 保证精确
    "per_label": [{"label":"月","count":472}]
}

这样 LLM 拿到的是"事实原料",而不是"已完成的答案",它只能基于事实综合表达,从机制上杜绝了编造数字。

5.3 近义理解与多步推理

开启"主题词近义理解"后,用户问"写月亮的诗",系统先让 LLM 把"月亮"扩展为 vocab 中相关的近义标签集合(月/羁旅/思乡…),召回更智能;LLM 扩展不可用时,回退到向量语义匹配。

Agent 还支持多步推理MAX_TURNS=5)与多轮对话指代消解:用户问完"李白",接着说"他写月亮的诗多吗","他"会被替换为"李白"再处理。

⚠️ 反幻觉铁律(写进 System Prompt):严禁凭记忆作答,所有事实必须来自工具返回;若工具无结果,如实告知"未查到",绝不可臆测。 这是把通用大模型改造成"可信知识库助手"的灵魂条款。

5.4 多模型编排与降级

KBCP_LLM_Provider 抽象了 Ollama(本地)/ DeepSeek / 智谱 GLM,统一 OpenAI 兼容接口。推理型本地模型(如 deepseek-r1)不支持 function calling,会被自动过滤;云模型按配置优先级串联,上一个不可用自动降级下一个,全部失败时还有本地确定性兜底(直接调 SQL 回答基础问题)。

而且我测试了一个不算是RAG的问题,是一个纯粹的语义理解问题,这个回答也很赞。我感觉这个是纯LLM的回答。有这样的兜底,那就非常棒了。
在这里插入图片描述


六、能力全景与落地权衡

把整套 AI 应用层的能力与适用场景汇总如下:

能力 技术路线 何时走这条路 优势
实体消歧 规则哈希索引 所有问题第一关 毫秒级、零成本
查询分类 规则引擎 所有问题第二关 可复现、不抖动
精确计数 COUNT(*) “多少首” 100% 准确、秒回
结构化检索 Text-to-SQL + 校验 “对比/主题统计” 可聚合、防幻觉
语义联想 向量检索(RAG) “赏析/意境/推荐” 跨字面语义匹配
综合推理 Agent + 工具调用 复杂/多步问题 LLM 推理 + 事实兜底

混合架构 vs 纯 RAG 的取舍:纯 RAG 实现简单,但面对"计数/对比/精确事实"会集体失灵;混合架构工程复杂度更高,却换来可引用、可解释、可追责的回答——这正是知识库区别于"聊天玩具"的本质。


📝 七、小结

设计目标 实现方式
确定性 SQL 计数快路径 + Text-to-SQL 只读执行 + 字段白名单
语义性 多语言 Embedding + 多源 RAG 检索 + 余弦相似度推荐
智能性 Agent 中枢 + 7 个结构化工具 + 多步/多轮推理
可控性 别名消歧 + 规则分类 + 反幻觉铁律 + 模型自动降级
可落地 Ollama/DeepSeek/智谱多后端 + TF-IDF 纯本地兜底

至此,中华诗词知识库系列六章全部完成:

第1章 数据收集        → 4600+ 诗人 / 62000+ 诗词的原始积累
第2章 数据清洗        → 分句、去重、元数据补全、入库 SQLite
第3章 数据结构设计     → 五表联动 + 受控词表,标准化与可扩展
第4章 Web 与 AI 打标   → Flask 系统 + LLM 多维自动标注
第5章 Web 实现与实例   → 四栏浏览、后台管理、智能问答落地
第6章 人工智能的应用   → 混合智能:消歧 / 分类 / Text-to-SQL / RAG / Agent

从"抓数据"到"会对话",我们走完了一条数据工程 → 知识工程 → 智能应用的完整路径。知识库的价值,不在于囤了多少文本,而在于当有人提问时,它能给出可引用、可解释、可信赖的答案——这就是人工智能在这套系统里真正的落点。

🌟 如果本文对你有帮助,欢迎 Star 支持!

GitHub 开源地址:https://github.com/liang1057/Knowledge-Base-of-Chinese-Poetry

CSDN 专栏:中华诗词知识库(KBCP)系列文章

📧 交流邮箱:liang1057@163.com

版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。

更多推荐