结构化 vs 非结构化知识:两个世界的碰撞与融合

知识库项目 kickoff 会议,老李站在投影仪前,PPT 翻到了第三页——一个孤零零的文件夹截图,上面写着“公司知识库/”。

“我看这事儿没那么复杂,”老李端着保温杯,语气斩钉截铁,“把公司所有的 Word、PDF、Excel 全放进一个文件夹,然后让 RAG 去检索,不就完了?我们以前搞文档管理,建个共享目录就行了,哪那么多讲究?”

小王在下面举起手,表情像憋了很久。

“老李,那员工的数据库记录呢?客服系统的 FAQ 呢?Slack 上的项目讨论呢?”

“那些……也导出成 PDF 塞进去嘛。”老李满不在乎。

“老李你想啊,知识跟人一样,有的有身份证,有的没有。你把有身份证的和没有的扔进一个房间,想找人的时候能找得准吗?”

老李的保温杯在半空中停了。


有身份证的知识:结构化的世界

“身份证?”老李把保温杯放下,“知识还分有没有身份证?”

“咱们公司员工信息表,”小王走到白板前画了个表格,“姓名、工号、部门、入职日期——每个字段都有明确的类型和约束。工号是 6 位数字,部门必须在预设列表里选,入职日期不能是未来。这种知识就像有身份证的人,你随时知道它在哪、长什么样、跟谁有关联。”

老李点点头:“数据库嘛,这个我懂。”

“数据库就是结构化知识的家。一行是一条记录,一列是一个属性。你想查‘基础架构部所有 2023 年以后入职的员工’,SQL 一写,毫秒级出结果。为什么这么快?因为数据整齐啊,每一列都是精准的索引。”

小王在屏幕上敲了几行伪代码:

python
# 结构化知识:员工信息,字段清晰,规则明确
employees = [
    {"姓名": "老李", "工号": "E0042", "部门": "基础架构部", "入职": "2015-03-12"},
    {"姓名": "小王", "工号": "E0198", "部门": "基础架构部", "入职": "2022-07-18"},
    {"姓名": "小张", "工号": "E0215", "部门": "前端组",     "入职": "2024-01-08"},
]

# 秒级查询:基础架构部所有 2023 年后入职的员工
new_hires = [
    e for e in employees
    if e["部门"] == "基础架构部" and e["入职"] >= "2023-01-01"
]
# 结果:小王,精准命中,毫无歧义

“这个查询没有模糊空间。部门叫‘基础架构部’就是‘基础架构部’,不存在‘大概是基础架构吧’这种回答。结构化知识最大的优点就是精确——能排序、能过滤、能聚合、能做数学运算。”

老李若有所思:“那按你这么说,咱们最好把所有知识都搞成结构化?”


流浪的知识:非结构化的混沌

“老李,你觉得你们上周的周会纪要,能变成表格吗?”

老李一愣。他们上周讨论的是数据库迁移方案,会议纪要里写着“老王认为 Postgres 更适合,但老赵担心迁移成本,最后老李拍板先做 POC”。这段讨论的上下文、语气、纠结的过程——哪一列能装下?

“装不下,”小王替他回答了,“这就是非结构化知识。它没有固定的字段,没有预设的分类,就像流浪汉一样,身上揣着宝贵的信息,但没有固定地址。”

小王在白板上写下几个例子:会议纪要、邮件往来、Slack 聊天记录、视频会议录音、产品白皮书 PDF。

“这些内容,你说它有没有价值?太有了。你问‘上次数据库迁移方案讨论的结果是什么’,标准答案就躺在某份会议纪要里。但你要靠 SQL 去搜?没门。因为‘结果’这个词不在纪 要里,纪要里写的是‘老李拍板先做 POC’。”

“那 RAG 不正好能解决这个问题?”老李反问,“向量检索,语义匹配,不管你怎么说都能找到。”

“能,但效果取决于你怎么处理这些‘流浪知识’。你把一份 50 页的 PDF 整体向量化,里面同时讲数据库迁移、年会安排、培训计划。用户问迁移方案,向量相似度可能把‘数据库’跟‘数据库营销培训’混在一起——因为它们都有‘数据库’这个词。”

老李沉默了。

python
# 非结构化知识:一篇会议纪要,信息丰富但毫无章法
meeting_notes = """
2025年11月3日 数据库迁移讨论
参会:老李、老王、老赵、小王
老王认为Postgres扩展性更好,老赵担心迁移脚本的维护成本。
老赵说:上次ERP迁移搞了三个月,这次能不能不折腾了。
小王提出先做POC,评估真实性能差异。
老李拍板:下周一前老王搭环境,小王写测试用例,POC结果出来再定。
(中间聊了15分钟年会抽奖的事,略)
结论:POC优先,两周后决策。
"""

# 一刀切向量化:年会抽奖的事也会被编码进去
# 检索"数据库迁移决策"时,可能干扰相关性判断

有暂住证的:半结构化知识

“那 Markdown、JSON、YAML 呢?”老李指着小王屏幕上的代码,“这些不算结构化?”

“这是半结构化,属于‘有暂住证但没正式户口’那类。它有结构,但结构是自描述的,没有强约束。”

小王打开一个示例文件:

python
# 半结构化知识:有标签、有分段,但没有强约束的 Schema
knowledge_entry = {
    "title": "Postgres 连接池配置指南",
    "author": "老王",
    "updated": "2025-10-28",
    "tags": ["数据库", "Postgres", "配置"],
    "content": """
## 适用版本
Postgres 14+

## 推荐配置
max_connections = 200
pool_size = 25

## 注意事项
生产环境务必配合 pgBouncer 使用。
""",
    "related_docs": ["数据库迁移方案", "pgBouncer 部署文档"]
}

# 有结构:可以按 tags 过滤、按 updated 排序
# 非结构:content 内部仍然是自然语言,需要向量检索

“你看这个 JSON,外层有 title、author、tags,这是结构化的部分,我可以快速过滤‘老王写的所有数据库相关文档’。但 content 字段里还是自然语言,要靠语义搜索。这就是半结构化的典型——外层帮你做过滤,内层靠向量做匹配。”

老李端详着那段 JSON,仿佛在看某种新型生物:“所以我们公司大部分文档,其实是半结构化?”

“差不多。技术文档是 Markdown + YAML 头部,FAQ 是 JSON 格式,API 文档是 OpenAPI spec——它们都有某种结构,但不是数据库表那种严格的约束。”


三种知识的处理管道

老李把白板上的内容来回看了几遍,然后问:“那你的意思是,我们不能把三种知识全倒进一个池子?”

“倒进一个池子也行,但你的检索管道得在流进来的那一刻,针对不同类型做不同的预处理。”

小王在玻璃上画了三条管道:

图:不同类型的知识,从不同入口进来,走不同的处理管道,最后汇聚到统一接口

“结构化知识走精确索引管道,按字段建倒排索引,查‘部门=基础架构部’就是精准命中。半结构化知识走混合管道,外层元数据做过滤,内层内容做向量检索。非结构化知识走语义管道,分块、清洗、向量化,全靠语义匹配。”

小王补充道:“一个用户提问‘基础架构部最近半年的技术决策’,系统可以在结构化数据里精准定位部门范围,在半结构化文档里按 tags 和 updated 过滤出技术类文档,在非结构化会议纪要里语义搜索决策相关内容,最后把三者拼在一起。这才是企业级 RAG 该有的样子。”

老李靠在椅背上,沉默了好一会儿。保温杯里的枸杞已经全部沉底,他也没注意到。

“那……这个管道谁来搭?”


能不能用一种格式一统天下?

“我一直有个想法,”老李突然说,“能不能把所有知识统一成一种格式?比如全转成 JSON?或者全转成 Markdown?”

“老李,你这个想法我也有过,后来发现行不通。”小王笑着说,“你想想,把员工信息表的每一行变成一个 Markdown 段落——‘老李,工号 E0042,隶属于基础架构部’——有意义吗?没有。因为表格的价值就在于字段之间可以运算、可以 JOIN、可以聚合。你把它变成自然语言,等于把它的‘超能力’废掉了。”

“反过来也一样,你把一份会议纪要硬塞进表格的某个列,那列叫‘结论’,你只能填一句。但实际的讨论过程、双方的观点交锋、老赵的犹豫——这些上下文全丢了。”

老李叹了口气:“所以还是得多种格式共存。”

“共存,但通过统一的接口对外暴露。”小王总结道,“底层各有各的存储和索引方式,上层给 RAG 的是统一的检索入口。用户不需要知道一条答案来自结构化还是非结构化,他只需要得到正确的答案。”

“说到接口,”老李把保温杯拿起来,终于喝了一口,“你刚画的那些管道,有现成的参考实现吗?”

小王咧嘴笑了:“我就知道你要问这个。下周一我把三个管道的原型代码都准备好,咱一条一条过。”

“嗯……行吧。”老李站起来,走了两步又停下,“那啥,我回去把我们部门的文档先按你这个分一下类。结构化的放数据库,半结构化的整理 tags,非结构化的……先分块再说。”

走到门口,他又转过身:“小王,你说我这二十年的文档管理经验,算结构化还是非结构化?”

“那属于文化传承,老李。放在知识图谱里,叫‘隐性知识’。”

会议室里爆发出一阵笑声。老李摆摆手走了,保温杯里的枸杞晃晃悠悠,像在点头。

Graph RAG:当你的知识有了'关系网'
知识库 vs RAG:它们不是同一个东西!