源文件:chapter3/structured-knowledge-extraction/README.md 实验 3-13:从结构化数据中提取隐性知识——以司法判例分析为例 配套《深入理解 AI Agent》第 3 章。演示如何让 Agent 不把知识库当成"只能检索的静态仓库", 而是先把数据读懂、从数据本身归纳出结构化的决策逻辑,再基于这套逻辑回答问题。 以三类罪名(盗窃罪 / 故意伤害罪 / 诈骗罪)的判例为例,完整走通四段流水线: 与"预定义僵化 schema + 回归黑箱"的做法相反,本实验的两个关键创新是: 因子不预设、由 LLM 从数据里自由归纳;判决经验不靠回归拟合刑期、而靠聚类出可解释的案件原型。 四段流水线 ① 自下而上因子发现( ) 不预先定义任何字段。
源文件:chapter3/structured-knowledge-extraction/README.md
配套《深入理解 AI Agent》第 3 章。演示如何让 Agent 不把知识库当成"只能检索的静态仓库",
而是先把数据读懂、从数据本身归纳出结构化的决策逻辑,再基于这套逻辑回答问题。
以三类罪名(盗窃罪 / 故意伤害罪 / 诈骗罪)的判例为例,完整走通四段流水线:
判例文本 ──①自下而上因子发现──▶ 模块化 schema(核心+各罪名扩展) │ ②结构化抽取(用发现的 schema 抽因子) │ ③各罪名内聚类 ──▶ 案件原型 + 层次因子重要性 │ 新案情 ──④对话 Agent(匹配最近原型、按重要性追问、给出建议)◀──┘
与"预定义僵化 schema + 回归黑箱"的做法相反,本实验的两个关键创新是:
因子不预设、由 LLM 从数据里自由归纳;判决经验不靠回归拟合刑期、而靠聚类出可解释的案件原型。
① 自下而上因子发现(discovery.py)
不预先定义任何字段。把判例文本分批喂给 LLM,让它自由列出每一批案例中所有可能影响判决的
因素;再用一次 LLM 调用把各批发现的原始因子归并、去重、规范化成一个模块化 schema:core(适用所有罪名的通用因子:自首、赔偿、认罪认罚、前科累犯……)+ extensions
(各罪名特有扩展因子:盗窃→涉案金额/入户/团伙,故意伤害→伤害等级/持械/预谋,诈骗→金额/受害人数)。
产出 data/schema.json(带缓存)。
② 结构化抽取(extractor.py)
用发现出来的 schema,从每条判例抽取「核心 + 该罪名扩展」因子(LLM 结构化输出,response_format=json_object)。文本未提及的因子返回 null。抽取结果缓存到data/extracted.jsonl,一次性抽取后重跑几乎免费。
③ 聚类成案件原型 + 层次因子重要性(archetypes.py)
把因子翻译成数值向量:罪名 / 分类因子(如伤害等级)用 one-hot 开关位(不用 1/2/3,
避免暗示大小关系);金额 / 人数取 ln 压缩量纲;是非情节取 0/1。在每个罪名内部用
KMeans 聚类(k 由轮廓系数自动挑选),得到若干「案件原型」——例如故意伤害罪会自动聚出
"轻微伤"、"轻伤"、"持械预谋致重伤" 等典型模式。再算两级重要性:
产出可读、自洽的 data/archetypes.json(含标准化参数与簇心)。
④ 对话式量刑建议 Agent(advisor_agent.py)
把「案件原型 + 层次因子重要性」当决策逻辑:从用户口语描述抽取已知因子 → 对照全局因子
重要性追问仍缺失的关键因子 → 把案件匹配到最近的案件原型(先按罪名圈定候选,再只在
已知维度上比距离)→ 让 LLM 基于该原型的统计数据(典型刑期区间、定义性因子)给出一段
有判例支持、可解释的建议(附法律免责声明)。所有刑期数字均来自原型统计,LLM 只负责讲清楚。
pip install -r requirements.txt cp env.example .env # 填入 OPENAI_API_KEY(默认模型 gpt-5.6-luna) python generate_data.py # 可选:重新生成合成判例数据集(已自带 data/cases.jsonl) python demo.py # 跑通 因子发现 → 抽取 → 聚类 → 对话建议 全流程
首次运行会调用 LLM 做因子发现(约 7 次)与逐条抽取(约 66 次),结果分别写入data/schema.json、data/extracted.jsonl;再次运行直接命中缓存,几乎免费。
阶段 1 自下而上发现的 schema: 核心通用因子: prior_record 前科 / self_surrender 自首 / compensation 赔偿 / guilty_plea 认罪认罚 / victim_reconciliation 谅解 ... 扩展·盗窃罪: amount_stolen 盗窃金额 / gang_involvement 团伙 / use_of_weapon 持械 扩展·故意伤害罪: injury_level 伤害等级[轻微伤/轻伤二级/重伤二级] / premeditation 预谋 ... 扩展·诈骗罪: amount_defrauded 诈骗金额 / victim_count 受害人数 / group_crime 团伙 阶段 3 各罪名内聚类(k 由轮廓系数自动选)→ 共 12 个案件原型;全局因子重要性排序: 1. 罪名 2. 伤害等级=重伤 3. 诈骗金额 4. 盗窃金额 5. 团伙作案 6. 是否预谋 ... ▸ 原型#0 [故意伤害罪] 中位 2 月:伤害等级=轻微伤(z=+2.5) ▸ 原型#1 [故意伤害罪] 中位 42 月:伤害等级=重伤二级(z=+3.9)、预谋(z=+1.8) —— "持械预谋重伤"型 ▸ 原型#5 [盗窃罪] 中位 51 月:盗窃金额高、前科/累犯 100% ... 阶段 4 对话:识别到盗窃案缺金额 → 按重要性追问金额/认罪/谅解 → 补全后匹配到 原型#6 (典型刑期中位 40 月、区间 24~50 月),并引用该原型的关键因子给出建议。
data/cases.jsonl 是自带的小样本合成数据(66 条,覆盖 3 类罪名),由 generate_data.py
用已知量刑公式加噪声生成:每条含自然语言 fact、结构化真值 gold、刑期 label_months。
关键点是因子在生成时被"写进"案情文本,发现阶段再从文本里把它们"读"回来——因子发现完全
不依赖生成时的字段列表,因此学到的模式来自数据本身。
真实目标数据集是 CAIL2018(中文刑事判决,数百万条)。因体量太大不便随仓库分发才用合成
小样本;换成真实数据只需把 generate_data.py 换成读取 CAIL 的 data_*.json
(每行含 fact、meta.accusation、meta.term_of_imprisonment),产出同结构的cases.jsonl 即可,发现 / 抽取 / 聚类 / 对话四段代码无需改动。
| 文件 | 作用 |
|---|---|
generate_data.py |
合成多罪名小样本判例数据集 |
discovery.py |
阶段 ①:自下而上因子发现 → 模块化 schema |
extractor.py |
阶段 ②:用发现的 schema 做结构化抽取(带缓存) |
archetypes.py |
阶段 ③:各罪名内聚类成案件原型 + 层次因子重要性 |
advisor_agent.py |
阶段 ④:对话式量刑建议 Agent(匹配最近原型) |
demo.py |
全流程演示入口 |
config.py |
OpenAI 客户端与模型配置 |
This experiment now supports a universal OpenRouter fallback for its chat LLM.
MOONSHOT_API_KEY / KIMI_API_KEY / OPENAI_API_KEY / DOUBAO_API_KEY …) is present, behavior is unchanged.OPENROUTER_API_KEY is set, the chat LLM is automatically routed through OpenRouter (https://openrouter.ai/api/v1). Model names are mapped automatically: gpt-*/o1-* → openai/…, claude-* → anthropic/claude-opus-4.8, kimi-* → moonshotai/kimi-k2.6, ids already containing / are kept as-is, and other provider-native ids (e.g. doubao-*) fall back to openai/gpt-5.6-luna. Set OPENROUTER_MODEL to force a specific OpenRouter model id.Add OPENROUTER_API_KEY=... to your .env (see env.example) to enable it.