跳到主要内容

用 LLM 抽结构化数据建图 — 合同、Pydantic 与实体消解

这一章讲三件事: 向量 RAG 在法律文档上的两个死穴; 用 LLM 抽结构化数据的完整链路(数据模型 → 抽取 → 导图); 抽完之后的脏活——实体消解,以及为什么它没有通用解。 这是全书从「用图」转到「建图」的枢纽章。

1. 先看死穴:向量检索在合同上的两种翻车

死穴一:跨文档混块

问「和 ACME 那份许可合同的付款条款是什么」——向量检索按相似度取前 k 块, 它不管块来自哪份合同。「payment terms」这种词每份合同里都有, 捞回来的块可能来自 ACME 的合同、也可能来自完全无关的合同; LLM 拿着混料作答,产出似是而非的综合答案1

死穴二:统计计数

问「我们现在和 ACME 有几份有效合同?」——要先按「有效」过滤、再计数。 这是标准的商业统计问题,而文本嵌入(前面说的把文字变成数串的产物)的设计目的只是取回语义相似的内容, 过滤、排序、聚合不是它的活2

两个死穴指向同一个缺口:文档的边界和结构在向量库里不存在。 补法是把结构显式建出来——这一章就是建结构的施工手册。

2. 核心原理:结构化抽取的四步

先交代历史坐标:从文本抽结构不是新需求,以前叫 information extraction (信息抽取),要多个机器学习模型配合,贵到只有大机构玩得起; LLM 把门槛打了下来——现在一句话提示就能抽3。OpenAI 甚至把 「按预定格式输出」做成了 API 的内置能力(Structured Outputs): 你声明想要的输出结构,API 保证返回严格符合4

第一步:定义数据模型——三件套决定抽取质量

用 Python 的 Pydantic 库声明「要从合同里抽什么」。 每个字段 = 字段名 + 类型 + 描述。书里的合同模型节选5:

class Contract(BaseModel):
contract_type: str = Field(..., description="合同类型。",
enum=["Service Agreement", "Licensing Agreement",
"Non-Disclosure Agreement (NDA)", ...]) # enum 锁定取值
parties: List[Organization] = Field(...) # 嵌套对象列表
effective_date: str = Field(..., description="生效日期。用 yyyy-MM-dd 格式。")
end_date: Optional[str] = Field(...) # 可能缺
total_amount: Optional[float] = Field(...) # 可能缺

三条纪律,每条都对应一种真实翻车:

  1. 可能缺失的字段必须标 Optional。 书里说得很重:不标, 有些 LLM 会编一个值来填空6。合同没有总金额是常态—— 模型不该因为「字段在那里」就造一个数出来;
  2. 取值可穷举的用 enum 锁死(合同类型就五种);取值不可穷举的 (组织角色:provider/client/supplier…)只在描述里给示例,不锁7;
  3. 描述写清格式与标准。 日期没有原生类型,要用描述钉住 yyyy-MM-dd 格式8; 国家字段直接指示「用两位字母 ISO 标准」,模型认识 ISO,照办9

另外两条工程常识:嵌套别太深(影响性能)10;系统消息里写明领域 和「这份输出将被用来干什么」;除这些之外,怎么调多半靠试11

第二步:抽取——一次调用,温度归零

抽取函数本身极简:系统消息 + 合同原文 + 上面定义的数据模型, 温度——生成随机度的旋钮——设 0;同一份合同抽两次要得到同一个结果12

第三步:导入图数据库

先定图模型(数据在图里长什么样):合同场景三个实体—— Contract 节点(类型/日期/条款/金额)、Organization 节点、Location 节点, 组织用带 role 属性的 HAS_PARTY 关系挂到合同上,组织再连位置13。 导入前给唯一标识建唯一约束(防重复),然后一条 Cypher 语句灌进去14

一个书里特意点名的坑:示例导入用随机 UUID 当合同 ID, 这条查询不幂等——跑两遍,库里就有两份一样的合同15。 (幂等:同一个操作执行一次和执行多次,结果相同。)

第四步:实体消解——同一个东西的三个名字

抽完了、灌进去了,还没有完。同一实体在不同文档里常以不同写法出现, 书里的例子:「UTI Asset Management Company」「…Company Limited」「…Company Ltd」 ——三个节点,一家公司16

实体消解(entity resolution)就是把同一现实实体的多种表示合并成一个节点17。 手段从土到洋,书里点了三样。土的:按字符串(一串字符)的相似度做匹配。

中间的:聚类(让算法把相近的自动归堆);更重的:专门的机器学习方法。

但书里给的最重要的判断是: 这件事高度领域特定,没有通用解——金融数据里好用的阈值(判断「算不算同一家」的分数线), 放到医疗数据上可能一塌糊涂。

最有效的策略是领域规则 + 领域专家定匹配标准

  • 迭代(做一遍、查一遍、再修)的人工复核18

3. 主走查:一份真实合同从文本到图

书里用公开数据集 CUAD(Contract Understanding Atticus Dataset, 由专家逐条审过、专供合同理解研究的公开合同集)里的一份许可合同走全程19:

输入:license_agreement.txt(一份真实的许可协议全文)

第 1 步 调 extract(),温度 0,gpt-4o,数据模型 = Contract
第 2 步 LLM 返回结构化结果:
contract_type: "Licensing Agreement"
parties: [ { name: "Mortgage Logic.com, Inc.", role: "Client",
location: Irvine, California, US },
{ name: "TrueLink, Inc.", role: "Provider",
location: San Luis Obispo, California, US } ]
effective_date: "1999-02-26"
term: "1 年,自动续约,除非提前 30 天通知终止"
end_date: None ← Optional 生效:合同里没写,模型没编
total_amount: None ← 同上[^20]
第 3 步 建 Constraint(Contract/Organization/Location 各自唯一)[^21]
第 4 步 Cypher 导入,图长成:
(Mortgage Logic.com)-[:HAS_PARTY {role:"Client"}]→(Contract)
(TrueLink)-[:HAS_PARTY {role:"Provider"}]→(Contract)
两家组织各自 -[:LOCATED_AT]→ 自己的 Location 节点[^22]
第 5 步 再把原文按块挂回合同节点(HAS_CHUNK)——
结构与非结构化文本在同一个图里会师[^23]

第 2 步两个 None 是这一章的题眼:数据模型里标了 Optional 的字段, LLM 如实报告「合同里没有」,而不是编一个日期或金额。 抽取的可信度,一半是模型给的,一半是你把「允许说不知道」设计进去给的。

4. 作者的判断与证据

  • 法律域按条款切块——通用切块按字数,但合同有天然的条款结构, 按条款切保住语义边界,下游分析质量更高20;
  • 实体消解没有银弹——领域规则 + 专家 + 迭代复核,是书里给的组合拳18;
  • 抽取质量的历史对照:过去的信息抽取系统 vs 今天的单次 LLM 调用, 是书里少有的「时代对比」论断3

判断(我们的,不是书里的): 这一章和第 08 章是「建图」的两种哲学, 值得现在就把岔路口指出来:本章是白盒抽取——你预先定义数据模型, 每个字段都有 schema 管,抽出来的图干净、可查询,但模型是你定死的, 遇到没预料的实体类型就抓瞎;第 08 章的微软管道是黑盒抽取—— 只给实体类型清单,描述全由 LLM 自由发挥,覆盖广但图「软」。 企业场景的务实顺序通常是先白盒(有明确 schema 需求时)、后黑盒(探索性材料)。 如果错,会错在: 如果材料的实体类型高度开放(新闻、闲聊), 白盒模型会频繁漏抽,黑盒反而合适——按「schema 先不先验」分,别按新旧分。

5. 边界与局限

  • 抽取质量没有在本章量化:抽得对不对,要等到第 11 章的评测框架; 本章的示例只展示「格式正确」,不证明「内容全对」;
  • 实体消解只讲了问题,书里没有给任何可运行的去重实现—— 这是全书少有的「点到为止」;
  • 图建模被声明为超出本书范围:书里直接给了一个合同模型让你用, 更复杂的建模要另学21;
  • 抽取是按文档逐份调 LLM 的,成本随文档数一段一段往上加,书里没有给出成本账 (对比:第 08 章的微软管道同样烧钱,我们在那里讨论);
  • 法律文档里的表格、签名页等非叙述内容,书里没有处理。

6. 可带走的

  1. 向量 RAG 在合同上的死穴:跨文档混块过滤计数聚合,都不是检索调参能救的;
  2. 结构化抽取的数据模型 = 字段名 + 类型 + 描述 三件套,描述里写格式与标准;
  3. 可能缺失的字段必须标 Optional——否则 LLM 编值填空;
  4. 取值可穷举用 enum 锁死;不可穷举只给示例;
  5. 抽取时温度设 0——同一份文档要抽出同一个结果;
  6. 导入查询要幂等;唯一约束先行,防重复导入;
  7. 实体消解(同名合并)没有通用解:领域规则 + 专家 + 迭代复核;
  8. 结构与文本不二选一:抽取建结构,原文按块挂回(HAS_CHUNK),一图两用。

7. 原文地图

主题原书章原文位置
跨文档混块死穴6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:35(搜「specific contract」)
统计计数死穴6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:71(搜「How many active contracts」)
嵌入不干过滤聚合6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:76(搜「semantically similar content」)
信息抽取的历史6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:145(搜「information extraction」)
LLM 降低门槛6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:151(搜「dramatically simplified」)
Structured Outputs 特性6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:156(搜「Structured Outputs feature」)
合同数据模型6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:216(搜「class Contract」)
不标 Optional 会编值6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:268(搜「halluci」)
enum 锁取值6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:277(搜「enum parameter」) · text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:284(搜「Service Agreement」)
role 不用 enum 的理由6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:322(搜「avoid restricting」)
嵌套别太深6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:320(搜「too many levels」)
日期格式、ISO6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:198(搜「yyyy-MM-dd」) · text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:345(搜「ISO standards」)
系统消息与试错6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:388(搜「trial and error」)
温度 0 抽取函数6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:394(搜「temperature=0」)
CUAD 数据集6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:417(搜「Contract Understanding Atticus」)
抽取结果(主走查)6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:445(搜「Licensing Agreement」) · text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:446(搜「Mortgage Logic」) · text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:253(搜「total_amount」)
图模型三实体6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:511(搜「Organization, and Location」)
唯一约束6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:553(搜「unique constraints」)
不幂等的导入6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:611(搜「not idempotent」)
实体消解定义与 UTI 例6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:633(搜「merging different representations」) · text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:639(搜「UTI Asset Management」)
没有通用解6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:655(搜「one-size-fits-all」)
领域规则+专家+迭代6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:661(搜「subject matter experts」)
按条款切块、HAS_CHUNK6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:706(搜「clauses preserves」) · text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:688(搜「HAS_CHUNK」)
图建模超范围6 Constructing knowledge graphs with LLMstext/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:506(搜「beyond the scope」)

Footnotes

  1. 出处:「6 Constructing knowledge graphs with LLMs」第 35 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:35,搜「specific contract」)。原文:跨多份合同切块检索时,前 k 块可能来自不相关合同,共享「payment」「terms」等词的块混进来,形成碎片化不一致的条款视图,LLM 综合时产出误导信息。

  2. 出处:「6 Constructing knowledge graphs with LLMs」第 76 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:76,搜「semantically similar content」)。

  3. 出处:「6 Constructing knowledge graphs with LLMs」第 145 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:145,搜「information extraction」)与第 151 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:151,搜「dramatically simplified」)。原文:过去要多个 ML 模型、工程团队,只有大机构负担得起。 2

  4. 出处:「6 Constructing knowledge graphs with LLMs」第 156 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:156,搜「Structured Outputs feature」)。原文:开发者可预先定义输出格式,保证模型响应遵守特定结构。

  5. 出处:「6 Constructing knowledge graphs with LLMs」第 216 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:216,搜「class Contract」)。Pydantic 是 Python 的数据校验库,用类声明字段与类型。

  6. 出处:「6 Constructing knowledge graphs with LLMs」第 268 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:268,搜「halluci」)。原文:信息可能缺失时必须标 Optional,否则一些 LLM 会为了填空而生成(幻觉)值;total_amount 可缺,因为有些合同没有金钱交换。

  7. 出处:「6 Constructing knowledge graphs with LLMs」第 322 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:322,搜「avoid restricting」)。enum 锁值见第 277 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:277,搜「enum parameter」)。

  8. 出处:「6 Constructing knowledge graphs with LLMs」第 198 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:198,搜「yyyy-MM-dd」)。原文:没有原生 datetime 类型,描述确保日期遵循特定格式。

  9. 出处:「6 Constructing knowledge graphs with LLMs」第 345 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:345,搜「ISO standards」)。原文:LLM 熟悉国家 ISO 标准,指示模型按标准归一。

  10. 出处:「6 Constructing knowledge graphs with LLMs」第 320 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:320,搜「too many levels」)。

  11. 出处:「6 Constructing knowledge graphs with LLMs」第 388 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:388,搜「trial and error」)。原文:定义领域、说明输出用途是清楚的;其余常靠试错。

  12. 出处:「6 Constructing knowledge graphs with LLMs」第 394 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:394,搜「temperature=0」)。「温度归零的原因」是我们的机制注解;书里只给了参数与调用方式。

  13. 出处:「6 Constructing knowledge graphs with LLMs」第 511 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:511,搜「Organization, and Location」)。组织经 HAS_PARTY(带 role)连合同;图 6.3 的位置关系标注为 HAS_LOCATION,代码用的是 LOCATED_AT(第 603 段,text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:603,搜「LOCATED_AT」),本书以代码为准。

  14. 出处:「6 Constructing knowledge graphs with LLMs」第 553 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:553,搜「unique constraints」)。原文:唯一约束既保数据完整性也提升查询性能。

  15. 出处:「6 Constructing knowledge graphs with LLMs」第 611 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:611,搜「not idempotent」)。原文:合同 ID 用 randomUUID() 生成,重跑会创建重复条目。

  16. 出处:「6 Constructing knowledge graphs with LLMs」第 639 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:639,搜「UTI Asset Management」)。三个变体名见第 639-641 段。

  17. 出处:「6 Constructing knowledge graphs with LLMs」第 633 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:633,搜「merging different representations」)。原文:实体消解=识别并合并同一现实实体在数据集/图中的不同表示。

  18. 出处:「6 Constructing knowledge graphs with LLMs」第 655 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:655,搜「one-size-fits-all」)与第 661 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:661,搜「subject matter experts」)。原文:领域特定、通用解罕见;最有效策略是领域本体/规则、专家定义匹配标准、迭代反馈回路。 2

  19. 出处:「6 Constructing knowledge graphs with LLMs」第 417 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:417,搜「Contract Understanding Atticus」)。CUAD 是为训练 AI 理解与审阅法律合同构建的专业语料(Hendrycks 等,2021)。

  20. 出处:「6 Constructing knowledge graphs with LLMs」第 706 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:706,搜「clauses preserves」)与第 688 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:688,搜「HAS_CHUNK」)。原文:法律合同按条款切能保住语义结构,改进下游分析质量。

  21. 出处:「6 Constructing knowledge graphs with LLMs」第 506 段(text/15-ch06-6-constructing-knowledge-graphs-with-llms.txt:506,搜「beyond the scope」)。原文:图建模超出本书范围,可用 LLM 协助或参考 Neo4j Graph Academy。