ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -0,0 +1,12 @@
# 运行 demo.py 时自动生成的产物(发现的 schema、抽取缓存、聚类模型),不应入库。
# 注意:data/cases.jsonl 是自带的小样本合成数据集,需保留入库。
data/schema.json
data/extracted.jsonl
data/archetypes.json
# Official CAIL archives are large, immutable upstream cache inputs. The
# bounded deterministic sample and validation evidence remain versionable.
data/official/*.zip
data/official/extracted/
.env
__pycache__/
*.pyc
@@ -0,0 +1,273 @@
# Experiment 3-12: Extracting Latent Knowledge from Structured Data / 实验 3-12:从结构化数据中提取隐性知识
> Companion material for *AI Agents in Depth*, Chapter 3 — judicial case analysis pipeline: bottom-up factor discovery → structured extraction → archetype clustering → conversational advisory Agent.
> 配套《深入理解 AI Agent》第 3 章——以司法判例分析为例:因子发现 → 结构化抽取 → 案件原型聚类 → 对话式建议 Agent。
← [Chapter 3 index / 返回第 3 章目录](../README.md)
---
## English
### Canonical official-data campaign
`python campaign.py` is the acceptance run. It reads the official CAIL2018
archive (cache-only), deterministically materializes a bounded 420-case sample
(360 train/60 held out across three charges), performs live bottom-up discovery
and live modular extraction, selects per-charge clusters with silhouette
diagnostics, and independently judges prototype-only held-out advice. The
official URL/repository revision/archive SHA-256 and bytes, split, raw receipts,
prototype statistics, leakage checks, and legal disclaimer gate are recorded
under `validation/runs/<run-id>/`; `validation/latest.json` is canonical.
The large `data/official/CAIL2018_ALL_DATA.zip` is intentionally ignored. Only
the deterministic sample and validation evidence are versionable.
### Legacy synthetic teaching path (not acceptance evidence)
The files described below predate the canonical official-data campaign. They
remain useful for a fast local walkthrough, but their synthetic cases and
hand-sized run are mechanism illustrations only; they do not satisfy
Experiment 3-12. Only `campaign.py` and `validation/latest.json` can close the
manuscript experiment.
### What this legacy demo is
This lab shows how an Agent can treat a knowledge base not as a “static warehouse you only retrieve from,” but as data to **read, understand, and turn into structured decision logic**—then answer questions using that logic.
Using three charge types (theft / intentional injury / fraud) as examples, it walks a four-stage pipeline:
```
Case texts ──① bottom-up factor discovery──▶ modular schema (core + per-charge extensions)
② structured extraction (factors via discovered schema)
③ per-charge clustering ──▶ case archetypes + hierarchical factor importance
New facts ──④ conversational Agent (match nearest archetype, ask by importance, advise) ◀──┘
```
Unlike “rigid predefined schema + black-box regression,” the two key ideas here are: **factors are not preset—the LLM induces them freely from data**; **sentencing experience is not fit by regression, but by clustering into interpretable case archetypes**.
### Four-stage pipeline
**① Bottom-up factor discovery (`discovery.py`)**
No fields are predefined. Case texts are batched to the LLM so it can **freely list** factors that may affect the judgment; a second LLM pass **merges, deduplicates, and normalizes** raw factors into a modular schema: `core` (cross-charge factors: surrender, compensation, guilty plea, prior record, …) + `extensions` (charge-specific: theft → amount/home invasion/gang; intentional injury → injury level/weapon/premeditation; fraud → amount/victim count). Output: `data/schema.json` (cached).
**② Structured extraction (`extractor.py`)**
Using the discovered schema, extract “core + that charges extensions” per case (LLM structured output, `response_format=json_object`). Factors not mentioned in the text return `null`. Results cache to `data/extracted.jsonl`; after one full pass, re-runs are nearly free.
**③ Clustering into case archetypes + hierarchical factor importance (`archetypes.py`)**
Factors become numeric vectors: charge / categorical factors (e.g. injury level) as one-hot (not 1/2/3, to avoid implying order); amounts / counts use `ln` scaling; binary facts as 0/1. **Within each charge**, KMeans clusters (k chosen by silhouette score) produce “case archetypes”—e.g. intentional injury may cluster into “minor injury,” “light injury,” “premeditated armed serious injury,” etc. Two levels of importance:
- **Global factor importance**: how well each factor separates all archetypes (between-cluster variance share) → global ranking
- **Within-archetype defining factors**: factors most distinctive for each archetype vs global, plus typical sentence distribution (median / range)
Readable output: `data/archetypes.json` (with normalization params and centroids).
**④ Conversational sentencing-advice Agent (`advisor_agent.py`)**
Uses “archetypes + hierarchical factor importance” as decision logic: extract known factors from the users free-form description → ask for still-missing **globally important** factors → **match the nearest case archetype** (filter by charge, then distance on known dims only) → LLM writes an interpretable suggestion grounded in that archetypes stats (typical sentence range, defining factors), with a legal disclaimer. All sentence numbers come from archetype statistics; the LLM only explains them.
### Run
```bash
# From the repository root: use the shared Chapter 3 environment
uv sync --locked --python 3.12 --extra ch3
# Activate it before changing directories:
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell: .venv\Scripts\Activate.ps1
# Windows cmd: .venv\Scripts\activate.bat
# pip fallback when uv is not installed:
# python -m pip install -e ".[ch3]"
cd chapter3/structured-knowledge-extraction
# Single-project compatibility path, still supported during migration:
# python -m pip install -r requirements.txt
cp env.example .env # set the selected provider key (OpenAI or DashScope/Bailian)
python generate_data.py # optional: regenerate synthetic cases (repo ships data/cases.jsonl)
python demo.py # full pipeline: discovery → extract → cluster → conversational advice
```
First run calls the LLM for factor discovery (~7 calls) and per-case extraction (~66 calls), writing `data/schema.json` and `data/extracted.jsonl`; later runs hit cache and are nearly free.
### Real run output (excerpt)
```
阶段 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 notes
`data/cases.jsonl` is a **bundled small synthetic sample** (66 cases, 3 charges), produced by `generate_data.py` with a known sentencing formula plus noise: each line has natural-language `fact`, structured ground truth `gold`, and sentence `label_months`. The point is that **factors are written into the narrative at generation time, then “read back” from text at discovery**—discovery does not depend on the generation field list, so patterns come from the data itself.
The **intended real dataset is CAIL2018** (Chinese criminal judgments, millions of rows). Volume makes shipping it impractical; to switch, replace `generate_data.py` with a reader for CAIL `data_*.json` (lines with `fact`, `meta.accusation`, `meta.term_of_imprisonment`) into the same `cases.jsonl` shape—discovery / extract / cluster / dialogue code need not change.
### Files
| File | Role |
|------|------|
| `generate_data.py` | Synthetic multi-charge small case set |
| `discovery.py` | Stage ①: bottom-up factor discovery → modular schema |
| `extractor.py` | Stage ②: structured extraction with discovered schema (cached) |
| `archetypes.py` | Stage ③: per-charge clustering + hierarchical factor importance |
| `advisor_agent.py` | Stage ④: conversational advice Agent (nearest archetype) |
| `demo.py` | End-to-end demo entry |
| `config.py` | OpenAI client and model config |
### Limitations and disclaimer
- **Teaching only**—demonstrates the paradigm “extract latent knowledge from structured data.”
- Data is synthetic, factor set simplified; clustering cannot capture full complexity of real sentencing.
- **No output constitutes legal advice.** Real sentencing depends on statutes, judicial interpretations, local policy, and many case-specific facts—consult a qualified lawyer; do not rely on this project for legal decisions.
---
## 中文
### 旧版合成教学路径(不属于验收证据)
以下文件早于本页开头的正式 CAIL2018 campaign,仅保留用于快速本地教学。
合成案例与小规模运行只能说明机制,不能验收实验 3-12;正文验收只认
`campaign.py``validation/latest.json`
### 这个旧版 demo 是什么
演示如何让 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 只负责讲清楚。
### 运行
```bash
# 在仓库根目录使用统一的第 3 章环境
uv sync --locked --python 3.12 --extra ch3
# 切换目录前先激活环境:
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell.venv\Scripts\Activate.ps1
# Windows cmd.venv\Scripts\activate.bat
# 未安装 uv 时可用 pip 兜底:
# python -m pip install -e ".[ch3]"
cd chapter3/structured-knowledge-extraction
# 迁移期间仍支持单项目兼容路径:
# python -m 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 客户端与模型配置 |
### 局限与免责声明
- 本项目**仅用于教学**,演示“从结构化数据中提取隐性知识”这一技术范式。
- 数据为合成、因子集经简化,聚类也无法刻画真实司法量刑的复杂性与非线性。
- **本项目的任何输出都不构成法律意见。** 真实案件量刑受法律条文、司法解释、地域政策与大量具体情节影响,请务必咨询专业律师,切勿据此做任何法律决策。
---
## Notes / 说明
### OpenRouter 通用回退 / Universal OpenRouter fallback
This experiment supports a **universal OpenRouter fallback** for its chat LLM.
- If the primary provider key (e.g. `MOONSHOT_API_KEY` / `KIMI_API_KEY` / `OPENAI_API_KEY` / `DOUBAO_API_KEY` …) is present, behavior is unchanged.
- Else if `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.
- Else a clear error lists the accepted keys.
Add `OPENROUTER_API_KEY=...` to your `.env` (see `env.example`) to enable it.
@@ -0,0 +1,106 @@
"""
阶段 4:对话式量刑建议 Agent。
把「案件原型 + 层次因子重要性」当决策逻辑来用:
1. 从用户口语描述里抽取已知因子(复用抽取器,含罪名判定);
2. 按**全局因子重要性顺序**,找出仍缺失、但很重要的因子,生成引导性追问;
3. 信息补全后,把案件**匹配到最近的案件原型**;
4. 用 LLM 把该原型的统计数据(典型刑期区间、定义性关键因子)组织成一段
有判例支持、可解释的中文建议(附法律免责声明)。
所有刑期数字都来自原型统计,LLM 只负责"把数字讲清楚",不自行编造。
"""
from config import MODEL, get_client
from archetypes import nearest_archetype
from discovery import all_factors
DISCLAIMER = (
"【免责声明】本回答由教学实验中的统计模型自动生成,仅用于演示"
"『从结构化数据中提取隐性知识』这一技术,不构成任何法律意见。真实案件量刑受"
"法律条文、司法解释、地域与具体情节等大量因素影响,请务必咨询专业律师。"
)
class LegalAdvisorAgent:
def __init__(self, schema, model):
self.schema = schema
self.model = model # archetypes.fit() 产出的模型
self.client = get_client()
self._factor = {f["key"]: f for f in all_factors(schema)}
# --- 步骤 1:抽取已知因子 ---
def extract_known(self, case_text):
from extractor import extract_one
return extract_one(case_text, schema=self.schema, client=self.client)
# --- 步骤 2:按全局重要性顺序,追问缺失的重要因子 ---
def missing_important_questions(self, known):
questions, asked = [], set()
for item in self.model["global_importance"]:
col = item["feature"]
# 从列名解析出因子 key(跳过罪名维——已判定)
if col.startswith("charge="):
continue
key = col.split(":", 1)[1].split("=", 1)[0]
if key in asked or key not in known:
continue
if known.get(key) is None: # 该因子适用于本罪名但用户尚未提供
f = self._factor.get(key, {})
questions.append({
"factor": key,
"name_cn": f.get("name_cn", key),
"importance": item["score"],
"question": f.get("question") or f"请补充:{f.get('name_cn', key)}",
})
asked.add(key)
return questions
# --- 步骤 3+4:匹配最近原型并给出建议 ---
def advise(self, known):
matched = nearest_archetype(self.model, known)
# fit() can return n_archetypes=0 when every charge has too few samples to cluster.
if matched is None:
raise ValueError("模型中没有可用案件原型,无法给出量刑建议(样本过少无法聚类)")
arch, dist = matched
m = arch["months"]
defining = "".join(
f"{d['label']}{d['direction']},典型 {d['typical']}"
for d in arch["defining"][:4]
)
evidence = (
f"- 命中案件原型 #{arch['id']}{arch['charge']},该原型含 {arch['size']} 例),"
f"匹配距离 {dist:.2f}\n"
f"- 该原型典型刑期:中位 {m['median']:.0f} 个月,区间 {m['min']:.0f}~{m['max']:.0f} 个月\n"
f"- 定义该原型的关键因子:{defining}"
)
known_desc = self._describe_known(known)
system = (
"你是一名严谨的司法数据分析助手。下面给出一个数据驱动模型把某案件匹配到的"
"『案件原型』及其统计数据(数字均来自模型,不得改动)。请用中文写一段 160 字"
"以内、条理清晰的量刑参考:先说明命中的原型及其典型刑期区间,再点明本案与该"
"原型共有的关键因子如何影响结果。不要编造模型未给出的数字,不要给确定性承诺,"
"不要重复免责声明(系统会另附)。"
)
user = f"本案已知因子:\n{known_desc}\n\n模型匹配依据:\n{evidence}"
resp = self.client.chat.completions.create(
model=MODEL, temperature=0.3,
messages=[{"role": "system", "content": system},
{"role": "user", "content": user}],
)
return arch, resp.choices[0].message.content.strip() + "\n\n" + DISCLAIMER
def _describe_known(self, known):
parts = [f"罪名:{known.get('charge')}"]
for key, v in known.items():
if key == "charge":
continue
f = self._factor.get(key, {})
if v is None:
tag = "未知"
elif isinstance(v, bool):
tag = "" if v else ""
else:
tag = str(v)
parts.append(f"{f.get('name_cn', key)}{tag}")
return "\n".join(" " + p for p in parts)
@@ -0,0 +1,232 @@
"""
阶段 3:聚类 + 层次重要性 —— 从结构化因子里发现「案件原型」与「因子重要性层次」。
不做刑期回归(那会得到一个说不清理由的黑箱),而是:
1. 把每条案例的因子翻译成数值特征向量:
- 罪名 / 分类因子(如伤害等级) 用 one-hot 开关位(不用 1/2/3,避免暗示大小关系);
- 数值因子(金额/人数)取 ln 压缩量纲;是非情节取 0/1。
(某因子若同时落在 core 与某罪名扩展里,按 key 去重,特征列不重复。)
2. 标准化后用 KMeans 聚类,k 由轮廓系数(silhouette)自动挑选,得到若干「案件原型」;
3. 计算两级重要性:
- 全局重要性:每个因子在原型之间的区分度(簇间方差占比)→ 全局因子重要性排序;
- 原型内重要性:每个原型相对全局均值最突出的因子 → 定义该原型的关键特征。
并统计每个原型的刑期分布(均值/中位/区间)作为「数据驱动的判决经验」。
产出 data/archetypes.json(可读、自洽,含标准化参数与簇心),供对话 Agent 直接引用。
"""
import json
import math
import os
import numpy as np
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
from discovery import all_factors
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
MODEL_PATH = os.path.join(DATA_DIR, "archetypes.json")
# --- 特征空间:自描述列名,训练与推理共用 -----------------------------------
def build_columns(schema, results):
"""根据 schema + 抽取结果确定有序特征列(列名自描述其含义)。"""
factors = all_factors(schema)
kind = {f["key"]: f["kind"] for f in factors}
charges = sorted({r["extracted"]["charge"] for r in results})
cols = [f"charge={c}" for c in charges]
for f in factors:
k = f["key"]
if kind[k] == "numeric":
cols.append(f"num:{k}")
elif kind[k] == "bool":
cols.append(f"bool:{k}")
else: # categorical:取值集合来自 schema 与实际数据的并集
vals = set(f.get("values") or [])
for r in results:
v = r["extracted"].get(k)
if v is not None:
vals.add(str(v))
for v in sorted(vals):
cols.append(f"cat:{k}={v}")
return cols
def vectorize(extraction, columns):
"""把一条抽取结果转成特征向量,并返回 known 掩码(该维是否有已知取值)。"""
charge = extraction.get("charge")
vec, known = [], []
for col in columns:
if col.startswith("charge="):
vec.append(1.0 if charge == col[len("charge="):] else 0.0)
known.append(True) # 罪名一旦判定即视为已知
elif col.startswith("num:"):
v = extraction.get(col[len("num:"):])
vec.append(math.log(v) if v else 0.0)
known.append(v is not None)
elif col.startswith("bool:"):
v = extraction.get(col[len("bool:"):])
vec.append(1.0 if v else 0.0)
known.append(v is not None)
else: # cat:key=value
body = col[len("cat:"):]
key, val = body.split("=", 1)
v = extraction.get(key)
vec.append(1.0 if (v is not None and str(v) == val) else 0.0)
known.append(v is not None)
return np.array(vec), np.array(known)
def column_label(col, schema):
"""列名 -> 中文可读标签。"""
name_cn = {f["key"]: f["name_cn"] for f in all_factors(schema)}
if col.startswith("charge="):
return "罪名=" + col[len("charge="):]
if col.startswith("num:"):
return name_cn.get(col[len("num:"):], col[len("num:"):]) + "(对数)"
if col.startswith("bool:"):
k = col[len("bool:"):]
return name_cn.get(k, k)
body = col[len("cat:"):]
key, val = body.split("=", 1)
return f"{name_cn.get(key, key)}={val}"
# --- 聚类 + 层次重要性 ------------------------------------------------------
def fit(schema, results, k_range=range(2, 5), save=True, verbose=True):
"""在**每个罪名内部**聚类出案件原型(书中:在某罪名内自动聚出典型模式),
再跨全部原型算全局因子重要性。"""
columns = build_columns(schema, results)
X_raw = np.array([vectorize(r["extracted"], columns)[0] for r in results])
months = np.array([r["label_months"] for r in results], dtype=float)
charges = np.array([r["extracted"]["charge"] for r in results])
is_charge_col = np.array([c.startswith("charge=") for c in columns])
scaler = StandardScaler().fit(X_raw)
Z = scaler.transform(X_raw)
archetypes, aid, sils = [], 0, []
for ch in sorted(set(charges)):
idx = np.where(charges == ch)[0]
Zc = Z[idx]
# 该罪名内用轮廓系数挑 k
best = None
for k in k_range:
if k >= len(idx):
break
km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(Zc)
sil = silhouette_score(Zc, km.labels_)
if best is None or sil > best[0]:
best = (sil, k, km)
if best is None:
if verbose:
print(f" {ch}: n={len(idx)} 样本过少,跳过聚类")
continue
sil, k, km = best
sils.append(sil)
if verbose:
print(f" {ch}: n={len(idx)} 自动选定 k={k} 轮廓系数={sil:.3f}")
for c in range(k):
sub = idx[km.labels_ == c]
z = km.cluster_centers_[c] # 标准化空间簇心(全维)
mth = months[sub]
# 定义性特征:|簇心| 最大的**非罪名**列(罪名在同一罪名内是常量,不算)
cand = [j for j in np.argsort(-np.abs(z)) if not is_charge_col[j]][:6]
defining = [{
"feature": columns[j],
"label": column_label(columns[j], schema),
"z": float(z[j]),
"direction": "高于平均" if z[j] > 0 else "低于平均",
"typical": _typical_value(columns[j], float(X_raw[sub, j].mean())),
} for j in cand]
archetypes.append({
"id": aid,
"charge": ch,
"size": int(len(sub)),
"months": {"mean": float(mth.mean()), "median": float(np.median(mth)),
"min": float(mth.min()), "max": float(mth.max())},
"defining": defining,
"centroid_std": z.tolist(),
})
aid += 1
# 全局重要性:跨全部原型的簇间方差占比(簇心加权方差)
cents = np.array([a["centroid_std"] for a in archetypes])
sizes = np.array([a["size"] for a in archetypes])
weights = sizes / sizes.sum()
between_var = (weights[:, None] * cents ** 2).sum(axis=0) # 标准化后总均值≈0
global_importance = [
{"feature": columns[j], "label": column_label(columns[j], schema),
"score": float(between_var[j])}
for j in np.argsort(-between_var)
]
archetypes.sort(key=lambda a: (a["charge"], a["months"]["median"]))
model = {
"columns": columns,
"scaler_mean": scaler.mean_.tolist(),
"scaler_scale": scaler.scale_.tolist(),
"n_archetypes": len(archetypes),
"silhouette_mean": float(np.mean(sils)) if sils else 0.0,
"global_importance": global_importance,
"archetypes": archetypes,
"n_samples": int(len(results)),
}
if save:
os.makedirs(DATA_DIR, exist_ok=True)
with open(MODEL_PATH, "w", encoding="utf-8") as fh:
json.dump(model, fh, ensure_ascii=False, indent=2)
return model
def _typical_value(col, raw_mean):
"""把某列在簇内的原始均值翻译成人话。"""
if col.startswith("num:"):
return f"{math.exp(raw_mean):,.0f}" if raw_mean else "多为缺失"
if col.startswith("charge="):
return f"{raw_mean*100:.0f}% 为该罪名"
if col.startswith("cat:"):
return f"{raw_mean*100:.0f}% 命中"
return f"{raw_mean*100:.0f}% 具备此情节" # bool
def load_model():
with open(MODEL_PATH, encoding="utf-8") as fh:
return json.load(fh)
def nearest_archetype(model, extraction):
"""把一条(可能不完整的)案件匹配到最近的案件原型:先按罪名圈定候选,
再只在**已知维度**上比距离(避免用缺失维=0 误导匹配)。"""
vec, known = vectorize(extraction, model["columns"])
z = (vec - np.array(model["scaler_mean"])) / np.array(model["scaler_scale"])
charge = extraction.get("charge")
cands = [a for a in model["archetypes"] if a["charge"] == charge] \
or model["archetypes"]
best = None
for a in cands:
diff = (np.array(a["centroid_std"]) - z) * known
d = float(np.linalg.norm(diff))
if best is None or d < best[1]:
best = (a, d)
return best
# --- 打印 -------------------------------------------------------------------
def print_model(model):
print(f" 样本数={model['n_samples']} 共发现 {model['n_archetypes']} 个案件原型"
f"(各罪名内聚类,平均轮廓系数={model['silhouette_mean']:.3f}")
print("\n 全局因子重要性排序(原型间区分度,越大越是划分原型的关键因子):")
for i, item in enumerate(model["global_importance"][:10], 1):
print(f" {i:>2}. {item['label']:<22} 区分度={item['score']:.3f}")
print("\n 案件原型(按罪名 + 典型刑期中位数排序):")
for a in model["archetypes"]:
m = a["months"]
print(f"\n ▸ 原型#{a['id']} [{a['charge']}] 规模 {a['size']}"
f" 典型刑期 中位 {m['median']:.0f} 月 / 区间 {m['min']:.0f}~{m['max']:.0f}")
for d in a["defining"][:4]:
print(f" · {d['label']:<20} {d['direction']}(z={d['z']:+.2f}) 典型:{d['typical']}")
@@ -0,0 +1,745 @@
#!/usr/bin/env python3
"""Canonical official-CAIL2018 campaign for Experiment 3-12."""
from __future__ import annotations
import argparse
import concurrent.futures
import hashlib
import json
import math
import os
import random
import re
import statistics
import sys
import time
import zipfile
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any, Dict, Iterable, List, Sequence, Tuple
import numpy as np
from openai import OpenAI
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
HERE = Path(__file__).resolve().parent
CHAPTER = HERE.parent
sys.path.insert(0, str(CHAPTER))
from experiment_utils import ChatRecorder, jsonable, sha256_file, write_campaign_evidence
ARK_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3"
MOONSHOT_ENDPOINT = "https://api.moonshot.cn/v1"
OFFICIAL_URL = "https://cail.oss-cn-qingdao.aliyuncs.com/CAIL2018_ALL_DATA.zip"
OFFICIAL_REPOSITORY = "https://github.com/china-ai-law-challenge/CAIL2018"
OFFICIAL_REVISION = "599781ffcbfb33237580c6766afe3af9e1ff7229"
CHARGES = ("盗窃罪", "故意伤害罪", "诈骗罪")
RAW_TO_CHARGE = {"盗窃": "盗窃罪", "故意伤害": "故意伤害罪", "诈骗": "诈骗罪"}
DISCLAIMER = "【免责声明】本结果仅用于数据分析教学,不构成法律意见或量刑承诺;真实案件请咨询有资质的律师。"
def parse_json(text: str) -> Dict[str, Any]:
value = (text or "").strip()
if "```" in value:
value = value.split("```", 2)[1]
if value.lstrip().startswith("json"):
value = value.lstrip()[4:]
return json.loads(value.strip())
def find_training_member(archive: Path) -> Tuple[str, Dict[str, Any]]:
with zipfile.ZipFile(archive) as zf:
candidates = []
for info in zf.infolist():
name = info.filename.lower()
if name.endswith(".json") and "train" in name and "data" in name and info.file_size > 1_000_000:
candidates.append(info)
if not candidates:
raise RuntimeError("official archive contains no CAIL JSON training member")
selected = max(candidates, key=lambda info: info.file_size)
return selected.filename, {
"member": selected.filename,
"uncompressed_bytes": selected.file_size,
"compressed_bytes": selected.compress_size,
"crc32": f"{selected.CRC:08x}",
}
def sentence_months(meta: Dict[str, Any]) -> int | None:
term = meta.get("term_of_imprisonment") or {}
if term.get("death_penalty") or term.get("life_imprisonment"):
return None
try:
months = int(term.get("imprisonment"))
except (TypeError, ValueError):
return None
return months if 0 < months <= 360 else None
def build_sample(archive: Path, sample_path: Path, seed: int, train_per_charge: int, heldout_per_charge: int):
expected = train_per_charge + heldout_per_charge
member, member_meta = find_training_member(archive)
selected: Dict[str, List[Dict[str, Any]]] = {charge: [] for charge in CHARGES}
seen_hashes = set()
with zipfile.ZipFile(archive) as zf, zf.open(member) as stream:
for line_number, raw in enumerate(stream, start=1):
try:
record = json.loads(raw)
except Exception:
continue
fact = str(record.get("fact") or "").strip()
meta = record.get("meta") or {}
accusations = meta.get("accusation") or []
if len(accusations) != 1:
continue
charge = RAW_TO_CHARGE.get(str(accusations[0]).strip("[]'\""))
months = sentence_months(meta)
if charge not in selected or months is None or len(fact) < 80:
continue
fingerprint = hashlib.sha256(fact.encode("utf-8")).hexdigest()
if fingerprint in seen_hashes:
continue
seen_hashes.add(fingerprint)
selected[charge].append(
{
"id": f"cail2018-{fingerprint[:16]}",
"charge": charge,
"fact": fact,
"label_months": months,
"source_member": member,
"source_line": line_number,
"fact_sha256": fingerprint,
}
)
if all(len(selected[item]) >= expected for item in CHARGES):
break
if any(len(selected[charge]) < expected for charge in CHARGES):
raise RuntimeError(f"official member did not yield balanced sample: { {k: len(v) for k, v in selected.items()} }")
rng = random.Random(seed)
rows = []
for charge in CHARGES:
group = selected[charge][:expected]
rng.shuffle(group)
for index, row in enumerate(group):
rows.append({**row, "split": "train" if index < train_per_charge else "heldout"})
rows.sort(key=lambda row: (row["split"], row["charge"], row["id"]))
sample_path.parent.mkdir(parents=True, exist_ok=True)
sample_path.write_text("".join(json.dumps(row, ensure_ascii=False) + "\n" for row in rows), encoding="utf-8")
return rows, member_meta
def load_or_build_sample(args: argparse.Namespace):
archive = Path(args.archive).resolve()
if not archive.exists():
raise RuntimeError(f"official CAIL2018 archive missing: {archive}; download from {OFFICIAL_URL}")
sample_path = HERE / "data" / "official" / f"cail2018_sample_seed{args.seed}.jsonl"
if sample_path.exists():
rows = [json.loads(line) for line in sample_path.read_text(encoding="utf-8").splitlines() if line.strip()]
member, member_meta = find_training_member(archive)
member_meta["member"] = member
else:
rows, member_meta = build_sample(archive, sample_path, args.seed, args.train_per_charge, args.heldout_per_charge)
expected = (args.train_per_charge + args.heldout_per_charge) * len(CHARGES)
if len(rows) != expected:
raise RuntimeError(f"sample size mismatch: expected {expected}, got {len(rows)}")
return archive, sample_path, rows, member_meta
class CachedCalls:
def __init__(self, args: argparse.Namespace):
self.args = args
self.root = HERE / "validation" / "checkpoints" / f"official-seed{args.seed}"
self.root.mkdir(parents=True, exist_ok=True)
def json_call(self, *, provider: str, purpose: str, messages: List[Dict[str, str]], max_tokens: int, key: str) -> Tuple[Dict[str, Any], Dict[str, Any]]:
safe = re.sub(r"[^a-zA-Z0-9_.-]+", "_", key)
path = self.root / f"{safe}.json"
model = self.args.discovery_model if provider == "ark" else self.args.judge_model
endpoint = self.args.discovery_endpoint if provider == "ark" else self.args.judge_endpoint
signature = hashlib.sha256(json.dumps({"provider": provider, "model": model, "endpoint": endpoint, "seed": self.args.seed, "messages": messages}, ensure_ascii=False, sort_keys=True).encode()).hexdigest()
if path.exists():
cached = json.loads(path.read_text(encoding="utf-8"))
if cached.get("signature") != signature:
raise RuntimeError(f"checkpoint signature mismatch: {path}")
return cached["parsed"], cached["receipt"]
api_key = os.getenv("ARK_API_KEY") if provider == "ark" else (os.getenv("MOONSHOT_API_KEY") or os.getenv("KIMI_API_KEY"))
client = OpenAI(api_key=api_key, base_url=endpoint, timeout=self.args.timeout, max_retries=3)
recorder = ChatRecorder(client, provider, endpoint)
response = recorder.create(
purpose=purpose,
model=model,
messages=messages,
temperature=0,
seed=self.args.seed,
max_tokens=max_tokens,
response_format={"type": "json_object"},
)
parsed = parse_json(response.choices[0].message.content or "{}")
payload = {"signature": signature, "parsed": parsed, "receipt": recorder.calls[-1]}
temporary = path.with_suffix(".tmp")
temporary.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
temporary.replace(path)
return parsed, recorder.calls[-1]
DISCOVERY_SYSTEM = """你是司法判例数据研究员。对给出的真实刑事案件事实与已知罪名进行自下而上分析,
自由发现文本中可能影响裁判与量刑的因素;不得套用预设字段清单。每个因素给出 key(英文 snake_case)、
name_cn、charge(通用或给定罪名)、kindnumeric/bool/categorical)、values(只列实际观察值)与简短证据说明。
只返回 JSON{"factors":[...]}。"""
def discovery_batch(cache: CachedCalls, rows: List[Dict[str, Any]], index: int):
cases = [{"id": row["id"], "charge": row["charge"], "fact": row["fact"]} for row in rows]
return cache.json_call(
provider="ark",
purpose=f"3-12 bottom-up factor discovery batch {index}",
messages=[{"role": "system", "content": DISCOVERY_SYSTEM}, {"role": "user", "content": json.dumps(cases, ensure_ascii=False)}],
max_tokens=3500,
key=f"discovery-{index:03d}",
)
def normalize_schema(raw: Dict[str, Any]) -> Dict[str, Any]:
output = {"core": [], "extensions": {charge: [] for charge in CHARGES}}
used = set()
for section, values in [("core", raw.get("core") or [])] + [(charge, (raw.get("extensions") or {}).get(charge) or []) for charge in CHARGES]:
for item in values:
key = re.sub(r"[^a-z0-9_]+", "_", str(item.get("key", "")).lower()).strip("_")
kind = str(item.get("kind", "")).lower()
if not key or key in used or kind not in {"numeric", "bool", "categorical"}:
continue
used.add(key)
factor = {
"key": key,
"name_cn": str(item.get("name_cn") or key),
"kind": kind,
"values": [str(value) for value in (item.get("values") or [])][:12] if kind == "categorical" else [],
"direction": str(item.get("direction") or "neutral"),
"question": str(item.get("question") or f"请补充{item.get('name_cn') or key}情况。"),
}
(output["core"] if section == "core" else output["extensions"][section]).append(factor)
return output
def consolidate_schema(cache: CachedCalls, raw_factors: List[Dict[str, Any]]):
system = f"""你是司法数据建模专家。下面是从 360 条真实 CAIL2018 训练案件分批自由发现的原始因素。
仅根据这些发现归并同义项,形成模块化 schema。core 最多 16 个跨罪名通用因素;extensions 必须且只能有
{list(CHARGES)} 三个键,每个最多 12 个罪名特有因素。不要引入原始发现中没有的因素。每项字段:
key,name_cn,kind(numeric|bool|categorical),values,direction(aggravating|mitigating|neutral),question。
只返回 JSON{{"core":[],"extensions":{{"盗窃罪":[],"故意伤害罪":[],"诈骗罪":[]}}}}"""
parsed, receipt = cache.json_call(
provider="ark",
purpose="3-12 consolidate bottom-up factors",
messages=[{"role": "system", "content": system}, {"role": "user", "content": json.dumps(raw_factors, ensure_ascii=False)}],
max_tokens=5000,
key="schema-consolidation",
)
return normalize_schema(parsed), receipt
def factors_for(schema: Dict[str, Any], charge: str) -> List[Dict[str, Any]]:
result, used = [], set()
for factor in schema["core"] + schema["extensions"][charge]:
if factor["key"] not in used:
result.append(factor)
used.add(factor["key"])
return result
def extraction_batch(cache: CachedCalls, schema: Dict[str, Any], rows: List[Dict[str, Any]], index: int):
cases = [{"id": row["id"], "charge": row["charge"], "fact": row["fact"]} for row in rows]
system = """你是司法判例结构化抽取器。严格按给定的、由训练数据自下而上发现的 schema 抽取。
numeric 输出数值,bool 输出 true/falsecategorical 取 schema 值;文本未提及必须为 null,不得推断。
保持每个 id 与 charge。只返回 JSON{"cases":[{"id":"...","charge":"...","factors":{...}},...]}。"""
return cache.json_call(
provider="ark",
purpose=f"3-12 modular extraction batch {index}",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": f"DISCOVERED SCHEMA:\n{json.dumps(schema, ensure_ascii=False)}\n\nCASES:\n{json.dumps(cases, ensure_ascii=False)}"},
],
max_tokens=6000,
key=f"extraction-{index:03d}",
)
def extraction_case(cache: CachedCalls, schema: Dict[str, Any], row: Dict[str, Any]):
"""Resume a provider-sensitive missing batch one case at a time.
Some real CAIL fact combinations can make a whole ten-case request stall or
trip provider filtering. A one-case retry preserves the identical schema,
source text, model, and extraction contract while retaining a receipt for
every recovered row instead of invalidating hundreds of completed calls.
"""
return cache.json_call(
provider="ark",
purpose=f"3-12 modular extraction case {row['id']}",
messages=[
{
"role": "system",
"content": (
"你是司法判例结构化抽取器。严格按给定的、由训练数据自下而上发现的 schema 抽取。"
"numeric 输出数值,bool 输出 true/falsecategorical 取 schema 值;文本未提及必须为 null,"
"不得推断。保持 id 与 charge。只返回 JSON"
'{"cases":[{"id":"...","charge":"...","factors":{...}}]}。'
),
},
{
"role": "user",
"content": (
f"DISCOVERED SCHEMA:\n{json.dumps(schema, ensure_ascii=False)}\n\n"
f"CASE:\n{json.dumps({'id': row['id'], 'charge': row['charge'], 'fact': row['fact']}, ensure_ascii=False)}"
),
},
],
max_tokens=2000,
key=f"extraction-case-{row['id']}",
)
def normalize_value(value: Any, factor: Dict[str, Any]):
if value is None or value == "":
return None
if factor["kind"] == "numeric":
if isinstance(value, (int, float)):
return float(value)
found = re.search(r"-?\d+(?:\.\d+)?", str(value).replace(",", ""))
return float(found.group()) if found else None
if factor["kind"] == "bool":
if isinstance(value, bool):
return value
text = str(value).strip().lower()
if text in {"true", "1", "", "", "存在"}:
return True
if text in {"false", "0", "", "", "不存在"}:
return False
return None
return str(value)
def normalize_extractions(schema: Dict[str, Any], source_rows: List[Dict[str, Any]], batch_outputs: List[Dict[str, Any]]):
raw_by_id = {}
for output in batch_outputs:
for item in output.get("cases") or []:
raw_by_id[str(item.get("id"))] = item
results, missing = [], []
for row in source_rows:
item = raw_by_id.get(row["id"])
if not item:
missing.append(row["id"])
continue
raw = item.get("factors") or item
extraction = {"charge": row["charge"]}
for factor in factors_for(schema, row["charge"]):
extraction[factor["key"]] = normalize_value(raw.get(factor["key"]), factor)
results.append({**row, "extracted": extraction})
if missing:
raise RuntimeError(f"live extraction omitted {len(missing)} cases: {missing[:5]}")
return results
def missing_extraction_rows(
source_rows: List[Dict[str, Any]], outputs: Iterable[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""Return source rows omitted by otherwise successful extraction calls."""
extracted_ids = {
str(item.get("id"))
for output in outputs
for item in (output.get("cases") or [])
if item.get("id") is not None
}
return [row for row in source_rows if row["id"] not in extracted_ids]
def build_feature_space(schema: Dict[str, Any], training: List[Dict[str, Any]]):
factor_by_key = {}
for factor in schema["core"] + [f for charge in CHARGES for f in schema["extensions"][charge]]:
factor_by_key.setdefault(factor["key"], factor)
columns = [f"charge={charge}" for charge in CHARGES]
for key, factor in factor_by_key.items():
if factor["kind"] == "numeric":
columns.append(f"num:{key}")
elif factor["kind"] == "bool":
columns.append(f"bool:{key}")
else:
values = set(factor.get("values") or [])
values.update(str(row["extracted"].get(key)) for row in training if row["extracted"].get(key) is not None)
columns.extend(f"cat:{key}={value}" for value in sorted(values))
return columns, factor_by_key
def vectorize(extraction: Dict[str, Any], columns: List[str]):
values, known = [], []
for column in columns:
if column.startswith("charge="):
values.append(1.0 if extraction.get("charge") == column[7:] else 0.0)
known.append(True)
elif column.startswith("num:"):
value = extraction.get(column[4:])
values.append(math.log1p(max(0.0, float(value))) if value is not None else 0.0)
known.append(value is not None)
elif column.startswith("bool:"):
value = extraction.get(column[5:])
values.append(1.0 if value is True else 0.0)
known.append(value is not None)
else:
key, expected = column[4:].split("=", 1)
value = extraction.get(key)
values.append(1.0 if value is not None and str(value) == expected else 0.0)
known.append(value is not None)
return np.asarray(values), np.asarray(known)
def fit_prototypes(schema: Dict[str, Any], training: List[Dict[str, Any]], seed: int):
columns, factor_by_key = build_feature_space(schema, training)
raw = np.asarray([vectorize(row["extracted"], columns)[0] for row in training])
scaler = StandardScaler().fit(raw)
z_all = scaler.transform(raw)
prototypes, diagnostics = [], {}
for charge in CHARGES:
positions = [index for index, row in enumerate(training) if row["charge"] == charge]
values = z_all[positions]
candidates = []
for k in range(2, 6):
model = KMeans(n_clusters=k, n_init=20, random_state=seed).fit(values)
score = float(silhouette_score(values, model.labels_))
candidates.append({"k": k, "silhouette": score, "model": model})
best = max(candidates, key=lambda item: item["silhouette"])
diagnostics[charge] = {"n": len(positions), "candidates": [{"k": item["k"], "silhouette": item["silhouette"]} for item in candidates], "selected_k": best["k"], "selected_silhouette": best["silhouette"]}
for cluster in range(best["k"]):
members_local = np.where(best["model"].labels_ == cluster)[0]
members = [positions[index] for index in members_local]
centroid = best["model"].cluster_centers_[cluster]
months = np.asarray([training[index]["label_months"] for index in members], dtype=float)
significant = [index for index in np.argsort(-np.abs(centroid)) if not columns[index].startswith("charge=")][:8]
prototypes.append(
{
"id": f"{charge}-prototype-{cluster}",
"charge": charge,
"size": len(members),
"centroid_std": centroid.tolist(),
"sentence_months": {
"median": float(np.median(months)),
"q25": float(np.percentile(months, 25)),
"q75": float(np.percentile(months, 75)),
"min": float(months.min()),
"max": float(months.max()),
},
"defining_features": [{"feature": columns[index], "z": float(centroid[index])} for index in significant],
}
)
centroids = np.asarray([item["centroid_std"] for item in prototypes])
weights = np.asarray([item["size"] for item in prototypes], dtype=float)
weights /= weights.sum()
between = (weights[:, None] * np.square(centroids)).sum(axis=0)
importance = [{"feature": columns[index], "score": float(between[index])} for index in np.argsort(-between)]
model = {
"columns": columns,
"scaler_mean": scaler.mean_.tolist(),
"scaler_scale": scaler.scale_.tolist(),
"prototypes": prototypes,
"importance": importance,
"diagnostics": diagnostics,
"training_samples": len(training),
}
return model
def match_prototype(model: Dict[str, Any], extraction: Dict[str, Any]):
raw, known = vectorize(extraction, model["columns"])
scale = np.asarray(model["scaler_scale"])
scale[scale == 0] = 1
z = (raw - np.asarray(model["scaler_mean"])) / scale
candidates = [item for item in model["prototypes"] if item["charge"] == extraction["charge"]]
best = None
for item in candidates:
distance = float(np.linalg.norm((z - np.asarray(item["centroid_std"])) * known) / max(1, known.sum()))
if best is None or distance < best[1]:
best = (item, distance)
return best
def advice_one(cache: CachedCalls, row: Dict[str, Any], prototype: Dict[str, Any], distance: float, index: int):
allowed = {
"prototype_id": prototype["id"],
"charge": prototype["charge"],
"prototype_size": prototype["size"],
"sentence_months": prototype["sentence_months"],
"defining_features": prototype["defining_features"],
"match_distance": distance,
}
messages = [
{
"role": "system",
"content": (
"你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、"
"外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON:"
'{"advice":"..."}。不要写免责声明,系统会统一附加。'
),
},
{"role": "user", "content": f"HELDOUT EXTRACTED FACTORS:\n{json.dumps(row['extracted'], ensure_ascii=False)}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{json.dumps(allowed, ensure_ascii=False)}"},
]
parsed, receipt = cache.json_call(provider="ark", purpose=f"3-12 held-out prototype-grounded advice {row['id']}", messages=messages, max_tokens=900, key=f"advice-{index:03d}-{row['id']}")
advice = str(parsed.get("advice") or "").strip() + "\n\n" + DISCLAIMER
return {"id": row["id"], "charge": row["charge"], "extracted": row["extracted"], "prototype": allowed, "advice": advice, "source_fact": row["fact"], "actual_label_months": row["label_months"], "advice_request_excludes_label": "label_months" not in json.dumps(receipt.get("request", {}), ensure_ascii=False)}, receipt
def judge_one(cache: CachedCalls, result: Dict[str, Any], index: int):
messages = [
{
"role": "system",
"content": (
"你是独立司法数据实验评审。评估建议是否忠实于给定原型统计、是否把统计当成不确定参考而非承诺、"
"对留出案件是否有用、是否包含明确法律免责声明。不要补充法律意见。只返回 JSON。"
),
},
{
"role": "user",
"content": f"""留出案件事实:{result['source_fact']}
真实裁判刑期(仅供事后评估,建议生成器从未看到):{result['actual_label_months']}个月
匹配原型:{json.dumps(result['prototype'], ensure_ascii=False)}
生成建议:{result['advice']}
返回:{{"prototype_grounding":1,"numeric_fidelity":1,"uncertainty_and_caution":1,"heldout_usefulness":1,
"disclaimer_present":true,"unsupported_claim":false,"reasoning":"..."}};分数 1-4。""",
},
]
parsed, receipt = cache.json_call(provider="moonshot", purpose=f"3-12 independent held-out judge {result['id']}", messages=messages, max_tokens=700, key=f"judge-{index:03d}-{result['id']}")
result["judge"] = parsed
return result, receipt
def token_usage(receipts: Iterable[Dict[str, Any]]):
totals = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
for receipt in receipts:
current = receipt.get("usage") or {}
for key in totals:
totals[key] += int(current.get(key) or 0)
return totals
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--archive", default=str(HERE / "data" / "official" / "CAIL2018_ALL_DATA.zip"))
parser.add_argument("--discovery-model", default=os.getenv("ARK_MODEL", "doubao-seed-1-6-250615"))
parser.add_argument("--judge-model", default=os.getenv("MEMORY_JUDGE_MODEL", "moonshot-v1-32k"))
parser.add_argument("--discovery-endpoint", default=ARK_ENDPOINT)
parser.add_argument("--judge-endpoint", default=MOONSHOT_ENDPOINT)
parser.add_argument("--seed", type=int, default=37)
parser.add_argument("--workers", type=int, default=4)
parser.add_argument("--timeout", type=float, default=180)
parser.add_argument("--train-per-charge", type=int, default=120)
parser.add_argument("--heldout-per-charge", type=int, default=20)
parser.add_argument("--discovery-batch", type=int, default=30)
parser.add_argument("--extraction-batch", type=int, default=10)
parser.add_argument("--advice-per-charge", type=int, default=4)
args = parser.parse_args()
if not os.getenv("ARK_API_KEY") or not (os.getenv("MOONSHOT_API_KEY") or os.getenv("KIMI_API_KEY")):
raise RuntimeError("ARK_API_KEY and MOONSHOT_API_KEY/KIMI_API_KEY are required")
archive, sample_path, rows, member_meta = load_or_build_sample(args)
training = [row for row in rows if row["split"] == "train"]
heldout = [row for row in rows if row["split"] == "heldout"]
cache = CachedCalls(args)
receipts, errors = [], []
shuffled = list(training)
random.Random(args.seed).shuffle(shuffled)
discovery_groups = [shuffled[start : start + args.discovery_batch] for start in range(0, len(shuffled), args.discovery_batch)]
discovery_outputs: Dict[int, List[Dict[str, Any]]] = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {pool.submit(discovery_batch, cache, group, index): index for index, group in enumerate(discovery_groups)}
for future in concurrent.futures.as_completed(futures):
index = futures[future]
try:
parsed, receipt = future.result()
discovery_outputs[index] = parsed.get("factors") or []
receipts.append(receipt)
print(f"discovery batch {index + 1}/{len(discovery_groups)}", flush=True)
except Exception as exc:
errors.append({"stage": "discovery", "batch": index, "type": type(exc).__name__, "error": str(exc)})
# Consolidation used to consume futures in completion order. That made its
# checkpoint signature change on every resume even though every discovery
# response was identical. Persist the original order once; for campaigns
# created before this receipt existed, checkpoint mtimes reconstruct the
# exact completion order that produced the saved consolidation response.
discovery_order_path = cache.root / "discovery-order.json"
if discovery_order_path.exists():
discovery_order = json.loads(discovery_order_path.read_text(encoding="utf-8"))["batch_order"]
else:
checkpoint_paths = [cache.root / f"discovery-{index:03d}.json" for index in discovery_outputs]
discovery_order = [
int(path.stem.rsplit("-", 1)[1])
for path in sorted(checkpoint_paths, key=lambda path: (path.stat().st_mtime_ns, path.name))
]
temporary = discovery_order_path.with_suffix(".tmp")
temporary.write_text(
json.dumps({"batch_order": discovery_order}, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
temporary.replace(discovery_order_path)
if sorted(discovery_order) != list(range(len(discovery_groups))) or set(discovery_outputs) != set(discovery_order):
raise RuntimeError("discovery campaign is missing batches or has an invalid persisted order")
raw_discovery: List[Dict[str, Any]] = [
factor for index in discovery_order for factor in discovery_outputs[index]
]
schema, receipt = consolidate_schema(cache, raw_discovery)
receipts.append(receipt)
if not schema["core"] or any(not schema["extensions"][charge] for charge in CHARGES):
raise RuntimeError("live bottom-up schema is missing core or charge extension factors")
extraction_groups = [rows[start : start + args.extraction_batch] for start in range(0, len(rows), args.extraction_batch)]
outputs: Dict[str, Dict[str, Any]] = {}
existing_batch_checkpoints = list(cache.root.glob("extraction-[0-9][0-9][0-9].json"))
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {}
for index, group in enumerate(extraction_groups):
batch_checkpoint = cache.root / f"extraction-{index:03d}.json"
if batch_checkpoint.exists() or not existing_batch_checkpoints:
futures[pool.submit(extraction_batch, cache, schema, group, index)] = (
f"batch-{index:03d}", index, None
)
else:
# This is a resume with a missing/failed batch. Split only the
# missing work; completed batch signatures remain untouched.
for row in group:
futures[pool.submit(extraction_case, cache, schema, row)] = (
f"case-{row['id']}", index, row["id"]
)
for future in concurrent.futures.as_completed(futures):
key, index, case_id = futures[future]
try:
parsed, receipt = future.result()
outputs[key] = parsed
receipts.append(receipt)
label = f"batch {index + 1}" if case_id is None else f"case {case_id}"
print(f"extraction {label}", flush=True)
except Exception as exc:
errors.append({"stage": "extraction", "batch": index, "case": case_id, "type": type(exc).__name__, "error": str(exc)})
# A request can return valid JSON yet silently omit one or more records.
# Recover those records individually before normalization, just as we do
# for entirely missing batch checkpoints on resume.
omitted_rows = missing_extraction_rows(rows, outputs.values())
if omitted_rows:
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {pool.submit(extraction_case, cache, schema, row): row for row in omitted_rows}
for future in concurrent.futures.as_completed(futures):
row = futures[future]
try:
parsed, receipt = future.result()
outputs[f"case-{row['id']}"] = parsed
receipts.append(receipt)
print(f"extraction omitted case {row['id']}", flush=True)
except Exception as exc:
errors.append(
{
"stage": "extraction",
"batch": None,
"case": row["id"],
"type": type(exc).__name__,
"error": str(exc),
}
)
extracted = normalize_extractions(schema, rows, [outputs[key] for key in sorted(outputs)])
train_extracted = [row for row in extracted if row["split"] == "train"]
heldout_extracted = [row for row in extracted if row["split"] == "heldout"]
model = fit_prototypes(schema, train_extracted, args.seed)
evaluation_rows = []
for charge in CHARGES:
evaluation_rows.extend([row for row in heldout_extracted if row["charge"] == charge][: args.advice_per_charge])
advice_results: Dict[int, Dict[str, Any]] = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {}
for index, row in enumerate(evaluation_rows):
prototype, distance = match_prototype(model, row["extracted"])
futures[pool.submit(advice_one, cache, row, prototype, distance, index)] = index
for future in concurrent.futures.as_completed(futures):
index = futures[future]
try:
result, receipt = future.result()
advice_results[index] = result
receipts.append(receipt)
except Exception as exc:
errors.append({"stage": "advice", "case": evaluation_rows[index]["id"], "type": type(exc).__name__, "error": str(exc)})
judged: Dict[int, Dict[str, Any]] = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {pool.submit(judge_one, cache, result, index): index for index, result in advice_results.items()}
for future in concurrent.futures.as_completed(futures):
index = futures[future]
try:
result, receipt = future.result()
judged[index] = result
receipts.append(receipt)
print(f"held-out advice/judge {len(judged)}/{len(evaluation_rows)}", flush=True)
except Exception as exc:
errors.append({"stage": "judge", "case": advice_results[index]["id"], "type": type(exc).__name__, "error": str(exc)})
heldout_results = [judged[index] for index in sorted(judged)]
archive_hash = sha256_file(archive)
train_counts = Counter(row["charge"] for row in training)
heldout_counts = Counter(row["charge"] for row in heldout)
score_fields = ("prototype_grounding", "numeric_fidelity", "uncertainty_and_caution", "heldout_usefulness")
judge_scores = {
field: statistics.mean(float(row["judge"].get(field, 1)) for row in heldout_results)
for field in score_fields
} if heldout_results else {}
acceptance = {
"official_cail_url_revision_archive_hash": OFFICIAL_URL.startswith("https://cail.") and len(OFFICIAL_REVISION) == 40 and len(archive_hash) == 64,
"hundreds_real_training_samples": len(training) >= 300 and all(row["source_member"] == member_meta["member"] for row in training),
"balanced_train_heldout_split": train_counts == Counter({charge: args.train_per_charge for charge in CHARGES}) and heldout_counts == Counter({charge: args.heldout_per_charge for charge in CHARGES}),
"live_bottom_up_discovery": len(raw_discovery) > 0 and len([call for call in receipts if "factor discovery batch" in str(call.get("purpose"))]) == len(discovery_groups),
"modular_schema_discovered": bool(schema["core"]) and all(schema["extensions"][charge] for charge in CHARGES),
"live_extraction_train_and_heldout": len(extracted) == len(rows) and len([
call for call in receipts if "modular extraction" in str(call.get("purpose"))
]) >= len(extraction_groups),
"cluster_diagnostics_all_charges": all(model["diagnostics"][charge]["selected_k"] >= 2 and model["diagnostics"][charge]["selected_silhouette"] > -1 for charge in CHARGES),
"importance_model": bool(model["importance"]) and bool(model["prototypes"]),
"heldout_advice_only_prototype_statistics": len(heldout_results) == len(evaluation_rows) and all(row["advice_request_excludes_label"] for row in heldout_results),
"independent_external_judge": len([call for call in receipts if call.get("provider") == "moonshot"]) == len(evaluation_rows),
"legal_disclaimer": bool(heldout_results) and all(DISCLAIMER in row["advice"] and bool(row["judge"].get("disclaimer_present")) for row in heldout_results),
"raw_request_response_receipts": bool(receipts) and all("request" in call and "response" in call for call in receipts),
"all_calls_succeeded": not errors,
}
acceptance["passed"] = all(acceptance.values())
evidence = {
"status": "passed" if acceptance["passed"] else ("partial" if receipts else "blocked"),
"official_source": {
"repository": OFFICIAL_REPOSITORY,
"repository_revision": OFFICIAL_REVISION,
"archive_url": OFFICIAL_URL,
"archive_path": str(archive),
"archive_sha256": archive_hash,
"archive_bytes": archive.stat().st_size,
"training_member": member_meta,
"sample_path": str(sample_path),
"sample_sha256": sha256_file(sample_path),
},
"configuration": vars(args),
"scope": {"training": len(training), "heldout": len(heldout), "train_by_charge": dict(train_counts), "heldout_by_charge": dict(heldout_counts), "advice_judged": len(heldout_results)},
"acceptance": acceptance,
"summary": {
"schema": {"core_factors": len(schema["core"]), "extension_factors": {charge: len(schema["extensions"][charge]) for charge in CHARGES}},
"clustering": model["diagnostics"],
"prototypes": len(model["prototypes"]),
"heldout_judge_means": judge_scores,
"api_calls": len(receipts),
"token_usage": token_usage(receipts),
"errors": len(errors),
},
"errors": errors,
"discovered_schema": schema,
"extractions": extracted,
"prototype_model": model,
"heldout_advice": heldout_results,
}
manifest = write_campaign_evidence(HERE, "3-12", evidence, receipts, input_paths=[HERE / "campaign.py", archive, sample_path])
print(json.dumps(manifest["summary"], ensure_ascii=False, indent=2))
print(f"Canonical evidence: {HERE / 'validation' / 'latest.json'}")
return 0 if acceptance["passed"] else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,93 @@
"""
全局配置:加载环境变量、提供 OpenAI 客户端与默认模型名。
只依赖官方 OpenAI SDK,读取 OPENAI_API_KEY。
默认模型 gpt-5.6-luna(便宜、够用于因子发现、结构化抽取与文案生成)。
"""
import os
from openai import OpenAI
try:
# 可选:如果安装了 python-dotenv,则自动加载同目录 .env
from dotenv import load_dotenv
load_dotenv()
except Exception: # pragma: no cover - dotenv 是可选依赖
pass
def _openrouter_model_id(model) -> str:
"""将供应商原生模型名映射为 OpenRouter 模型 id(通用 OpenRouter 回退用)。
显式的 OPENROUTER_MODEL 环境变量优先。"""
override = os.getenv("OPENROUTER_MODEL")
if override:
return override
m = (model or "").strip()
if not m:
return "openai/gpt-5.6-luna"
if "/" in m:
return m
ml = m.lower()
if ml.startswith(("gpt-", "o1", "o3", "o4", "chatgpt")):
return "openai/" + m
if ml.startswith("claude-"):
return "anthropic/claude-opus-4.8"
if ml.startswith("kimi"):
# kimi-k3 is not on OpenRouter; moonshotai/kimi-k2.6 is the closest hosted id.
return "moonshotai/kimi-k2.6"
return "openai/gpt-5.6-luna"
# Provider: OpenAI by default; DashScope/Qwen/Bailian are OpenAI-compatible.
PROVIDER = os.getenv("LLM_PROVIDER", "openai").lower()
PROVIDER = {"qwen": "dashscope", "bailian": "dashscope"}.get(PROVIDER, PROVIDER)
# 默认模型,可用环境变量覆盖
MODEL = os.getenv(
"DASHSCOPE_MODEL" if PROVIDER == "dashscope" else "OPENAI_MODEL",
"qwen3.7-plus" if PROVIDER == "dashscope" else "gpt-5.6-luna",
)
# 通用 OpenRouter 回退:若没有 OPENAI_API_KEY 但设置了 OPENROUTER_API_KEY
# 则把聊天模型路由到 OpenRouter,并把模型名映射为 OpenRouter 的 id。
_OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
_DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
_OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
# gpt-5.x(含 gpt-5.6*)在 OpenAI 直连 API 上需要组织实名认证;只要设置了
# OPENROUTER_API_KEY,就优先把这类 id 走 OpenRouter。
_PREFER_OPENROUTER = bool(_OPENROUTER_API_KEY) and MODEL.lower().startswith("gpt-5")
_PRIMARY_API_KEY = _DASHSCOPE_API_KEY if PROVIDER == "dashscope" else _OPENAI_API_KEY
_USE_OPENROUTER = _PREFER_OPENROUTER or ((not _PRIMARY_API_KEY) and bool(_OPENROUTER_API_KEY))
if _USE_OPENROUTER:
MODEL = _openrouter_model_id(MODEL)
def get_client() -> OpenAI:
"""返回一个配置好的 OpenAI 客户端。
优先使用官方端点(读取 OPENAI_API_KEY);若缺失则在存在 OPENROUTER_API_KEY 时
回退到 OpenRouterOpenAI 兼容端点)。"""
# timeout + 自动重试:发现/抽取阶段要连续发几十次请求,单次瞬时错误
# (网络抖动 / 限流 / 5xx)不应中断整条流水线。
if _PRIMARY_API_KEY and not _PREFER_OPENROUTER:
if PROVIDER == "dashscope":
return OpenAI(
api_key=_DASHSCOPE_API_KEY,
base_url=os.getenv(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1",
),
timeout=60.0,
max_retries=5,
)
return OpenAI(api_key=_OPENAI_API_KEY, timeout=60.0, max_retries=5)
if _OPENROUTER_API_KEY:
return OpenAI(
api_key=_OPENROUTER_API_KEY,
base_url="https://openrouter.ai/api/v1",
timeout=60.0,
max_retries=5,
)
raise RuntimeError(
"未找到所选 provider 的 API Key,请设置 OPENAI_API_KEY、DASHSCOPE_API_KEY "
"或 OPENROUTER_API_KEY。"
)
@@ -0,0 +1,66 @@
{"id": "theft_24", "charge": "盗窃罪", "fact": "被告人冯某,男。此前无违法犯罪记录。经审理查明:被告人冯某伙同他人结伙在某电动车棚内窃取他人财物,经鉴定价值人民币34170元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 34170, "prior_record": false, "surrender": false, "restitution": true, "confession": true, "burglary": false, "carry_weapon": false, "gang": true}, "label_months": 30}
{"id": "assault_06", "charge": "故意伤害罪", "fact": "被告人冯某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人冯某因琐事发生口角后,纠集多人殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭认罪认罚。", "gold": {"prior_record": true, "surrender": false, "restitution": false, "confession": true, "injury_level": "轻微伤", "armed": false, "premeditated": false, "gang": true}, "label_months": 12}
{"id": "assault_07", "charge": "故意伤害罪", "fact": "被告人严某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人严某因积怨已久、事先预谋,纠集多人殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭辩称系正当防卫。", "gold": {"prior_record": true, "surrender": false, "restitution": false, "confession": false, "injury_level": "轻微伤", "armed": false, "premeditated": true, "gang": true}, "label_months": 23}
{"id": "assault_18", "charge": "故意伤害罪", "fact": "被告人严某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人严某因琐事发生口角后,纠集多人殴打被害人,致其经鉴定为轻伤二级。案发后主动投案自首,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": true, "surrender": true, "restitution": true, "confession": true, "injury_level": "轻伤", "armed": false, "premeditated": false, "gang": true}, "label_months": 5}
{"id": "theft_11", "charge": "盗窃罪", "fact": "被告人郑某,男。此前无违法犯罪记录。经审理查明:被告人郑某伙同他人结伙在某网吧内窃取他人财物,经鉴定价值人民币107830元。案发后主动到公安机关投案自首,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 107830, "prior_record": false, "surrender": true, "restitution": true, "confession": true, "burglary": false, "carry_weapon": false, "gang": true}, "label_months": 27}
{"id": "theft_23", "charge": "盗窃罪", "fact": "被告人孙某,男。此前无违法犯罪记录。经审理查明:被告人孙某单独在某手机专卖店内窃取他人财物,经鉴定价值人民币24170元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 24170, "prior_record": false, "surrender": false, "restitution": true, "confession": true, "burglary": false, "carry_weapon": false, "gang": false}, "label_months": 24}
{"id": "assault_19", "charge": "故意伤害罪", "fact": "被告人华某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人华某因积怨已久、事先预谋,持赤手空拳殴打被害人,致其经鉴定为重伤二级。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": true, "surrender": false, "restitution": true, "confession": true, "injury_level": "重伤", "armed": false, "premeditated": true, "gang": false}, "label_months": 43}
{"id": "theft_13", "charge": "盗窃罪", "fact": "被告人姜某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人姜某单独翻窗入户进入被害人位于某网吧的住宅内,作案时随身携带匕首一把窃取他人财物,经鉴定价值人民币130680元。后被公安机关抓获归案,赃款已被挥霍,未退赔。当庭对指控予以否认。", "gold": {"amount": 130680, "prior_record": true, "surrender": false, "restitution": false, "confession": false, "burglary": true, "carry_weapon": true, "gang": false}, "label_months": 67}
{"id": "assault_11", "charge": "故意伤害罪", "fact": "被告人朱某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人朱某因琐事发生口角后,持赤手空拳殴打被害人,致其经鉴定为轻伤二级。案发后主动投案自首,未赔偿被害人损失。当庭认罪认罚。", "gold": {"prior_record": true, "surrender": true, "restitution": false, "confession": true, "injury_level": "轻伤", "armed": false, "premeditated": false, "gang": false}, "label_months": 11}
{"id": "theft_17", "charge": "盗窃罪", "fact": "被告人郑某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人郑某伙同他人结伙在某小区内窃取他人财物,经鉴定价值人民币206390元。后被公安机关抓获归案,赃款已被挥霍,未退赔。当庭对指控予以否认。", "gold": {"amount": 206390, "prior_record": true, "surrender": false, "restitution": false, "confession": false, "burglary": false, "carry_weapon": false, "gang": true}, "label_months": 58}
{"id": "theft_19", "charge": "盗窃罪", "fact": "被告人郑某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人郑某单独翻窗入户进入被害人位于某小区的住宅内,作案时随身携带匕首一把窃取他人财物,经鉴定价值人民币344880元。案发后主动到公安机关投案自首,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 344880, "prior_record": true, "surrender": true, "restitution": true, "confession": true, "burglary": true, "carry_weapon": true, "gang": false}, "label_months": 51}
{"id": "theft_21", "charge": "盗窃罪", "fact": "被告人姜某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人姜某单独翻窗入户进入被害人位于某商场的住宅内窃取他人财物,经鉴定价值人民币67110元。案发后主动到公安机关投案自首,赃款已被挥霍,未退赔。当庭对指控予以否认。", "gold": {"amount": 67110, "prior_record": true, "surrender": true, "restitution": false, "confession": false, "burglary": true, "carry_weapon": false, "gang": false}, "label_months": 49}
{"id": "fraud_03", "charge": "诈骗罪", "fact": "被告人邹某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人邹某以帮忙办事为由,虚构事实骗取被害人钱财,骗取财物共计人民币686530元。案发后主动投案自首,赃款未追回。当庭否认诈骗故意。", "gold": {"amount": 686530, "prior_record": true, "surrender": true, "restitution": false, "confession": false, "scam_type": "普通", "victim_count": 4, "gang": false}, "label_months": 64}
{"id": "theft_12", "charge": "盗窃罪", "fact": "被告人赵某,男。此前无违法犯罪记录。经审理查明:被告人赵某单独翻窗入户进入被害人位于某手机专卖店的住宅内窃取他人财物,经鉴定价值人民币250680元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 250680, "prior_record": false, "surrender": false, "restitution": true, "confession": true, "burglary": true, "carry_weapon": false, "gang": false}, "label_months": 45}
{"id": "fraud_16", "charge": "诈骗罪", "fact": "被告人谢某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人谢某伙同他人组成团伙,通过拨打电话、发送短信等电信网络手段,虚构投资项目骗取6名被害人钱财,骗取财物共计人民币362950元。后被公安机关抓获,已退赔全部赃款。当庭否认诈骗故意。", "gold": {"amount": 362950, "prior_record": true, "surrender": false, "restitution": true, "confession": false, "scam_type": "电信网络", "victim_count": 6, "gang": true}, "label_months": 74}
{"id": "fraud_02", "charge": "诈骗罪", "fact": "被告人钱某,男。此前无犯罪记录。经审理查明:被告人钱某在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币336580元。案发后主动投案自首,赃款未追回。当庭认罪认罚。", "gold": {"amount": 336580, "prior_record": false, "surrender": true, "restitution": false, "confession": true, "scam_type": "合同", "victim_count": 5, "gang": false}, "label_months": 50}
{"id": "theft_02", "charge": "盗窃罪", "fact": "被告人赵某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人赵某单独翻窗入户进入被害人位于某网吧的住宅内窃取他人财物,经鉴定价值人民币202880元。案发后主动到公安机关投案自首,赃款已被挥霍,未退赔。当庭认罪认罚。", "gold": {"amount": 202880, "prior_record": true, "surrender": true, "restitution": false, "confession": true, "burglary": true, "carry_weapon": false, "gang": false}, "label_months": 52}
{"id": "fraud_10", "charge": "诈骗罪", "fact": "被告人许某,男。此前无犯罪记录。经审理查明:被告人许某伙同他人组成团伙,以帮忙办事为由,虚构事实骗取被害人钱财,骗取财物共计人民币464030元。案发后主动投案自首,赃款未追回。当庭否认诈骗故意。", "gold": {"amount": 464030, "prior_record": false, "surrender": true, "restitution": false, "confession": false, "scam_type": "普通", "victim_count": 4, "gang": true}, "label_months": 59}
{"id": "fraud_05", "charge": "诈骗罪", "fact": "被告人魏某,男。此前无犯罪记录。经审理查明:被告人魏某以帮忙办事为由,虚构事实骗取被害人钱财,骗取财物共计人民币581550元。后被公安机关抓获,赃款未追回。当庭认罪认罚。", "gold": {"amount": 581550, "prior_record": false, "surrender": false, "restitution": false, "confession": true, "scam_type": "普通", "victim_count": 2, "gang": false}, "label_months": 59}
{"id": "fraud_13", "charge": "诈骗罪", "fact": "被告人魏某,男。此前无犯罪记录。经审理查明:被告人魏某以帮忙办事为由,虚构事实骗取被害人钱财,骗取财物共计人民币621520元。后被公安机关抓获,已退赔全部赃款。当庭认罪认罚。", "gold": {"amount": 621520, "prior_record": false, "surrender": false, "restitution": true, "confession": true, "scam_type": "普通", "victim_count": 4, "gang": false}, "label_months": 53}
{"id": "fraud_17", "charge": "诈骗罪", "fact": "被告人卫某,男。此前无犯罪记录。经审理查明:被告人卫某伙同他人组成团伙,在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币280260元。案发后主动投案自首,已退赔全部赃款。当庭认罪认罚。", "gold": {"amount": 280260, "prior_record": false, "surrender": true, "restitution": true, "confession": true, "scam_type": "合同", "victim_count": 3, "gang": true}, "label_months": 48}
{"id": "theft_01", "charge": "盗窃罪", "fact": "被告人沈某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人沈某伙同他人结伙在某商场内窃取他人财物,经鉴定价值人民币256310元。案发后主动到公安机关投案自首,已退赔全部赃款并取得谅解。当庭对指控予以否认。", "gold": {"amount": 256310, "prior_record": true, "surrender": true, "restitution": true, "confession": false, "burglary": false, "carry_weapon": false, "gang": true}, "label_months": 46}
{"id": "assault_02", "charge": "故意伤害罪", "fact": "被告人谢某,男。平时表现尚可,无前科。经审理查明:被告人谢某因琐事发生口角后,持赤手空拳殴打被害人,致其经鉴定为轻伤二级。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": false, "restitution": true, "confession": true, "injury_level": "轻伤", "armed": false, "premeditated": false, "gang": false}, "label_months": 1}
{"id": "assault_21", "charge": "故意伤害罪", "fact": "被告人褚某,男。平时表现尚可,无前科。经审理查明:被告人褚某因琐事发生口角后,持赤手空拳殴打被害人,致其经鉴定为重伤二级。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭辩称系正当防卫。", "gold": {"prior_record": false, "surrender": false, "restitution": false, "confession": false, "injury_level": "重伤", "armed": false, "premeditated": false, "gang": false}, "label_months": 41}
{"id": "theft_18", "charge": "盗窃罪", "fact": "被告人吕某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人吕某伙同他人结伙在某网吧内窃取他人财物,经鉴定价值人民币264100元。案发后主动到公安机关投案自首,赃款已被挥霍,未退赔。当庭认罪认罚。", "gold": {"amount": 264100, "prior_record": true, "surrender": true, "restitution": false, "confession": true, "burglary": false, "carry_weapon": false, "gang": true}, "label_months": 49}
{"id": "fraud_08", "charge": "诈骗罪", "fact": "被告人魏某,男。此前无犯罪记录。经审理查明:被告人魏某通过拨打电话、发送短信等电信网络手段,虚构投资项目骗取2名被害人钱财,骗取财物共计人民币760090元。案发后主动投案自首,赃款未追回。当庭认罪认罚。", "gold": {"amount": 760090, "prior_record": false, "surrender": true, "restitution": false, "confession": true, "scam_type": "电信网络", "victim_count": 2, "gang": false}, "label_months": 58}
{"id": "fraud_04", "charge": "诈骗罪", "fact": "被告人曹某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人曹某通过拨打电话、发送短信等电信网络手段,虚构投资项目骗取32名被害人钱财,骗取财物共计人民币641500元。后被公安机关抓获,已退赔全部赃款。当庭认罪认罚。", "gold": {"amount": 641500, "prior_record": true, "surrender": false, "restitution": true, "confession": true, "scam_type": "电信网络", "victim_count": 32, "gang": false}, "label_months": 78}
{"id": "fraud_09", "charge": "诈骗罪", "fact": "被告人许某,男。此前无犯罪记录。经审理查明:被告人许某伙同他人组成团伙,在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币561980元。后被公安机关抓获,赃款未追回。当庭否认诈骗故意。", "gold": {"amount": 561980, "prior_record": false, "surrender": false, "restitution": false, "confession": false, "scam_type": "合同", "victim_count": 2, "gang": true}, "label_months": 67}
{"id": "theft_03", "charge": "盗窃罪", "fact": "被告人孙某,男。此前无违法犯罪记录。经审理查明:被告人孙某伙同他人结伙翻窗入户进入被害人位于某电动车棚的住宅内,作案时随身携带匕首一把窃取他人财物,经鉴定价值人民币322620元。案发后主动到公安机关投案自首,已退赔全部赃款并取得谅解。当庭对指控予以否认。", "gold": {"amount": 322620, "prior_record": false, "surrender": true, "restitution": true, "confession": false, "burglary": true, "carry_weapon": true, "gang": true}, "label_months": 49}
{"id": "theft_22", "charge": "盗窃罪", "fact": "被告人谢某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人谢某单独翻窗入户进入被害人位于某商场的住宅内,作案时随身携带匕首一把窃取他人财物,经鉴定价值人民币77170元。案发后主动到公安机关投案自首,赃款已被挥霍,未退赔。当庭认罪认罚。", "gold": {"amount": 77170, "prior_record": true, "surrender": true, "restitution": false, "confession": true, "burglary": true, "carry_weapon": true, "gang": false}, "label_months": 50}
{"id": "fraud_19", "charge": "诈骗罪", "fact": "被告人姜某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人姜某伙同他人组成团伙,在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币242630元。后被公安机关抓获,已退赔全部赃款。当庭认罪认罚。", "gold": {"amount": 242630, "prior_record": true, "surrender": false, "restitution": true, "confession": true, "scam_type": "合同", "victim_count": 2, "gang": true}, "label_months": 62}
{"id": "assault_09", "charge": "故意伤害罪", "fact": "被告人陶某,男。平时表现尚可,无前科。经审理查明:被告人陶某因积怨已久、事先预谋,持持械(砍刀)殴打被害人,致其经鉴定为轻伤二级。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭辩称系正当防卫。", "gold": {"prior_record": false, "surrender": false, "restitution": false, "confession": false, "injury_level": "轻伤", "armed": true, "premeditated": true, "gang": false}, "label_months": 30}
{"id": "theft_14", "charge": "盗窃罪", "fact": "被告人华某,男。此前无违法犯罪记录。经审理查明:被告人华某单独在某商场内窃取他人财物,经鉴定价值人民币29080元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 29080, "prior_record": false, "surrender": false, "restitution": true, "confession": true, "burglary": false, "carry_weapon": false, "gang": false}, "label_months": 26}
{"id": "theft_07", "charge": "盗窃罪", "fact": "被告人何某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人何某单独在某商场内窃取他人财物,经鉴定价值人民币275730元。案发后主动到公安机关投案自首,赃款已被挥霍,未退赔。当庭认罪认罚。", "gold": {"amount": 275730, "prior_record": true, "surrender": true, "restitution": false, "confession": true, "burglary": false, "carry_weapon": false, "gang": false}, "label_months": 49}
{"id": "fraud_12", "charge": "诈骗罪", "fact": "被告人邹某,男。此前无犯罪记录。经审理查明:被告人邹某伙同他人组成团伙,在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币91590元。后被公安机关抓获,赃款未追回。当庭否认诈骗故意。", "gold": {"amount": 91590, "prior_record": false, "surrender": false, "restitution": false, "confession": false, "scam_type": "合同", "victim_count": 4, "gang": true}, "label_months": 60}
{"id": "assault_22", "charge": "故意伤害罪", "fact": "被告人金某,男。平时表现尚可,无前科。经审理查明:被告人金某因琐事发生口角后,纠集多人殴打被害人,致其经鉴定为轻伤二级。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": false, "restitution": false, "confession": true, "injury_level": "轻伤", "armed": false, "premeditated": false, "gang": true}, "label_months": 12}
{"id": "assault_04", "charge": "故意伤害罪", "fact": "被告人李某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人李某因积怨已久、事先预谋,持赤手空拳殴打被害人,致其经鉴定为重伤二级。案发后主动投案自首,未赔偿被害人损失。当庭辩称系正当防卫。", "gold": {"prior_record": true, "surrender": true, "restitution": false, "confession": false, "injury_level": "重伤", "armed": false, "premeditated": true, "gang": false}, "label_months": 49}
{"id": "assault_14", "charge": "故意伤害罪", "fact": "被告人戚某,男。平时表现尚可,无前科。经审理查明:被告人戚某因积怨已久、事先预谋,持赤手空拳殴打被害人,致其经鉴定为轻伤二级。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭辩称系正当防卫。", "gold": {"prior_record": false, "surrender": false, "restitution": false, "confession": false, "injury_level": "轻伤", "armed": false, "premeditated": true, "gang": false}, "label_months": 18}
{"id": "fraud_07", "charge": "诈骗罪", "fact": "被告人钱某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人钱某通过拨打电话、发送短信等电信网络手段,虚构投资项目骗取31名被害人钱财,骗取财物共计人民币207800元。后被公安机关抓获,赃款未追回。当庭认罪认罚。", "gold": {"amount": 207800, "prior_record": true, "surrender": false, "restitution": false, "confession": true, "scam_type": "电信网络", "victim_count": 31, "gang": false}, "label_months": 77}
{"id": "fraud_14", "charge": "诈骗罪", "fact": "被告人吕某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人吕某以帮忙办事为由,虚构事实骗取被害人钱财,骗取财物共计人民币459320元。后被公安机关抓获,已退赔全部赃款。当庭认罪认罚。", "gold": {"amount": 459320, "prior_record": true, "surrender": false, "restitution": true, "confession": true, "scam_type": "普通", "victim_count": 3, "gang": false}, "label_months": 62}
{"id": "assault_05", "charge": "故意伤害罪", "fact": "被告人赵某,男。平时表现尚可,无前科。经审理查明:被告人赵某因琐事发生口角后,纠集多人并持械殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": false, "restitution": true, "confession": true, "injury_level": "轻微伤", "armed": true, "premeditated": false, "gang": true}, "label_months": 2}
{"id": "assault_01", "charge": "故意伤害罪", "fact": "被告人冯某,男。平时表现尚可,无前科。经审理查明:被告人冯某因琐事发生口角后,纠集多人殴打被害人,致其经鉴定为轻伤二级。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": false, "restitution": false, "confession": true, "injury_level": "轻伤", "armed": false, "premeditated": false, "gang": true}, "label_months": 12}
{"id": "assault_10", "charge": "故意伤害罪", "fact": "被告人曹某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人曹某因积怨已久、事先预谋,持赤手空拳殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭辩称系正当防卫。", "gold": {"prior_record": true, "surrender": false, "restitution": true, "confession": false, "injury_level": "轻微伤", "armed": false, "premeditated": true, "gang": false}, "label_months": 8}
{"id": "fraud_20", "charge": "诈骗罪", "fact": "被告人陶某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人陶某伙同他人组成团伙,通过拨打电话、发送短信等电信网络手段,虚构投资项目骗取37名被害人钱财,骗取财物共计人民币89250元。后被公安机关抓获,已退赔全部赃款。当庭否认诈骗故意。", "gold": {"amount": 89250, "prior_record": true, "surrender": false, "restitution": true, "confession": false, "scam_type": "电信网络", "victim_count": 37, "gang": true}, "label_months": 75}
{"id": "assault_16", "charge": "故意伤害罪", "fact": "被告人金某,男。平时表现尚可,无前科。经审理查明:被告人金某因琐事发生口角后,持赤手空拳殴打被害人,致其经鉴定为轻伤二级。案发后主动投案自首,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": true, "restitution": true, "confession": true, "injury_level": "轻伤", "armed": false, "premeditated": false, "gang": false}, "label_months": 1}
{"id": "fraud_01", "charge": "诈骗罪", "fact": "被告人周某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人周某伙同他人组成团伙,在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币69370元。案发后主动投案自首,赃款未追回。当庭认罪认罚。", "gold": {"amount": 69370, "prior_record": true, "surrender": true, "restitution": false, "confession": true, "scam_type": "合同", "victim_count": 2, "gang": true}, "label_months": 54}
{"id": "assault_15", "charge": "故意伤害罪", "fact": "被告人许某,男。平时表现尚可,无前科。经审理查明:被告人许某因琐事发生口角后,持持械(砍刀)殴打被害人,致其经鉴定为轻伤二级。案发后主动投案自首,未赔偿被害人损失。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": true, "restitution": false, "confession": true, "injury_level": "轻伤", "armed": true, "premeditated": false, "gang": false}, "label_months": 13}
{"id": "assault_03", "charge": "故意伤害罪", "fact": "被告人尤某,男。平时表现尚可,无前科。经审理查明:被告人尤某因积怨已久、事先预谋,持赤手空拳殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": false, "restitution": false, "confession": true, "injury_level": "轻微伤", "armed": false, "premeditated": true, "gang": false}, "label_months": 7}
{"id": "assault_12", "charge": "故意伤害罪", "fact": "被告人戚某,男。平时表现尚可,无前科。经审理查明:被告人戚某因琐事发生口角后,持持械(砍刀)殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": false, "restitution": true, "confession": true, "injury_level": "轻微伤", "armed": true, "premeditated": false, "gang": false}, "label_months": 1}
{"id": "theft_08", "charge": "盗窃罪", "fact": "被告人华某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人华某伙同他人结伙翻窗入户进入被害人位于某手机专卖店的住宅内窃取他人财物,经鉴定价值人民币262690元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 262690, "prior_record": true, "surrender": false, "restitution": true, "confession": true, "burglary": true, "carry_weapon": false, "gang": true}, "label_months": 60}
{"id": "theft_16", "charge": "盗窃罪", "fact": "被告人吕某,男。此前无违法犯罪记录。经审理查明:被告人吕某伙同他人结伙翻窗入户进入被害人位于某小区的住宅内,作案时随身携带匕首一把窃取他人财物,经鉴定价值人民币9880元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭对指控予以否认。", "gold": {"amount": 9880, "prior_record": false, "surrender": false, "restitution": true, "confession": false, "burglary": true, "carry_weapon": true, "gang": true}, "label_months": 39}
{"id": "theft_06", "charge": "盗窃罪", "fact": "被告人秦某,男。此前无违法犯罪记录。经审理查明:被告人秦某单独在某电动车棚内,作案时随身携带匕首一把窃取他人财物,经鉴定价值人民币268560元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭对指控予以否认。", "gold": {"amount": 268560, "prior_record": false, "surrender": false, "restitution": true, "confession": false, "burglary": false, "carry_weapon": true, "gang": false}, "label_months": 45}
{"id": "theft_20", "charge": "盗窃罪", "fact": "被告人曹某,男。此前无违法犯罪记录。经审理查明:被告人曹某单独翻窗入户进入被害人位于某小区的住宅内窃取他人财物,经鉴定价值人民币261380元。案发后主动到公安机关投案自首,已退赔全部赃款并取得谅解。当庭对指控予以否认。", "gold": {"amount": 261380, "prior_record": false, "surrender": true, "restitution": true, "confession": false, "burglary": true, "carry_weapon": false, "gang": false}, "label_months": 40}
{"id": "fraud_15", "charge": "诈骗罪", "fact": "被告人姜某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人姜某伙同他人组成团伙,在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币229490元。后被公安机关抓获,已退赔全部赃款。当庭否认诈骗故意。", "gold": {"amount": 229490, "prior_record": true, "surrender": false, "restitution": true, "confession": false, "scam_type": "合同", "victim_count": 5, "gang": true}, "label_months": 68}
{"id": "fraud_18", "charge": "诈骗罪", "fact": "被告人蒋某,男。此前无犯罪记录。经审理查明:被告人蒋某伙同他人组成团伙,以帮忙办事为由,虚构事实骗取被害人钱财,骗取财物共计人民币159700元。后被公安机关抓获,赃款未追回。当庭认罪认罚。", "gold": {"amount": 159700, "prior_record": false, "surrender": false, "restitution": false, "confession": true, "scam_type": "普通", "victim_count": 3, "gang": true}, "label_months": 56}
{"id": "fraud_11", "charge": "诈骗罪", "fact": "被告人姜某,男。此前无犯罪记录。经审理查明:被告人姜某通过拨打电话、发送短信等电信网络手段,虚构投资项目骗取37名被害人钱财,骗取财物共计人民币681430元。案发后主动投案自首,已退赔全部赃款。当庭认罪认罚。", "gold": {"amount": 681430, "prior_record": false, "surrender": true, "restitution": true, "confession": true, "scam_type": "电信网络", "victim_count": 37, "gang": false}, "label_months": 58}
{"id": "theft_10", "charge": "盗窃罪", "fact": "被告人邹某,男。此前无违法犯罪记录。经审理查明:被告人邹某单独翻窗入户进入被害人位于某网吧的住宅内窃取他人财物,经鉴定价值人民币153580元。后被公安机关抓获归案,赃款已被挥霍,未退赔。当庭对指控予以否认。", "gold": {"amount": 153580, "prior_record": false, "surrender": false, "restitution": false, "confession": false, "burglary": true, "carry_weapon": false, "gang": false}, "label_months": 50}
{"id": "theft_04", "charge": "盗窃罪", "fact": "被告人蒋某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人蒋某伙同他人结伙在某电动车棚内窃取他人财物,经鉴定价值人民币184580元。后被公安机关抓获归案,已退赔全部赃款并取得谅解。当庭认罪认罚。", "gold": {"amount": 184580, "prior_record": true, "surrender": false, "restitution": true, "confession": true, "burglary": false, "carry_weapon": false, "gang": true}, "label_months": 49}
{"id": "assault_20", "charge": "故意伤害罪", "fact": "被告人魏某,男。曾因寻衅滋事被判刑,系累犯。经审理查明:被告人魏某因琐事发生口角后,持持械(砍刀)殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": true, "surrender": false, "restitution": true, "confession": true, "injury_level": "轻微伤", "armed": true, "premeditated": false, "gang": false}, "label_months": 7}
{"id": "assault_17", "charge": "故意伤害罪", "fact": "被告人杨某,男。平时表现尚可,无前科。经审理查明:被告人杨某因积怨已久、事先预谋,持赤手空拳殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,未赔偿被害人损失。当庭辩称系正当防卫。", "gold": {"prior_record": false, "surrender": false, "restitution": false, "confession": false, "injury_level": "轻微伤", "armed": false, "premeditated": true, "gang": false}, "label_months": 10}
{"id": "assault_13", "charge": "故意伤害罪", "fact": "被告人蒋某,男。平时表现尚可,无前科。经审理查明:被告人蒋某因琐事发生口角后,持持械(砍刀)殴打被害人,致其经鉴定为轻微伤。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭辩称系正当防卫。", "gold": {"prior_record": false, "surrender": false, "restitution": true, "confession": false, "injury_level": "轻微伤", "armed": true, "premeditated": false, "gang": false}, "label_months": 2}
{"id": "theft_05", "charge": "盗窃罪", "fact": "被告人施某,男。此前无违法犯罪记录。经审理查明:被告人施某单独在某商场内窃取他人财物,经鉴定价值人民币29220元。后被公安机关抓获归案,赃款已被挥霍,未退赔。当庭对指控予以否认。", "gold": {"amount": 29220, "prior_record": false, "surrender": false, "restitution": false, "confession": false, "burglary": false, "carry_weapon": false, "gang": false}, "label_months": 36}
{"id": "assault_08", "charge": "故意伤害罪", "fact": "被告人卫某,男。平时表现尚可,无前科。经审理查明:被告人卫某因积怨已久、事先预谋,持赤手空拳殴打被害人,致其经鉴定为重伤二级。作案后逃离现场,后被抓获,已赔偿被害人损失并取得谅解。当庭认罪认罚。", "gold": {"prior_record": false, "surrender": false, "restitution": true, "confession": true, "injury_level": "重伤", "armed": false, "premeditated": true, "gang": false}, "label_months": 36}
{"id": "fraud_06", "charge": "诈骗罪", "fact": "被告人严某,男。曾因诈骗被判刑,系累犯。经审理查明:被告人严某伙同他人组成团伙,在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人钱财,骗取财物共计人民币151930元。后被公安机关抓获,已退赔全部赃款。当庭否认诈骗故意。", "gold": {"amount": 151930, "prior_record": true, "surrender": false, "restitution": true, "confession": false, "scam_type": "合同", "victim_count": 4, "gang": true}, "label_months": 65}
{"id": "theft_09", "charge": "盗窃罪", "fact": "被告人周某,男。曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。经审理查明:被告人周某单独翻窗入户进入被害人位于某菜市场的住宅内,作案时随身携带匕首一把窃取他人财物,经鉴定价值人民币160660元。后被公安机关抓获归案,赃款已被挥霍,未退赔。当庭认罪认罚。", "gold": {"amount": 160660, "prior_record": true, "surrender": false, "restitution": false, "confession": true, "burglary": true, "carry_weapon": true, "gang": false}, "label_months": 65}
{"id": "theft_15", "charge": "盗窃罪", "fact": "被告人周某,男。此前无违法犯罪记录。经审理查明:被告人周某单独在某写字楼内窃取他人财物,经鉴定价值人民币371670元。后被公安机关抓获归案,赃款已被挥霍,未退赔。当庭认罪认罚。", "gold": {"amount": 371670, "prior_record": false, "surrender": false, "restitution": false, "confession": true, "burglary": false, "carry_weapon": false, "gang": false}, "label_months": 45}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,89 @@
"""
实验 3-12 全流程演示:从司法判例中提取隐性知识。
运行:
python demo.py
依次执行四个阶段:
阶段 1 自下而上因子发现:让 LLM 自由归纳因子,归并成模块化 schema(核心+各罪名扩展);
阶段 2 结构化抽取:用发现的 schema 从每条判例抽取因子(带缓存);
阶段 3 聚类 + 层次重要性:把因子向量聚成「案件原型」,算全局与原型内因子重要性;
阶段 4 对话式建议 Agent:把新案情匹配到最近原型,按重要性追问缺失因子,给出建议。
"""
import json
import os
import sys
import archetypes
import discovery
from advisor_agent import LegalAdvisorAgent
from extractor import extract_dataset, load_dataset
def section(title):
print("\n" + "=" * 74)
print(title)
print("=" * 74)
def main():
cases = load_dataset()
# ---------- 阶段 1:自下而上因子发现 ----------
section("阶段 1 / 自下而上因子发现(LLM 自由归纳 → 模块化 schema)")
schema = discovery.discover_schema(cases, batch_size=12, use_cache=True)
discovery.print_schema(schema)
# ---------- 阶段 2:结构化抽取 ----------
section("阶段 2 / 结构化抽取(用发现的 schema 抽取每条判例的因子)")
results = extract_dataset(schema, use_cache=True, verbose=True)
print("\n抽取样例(前 2 条):")
for r in results[:2]:
print(f"\n[{r['id']}] {r['fact'][:56]}...")
print(f" 抽取: {json.dumps(r['extracted'], ensure_ascii=False)}")
# ---------- 阶段 3:聚类 + 层次重要性 ----------
section("阶段 3 / 聚类成案件原型 + 层次因子重要性")
model = archetypes.fit(schema, results, save=True)
archetypes.print_model(model)
print(f"\n 模型已保存 -> {os.path.join('data', 'archetypes.json')}")
# ---------- 阶段 4:对话式量刑建议 Agent ----------
section("阶段 4 / 对话式量刑建议 Agent(匹配最近原型 + 按重要性追问)")
agent = LegalAdvisorAgent(schema, model)
user_turn1 = (
"我朋友之前因为盗窃被判过刑,这次他撬门进了别人家里偷东西,被抓的时候没反抗。"
"这种情况大概会判多久?"
)
print(f"\n用户: {user_turn1}")
known = agent.extract_known(user_turn1)
print(f"\nAgent 已识别因子: {json.dumps(known, ensure_ascii=False)}")
questions = agent.missing_important_questions(known)
print("\nAgent 追问(按全局因子重要性排序,只问缺失且重要的):")
for q in questions[:5]:
print(f" - [{q['name_cn']} 重要度{q['importance']:.3f}] {q['question']}")
user_turn2 = (
"补充一下:这次偷的东西价值大概 5 万元,事后他没有退赃,"
"作案时也没带凶器,是他一个人干的,到了法庭上他认罪认罚了。"
)
print(f"\n用户: {user_turn2}")
known2 = agent.extract_known(user_turn1 + " " + user_turn2)
print(f"\nAgent 更新后的因子: {json.dumps(known2, ensure_ascii=False)}")
arch, advice = agent.advise(known2)
print(f"\nAgent 匹配到 原型#{arch['id']}(典型刑期中位 {arch['months']['median']:.0f} 月)")
print("\nAgent 量刑建议:\n")
print(advice)
if __name__ == "__main__":
try:
main()
except RuntimeError as exc:
print(f"启动失败:{exc}", file=sys.stderr)
sys.exit(1)
@@ -0,0 +1,141 @@
"""
阶段 1:自下而上的因子发现(bottom-up factor discovery)。
不预先定义任何僵化的数据模式,而是:
1. 把判例文本分批喂给 LLM,让它**自由列出**每一批案例中所有可能影响判决的因素;
2. 汇总各批发现的原始因子,再用一次 LLM 调用做**归并与规范化**,产出一个
「模块化数据模式」:
- core —— 适用于所有罪名的通用因子(自首、赔偿、认罪、前科……);
- extensions —— 各罪名特有的扩展因子(盗窃→涉案金额/入户;伤害→伤害等级……)。
产出的 schema 落盘到 data/schema.json,供后续抽取 / 聚类 / 对话三段复用。
schema 里每个因子含:key(英文)、name_cn、kind(numeric/bool/categorical)、
values(categorical 取值)、direction(aggravating/mitigating/neutral)、question(引导性追问)。
"""
import json
import os
from config import MODEL, get_client
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
SCHEMA_PATH = os.path.join(DATA_DIR, "schema.json")
_BATCH_SYS = """你是协助司法数据分析的专家。下面给你若干条刑事判决书的「事实」段落。
请你**自由归纳**出其中所有可能影响法院量刑/判决的因素(不要局限于任何预设清单)。
对每个因素给出:
- key: 简短英文 snake_case 标识
- name_cn: 中文名
- charge: 该因素主要适用的罪名(若各类案件通用则填 "通用"
- kind: numeric(数值,如金额/人数)| bool(是非情节)| categorical(多取值,如伤害等级)
- values: 若 kind 为 categorical,列出观察到的取值数组;否则为空数组
只输出 JSON{"factors": [ {factor...}, ... ]}"""
_CONSOLIDATE_SYS = """你是司法数据建模专家。下面是从多批判例中分别发现的**原始因子清单**
(可能有重复、同义、命名不一致)。请把它们**归并、去重、规范化**成一个模块化数据模式:
- core: 适用于所有罪名的通用因子(如自首、赔偿谅解、认罪认罚、前科累犯)
- extensions: 一个对象,键为罪名(如 "盗窃罪"/"故意伤害罪"/"诈骗罪"),值为该罪名特有的因子数组
规范化要求:
- 合并同义因子(如"自首/主动投案""认罪认罚/认罪/如实供述""赔偿/退赔/退赃"
"累犯/前科"、同一罪名下的"涉案金额/物品价值/诈骗金额"只保留一个),
每组只保留一个最清晰的 key 与中文名;
- 剔除与量刑无实质关系的因素(如被告人性别、案发地点这类描述性信息);
- "是否否认指控/辩称正当防卫"这类与"认罪认罚"互为反面的,不要重复保留。
每个因子输出字段:
key, name_cn, kind(numeric|bool|categorical), values(categorical 的取值数组,否则[]),
direction(aggravating 从重 | mitigating 从轻 | neutral 中性),
question(当该因子缺失时,向当事人提出的一句中文引导性问题)
只输出 JSON{"core": [...], "extensions": {"罪名": [...], ...}}"""
def _chat_json(client, system, user):
resp = client.chat.completions.create(
model=MODEL,
temperature=0,
response_format={"type": "json_object"},
messages=[{"role": "system", "content": system},
{"role": "user", "content": user}],
)
try:
return json.loads(resp.choices[0].message.content)
except json.JSONDecodeError:
return {}
def discover_schema(cases, batch_size=12, use_cache=True, verbose=True):
"""自下而上发现因子并归并成模块化 schema。带磁盘缓存(避免重复花钱)。"""
if use_cache and os.path.exists(SCHEMA_PATH):
with open(SCHEMA_PATH, encoding="utf-8") as fh:
if verbose:
print(f" 命中缓存 schema -> {SCHEMA_PATH}")
return json.load(fh)
client = get_client()
# --- 第 1 步:分批自由发现 ---
raw_factors = []
for start in range(0, len(cases), batch_size):
batch = cases[start:start + batch_size]
facts = "\n\n".join(f"[案例{start + j + 1}]{c['charge']}{c['fact']}"
for j, c in enumerate(batch))
out = _chat_json(client, _BATCH_SYS, facts)
got = out.get("factors", [])
raw_factors.extend(got)
if verbose:
print(f" 批次 {start // batch_size + 1}:发现 {len(got)} 个候选因子")
# --- 第 2 步:归并 / 规范化成模块化 schema ---
if verbose:
print(f" 汇总 {len(raw_factors)} 个原始因子,做归并与规范化 ...")
schema = _chat_json(client, _CONSOLIDATE_SYS,
"原始因子清单:\n" + json.dumps(raw_factors, ensure_ascii=False))
schema.setdefault("core", [])
schema.setdefault("extensions", {})
os.makedirs(DATA_DIR, exist_ok=True)
with open(SCHEMA_PATH, "w", encoding="utf-8") as fh:
json.dump(schema, fh, ensure_ascii=False, indent=2)
if verbose:
print(f" 发现的模块化 schema 已保存 -> {SCHEMA_PATH}")
return schema
# --- schema 便捷访问 ---------------------------------------------------------
def load_schema():
with open(SCHEMA_PATH, encoding="utf-8") as fh:
return json.load(fh)
def factors_for_charge(schema, charge):
"""返回某罪名适用的因子列表:核心通用因子 + 该罪名扩展因子(按 key 去重)。"""
seen, out = set(), []
for f in schema.get("core", []) + schema.get("extensions", {}).get(charge, []):
if f["key"] in seen: # 去重:某因子同时落在 core 和扩展里时只保留一次
continue
seen.add(f["key"])
out.append(f)
return out
def all_factors(schema):
"""全部因子(core + 所有扩展),按 key 去重。"""
seen, out = set(), []
lists = [schema.get("core", [])] + list(schema.get("extensions", {}).values())
for lst in lists:
for f in lst:
if f["key"] in seen:
continue
seen.add(f["key"])
out.append(f)
return out
def print_schema(schema):
print(" 核心通用因子 (core):")
for f in schema.get("core", []):
vals = f"={f['values']}" if f.get("values") else ""
print(f" - {f['key']:<16} {f['name_cn']} [{f['kind']}{vals}] {f.get('direction','')}")
for charge, lst in schema.get("extensions", {}).items():
print(f" 扩展因子 · {charge}:")
for f in lst:
vals = f"={f['values']}" if f.get("values") else ""
print(f" - {f['key']:<16} {f['name_cn']} [{f['kind']}{vals}] {f.get('direction','')}")
@@ -0,0 +1,18 @@
# Provider: openai (default) or dashscope/qwen/bailian
LLM_PROVIDER=openai
# 复制为 .env 后填入你的 OpenAI API Key
OPENAI_API_KEY=your-openai-api-key
# Alibaba Cloud Model Studio / Bailian (Qwen)
# DASHSCOPE_API_KEY=your-dashscope-api-key
# DASHSCOPE_BASE_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1
# 可选:覆盖默认模型(默认 gpt-5.6-luna
# OPENAI_MODEL=gpt-5.6-luna
# OpenRouter 通用回退(可选):若未设置 OPENAI_API_KEY 但设置了 OPENROUTER_API_KEY
# 则聊天 LLM 会自动路由到 OpenRoutergpt-5.6-luna -> openai/gpt-5.6-luna
# 可用 OPENROUTER_MODEL 覆盖)。
OPENROUTER_API_KEY=your_openrouter_api_key_here
# OPENROUTER_MODEL=openai/gpt-5.6-luna
@@ -0,0 +1,144 @@
"""
阶段 2:结构化抽取 —— 用发现出来的 schema 从判例文本抽取结构化因子。
流程:
1. 先判定案件罪名(从 schema 已知的罪名里选);
2. 按「核心通用因子 + 该罪名扩展因子」逐项抽取,输出结构化 JSON;
3. 文本未提及的因子返回 null(供对话 Agent 判断"还缺什么信息");
4. 带磁盘缓存(data/extracted.jsonl),一次性抽取后重跑几乎免费。
输出统一为 {"charge": <罪名>, <factor_key>: <值|null>, ...}。
"""
import json
import os
from config import MODEL, get_client
from discovery import factors_for_charge, load_schema
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
CACHE_PATH = os.path.join(DATA_DIR, "extracted.jsonl")
def _factor_lines(factors):
lines = []
for f in factors:
if f["kind"] == "numeric":
t = "数值(整数,去掉单位)"
elif f["kind"] == "bool":
t = "true/false"
else:
t = "取值之一:" + "/".join(f.get("values", [])) if f.get("values") else "分类取值"
lines.append(f' - "{f["key"]}": {t} # {f["name_cn"]}')
return "\n".join(lines)
def _charges(schema):
return list(schema.get("extensions", {}).keys())
def extract_one(fact_text, schema=None, client=None, charge=None):
"""从单条判例文本抽取 {charge, factors...}。缺失因子取 null。
charge 已知时(数据集抽取)直接沿用,省一次调用;未知时(对话新案情)先让 LLM 判定。
"""
schema = schema or load_schema()
client = client or get_client()
charges = _charges(schema)
# 第 1 步:判定罪名(仅在未提供时调用 LLM)
if charge is None:
charge_resp = client.chat.completions.create(
model=MODEL, temperature=0,
response_format={"type": "json_object"},
messages=[
{"role": "system", "content":
"判断下述刑事案件属于哪个罪名,只能从这些里选:"
+ "/".join(charges) + '。只输出 JSON{"charge": "..."}。'},
{"role": "user", "content": fact_text},
],
)
charge = json.loads(charge_resp.choices[0].message.content).get("charge")
if charge not in charges: # 兜底:默认第一个罪名
charge = charges[0]
# 第 2 步:按该罪名适用的因子抽取
factors = factors_for_charge(schema, charge)
sys = (
"你是协助司法数据分析的信息抽取助手。请从判决书「事实」段落中抽取以下因子,"
"只输出一个 JSON 对象:\n" + _factor_lines(factors) + "\n\n规则:\n"
"1. 数值因子输出整数(去掉'''人民币'''等字样)。\n"
"2. 是非因子:文本明确支持则 true,明确否定则 false。\n"
"3. 分类因子只能取给定取值之一。\n"
"4. 文本完全没有相关信息的因子取 null(不要臆测)。\n"
"5. 只输出 JSON,不要解释。"
)
resp = client.chat.completions.create(
model=MODEL, temperature=0,
response_format={"type": "json_object"},
messages=[{"role": "system", "content": sys},
{"role": "user", "content": f"判决书事实段落:\n{fact_text}"}],
)
raw = json.loads(resp.choices[0].message.content)
return _normalize(raw, charge, factors)
def _normalize(raw, charge, factors):
out = {"charge": charge}
for f in factors:
v = raw.get(f["key"])
if v is None or v == "":
out[f["key"]] = None
elif f["kind"] == "numeric":
if isinstance(v, str):
digits = "".join(ch for ch in v if ch.isdigit())
out[f["key"]] = int(digits) if digits else None
else:
try:
out[f["key"]] = int(v)
except (TypeError, ValueError):
out[f["key"]] = None
elif f["kind"] == "bool":
out[f["key"]] = bool(v) if isinstance(v, bool) else str(v).lower() in ("true", "1", "")
else: # categorical
out[f["key"]] = str(v)
return out
def load_dataset():
path = os.path.join(DATA_DIR, "cases.jsonl")
with open(path, encoding="utf-8") as fh:
return [json.loads(line) for line in fh if line.strip()]
def extract_dataset(schema, use_cache=True, verbose=True):
"""对整个数据集抽取,带缓存。返回 list,每项含原案例字段 + `extracted`。"""
cases = load_dataset()
cache = {}
if use_cache and os.path.exists(CACHE_PATH):
with open(CACHE_PATH, encoding="utf-8") as fh:
for line in fh:
if line.strip():
rec = json.loads(line)
cache[rec["id"]] = rec["extracted"]
client = get_client()
results, n_called = [], 0
for c in cases:
if c["id"] in cache:
extracted = cache[c["id"]]
else:
extracted = extract_one(c["fact"], schema=schema, client=client,
charge=c.get("charge"))
cache[c["id"]] = extracted
n_called += 1
if verbose:
print(f" 抽取 {c['id']} ({extracted.get('charge')}) ... 完成")
results.append({**c, "extracted": extracted})
with open(CACHE_PATH, "w", encoding="utf-8") as fh:
for r in results:
fh.write(json.dumps({"id": r["id"], "extracted": r["extracted"]},
ensure_ascii=False) + "\n")
if verbose:
print(f" 本次实际调用 LLM {n_called} 次,其余命中缓存。")
return results
@@ -0,0 +1,175 @@
"""
合成一个小样本、多罪名的刑事判例数据集。
CAIL2018(真实目标数据集)体量太大(数百万条),不便随仓库分发;本实验自带一个
可离线运行的小样本,覆盖三类罪名:盗窃罪、故意伤害罪、诈骗罪。
每条案例包含:
- `charge` 罪名(生成时已知,仅作参考;抽取阶段会由 LLM 自行判定);
- `fact` 一段自然语言判决书事实描述;
- `gold` 生成时使用的因子真值(仅供人工核对,抽取不依赖它);
- `label_months` 刑期(月),由一个「已知」的量刑公式加噪声生成。
关键点:**因子在生成时被"写进"案情文本,发现阶段再从文本里把它们""回来**。
生成用的字段名(英文 key)只服务于本文件,下游的因子发现完全不依赖它——发现阶段
让 LLM 自由归纳因子,因此学到的模式来自数据本身,而非这里的先验字段列表。
真实迁移:把本文件替换为读取 CAIL2018 的 `data_*.json`(每行含 `fact` 与
`meta.term_of_imprisonment` 与 `meta.accusation`),产出同样结构的 `cases.jsonl` 即可。
"""
import json
import math
import os
import random
random.seed(42)
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
OUT_PATH = os.path.join(DATA_DIR, "cases.jsonl")
NAMES = list("赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹")
LOCATIONS = ["某小区", "某商场", "某写字楼", "某菜市场", "某手机专卖店", "某电动车棚", "某网吧"]
# ---------------------------------------------------------------------------
# 盗窃罪
# ---------------------------------------------------------------------------
def gen_theft(i: int) -> dict:
amount = int(round(random.uniform(1500, 400000), -1))
f = {
"prior_record": random.random() < 0.5,
"surrender": random.random() < 0.45,
"restitution": random.random() < 0.5,
"confession": random.random() < 0.6,
"burglary": random.random() < 0.45,
"carry_weapon": random.random() < 0.25,
"gang": random.random() < 0.4,
}
m = -18 + 5.2 * math.log(amount)
m += f["prior_record"] * 11 + f["burglary"] * 7 + f["carry_weapon"] * 5 + f["gang"] * 3
m += -f["surrender"] * 9 - f["restitution"] * 6 - f["confession"] * 3
m += random.gauss(0, 1.2)
months = int(max(1, min(180, round(m))))
name = "被告人" + random.choice(NAMES) + ""
prior = "曾因盗窃罪被判刑,刑满释放后再次作案,系累犯。" if f["prior_record"] else "此前无违法犯罪记录。"
scene = f"翻窗入户进入被害人位于{random.choice(LOCATIONS)}的住宅内" if f["burglary"] else f"{random.choice(LOCATIONS)}"
weapon = ",作案时随身携带匕首一把" if f["carry_weapon"] else ""
gang = "伙同他人结伙" if f["gang"] else "单独"
surrender = "案发后主动到公安机关投案自首," if f["surrender"] else "后被公安机关抓获归案,"
restitution = "已退赔全部赃款并取得谅解。" if f["restitution"] else "赃款已被挥霍,未退赔。"
confession = "当庭认罪认罚。" if f["confession"] else "当庭对指控予以否认。"
fact = (
f"{name},男。{prior}经审理查明:{name}{gang}{scene}{weapon}窃取他人财物,"
f"经鉴定价值人民币{amount}元。{surrender}{restitution}{confession}"
)
return {"id": f"theft_{i:02d}", "charge": "盗窃罪", "fact": fact,
"gold": {"amount": amount, **f}, "label_months": months}
# ---------------------------------------------------------------------------
# 故意伤害罪
# ---------------------------------------------------------------------------
_INJURY_BASE = {"轻微伤": 2.0, "轻伤": 12.0, "重伤": 40.0}
def gen_assault(i: int) -> dict:
injury = random.choice(["轻微伤", "轻微伤", "轻伤", "轻伤", "重伤"])
f = {
"prior_record": random.random() < 0.35,
"surrender": random.random() < 0.4,
"restitution": random.random() < 0.55, # 赔偿谅解在伤害案中权重很大
"confession": random.random() < 0.6,
"injury_level": injury,
"armed": random.random() < 0.45,
"premeditated": random.random() < 0.3,
"gang": random.random() < 0.35,
}
m = _INJURY_BASE[injury]
m += f["prior_record"] * 8 + f["armed"] * 10 + f["premeditated"] * 8 + f["gang"] * 4
m += -f["surrender"] * 6 - f["restitution"] * 10 - f["confession"] * 3
m += random.gauss(0, 1.0)
months = int(max(1, min(180, round(m))))
name = "被告人" + random.choice(NAMES) + ""
prior = "曾因寻衅滋事被判刑,系累犯。" if f["prior_record"] else "平时表现尚可,无前科。"
plan = "因积怨已久、事先预谋," if f["premeditated"] else "因琐事发生口角后,"
gang = "纠集多人" if f["gang"] else ""
weapon = ("持械(砍刀)" if f["armed"] else "赤手空拳") if not f["gang"] else ("并持械" if f["armed"] else "")
injury_desc = {"轻微伤": "经鉴定为轻微伤", "轻伤": "经鉴定为轻伤二级", "重伤": "经鉴定为重伤二级"}[injury]
surrender = "案发后主动投案自首," if f["surrender"] else "作案后逃离现场,后被抓获,"
restitution = "已赔偿被害人损失并取得谅解。" if f["restitution"] else "未赔偿被害人损失。"
confession = "当庭认罪认罚。" if f["confession"] else "当庭辩称系正当防卫。"
fact = (
f"{name},男。{prior}经审理查明:{name}{plan}{gang}{weapon}殴打被害人,"
f"致其{injury_desc}{surrender}{restitution}{confession}"
)
return {"id": f"assault_{i:02d}", "charge": "故意伤害罪", "fact": fact,
"gold": f, "label_months": months}
# ---------------------------------------------------------------------------
# 诈骗罪
# ---------------------------------------------------------------------------
def gen_fraud(i: int) -> dict:
amount = int(round(random.uniform(8000, 800000), -1))
scam = random.choice(["电信网络", "电信网络", "合同", "普通"])
victims = random.randint(1, 40) if scam == "电信网络" else random.randint(1, 5)
f = {
"prior_record": random.random() < 0.35,
"surrender": random.random() < 0.4,
"restitution": random.random() < 0.45,
"confession": random.random() < 0.6,
"scam_type": scam,
"victim_count": victims,
"gang": random.random() < 0.5,
}
m = -22 + 6.0 * math.log(amount)
m += f["prior_record"] * 10 + f["gang"] * 4
m += {"电信网络": 8.0, "合同": 3.0, "普通": 0.0}[scam]
m += math.log(victims + 1) * 3.0
m += -f["surrender"] * 8 - f["restitution"] * 7 - f["confession"] * 3
m += random.gauss(0, 1.2)
months = int(max(1, min(180, round(m))))
name = "被告人" + random.choice(NAMES) + ""
prior = "曾因诈骗被判刑,系累犯。" if f["prior_record"] else "此前无犯罪记录。"
method = {
"电信网络": f"通过拨打电话、发送短信等电信网络手段,虚构投资项目骗取{victims}名被害人",
"合同": "在签订、履行合同过程中,以虚假身份和虚构履约能力骗取被害人",
"普通": "以帮忙办事为由,虚构事实骗取被害人",
}[scam]
gang = "伙同他人组成团伙," if f["gang"] else ""
surrender = "案发后主动投案自首," if f["surrender"] else "后被公安机关抓获,"
restitution = "已退赔全部赃款。" if f["restitution"] else "赃款未追回。"
confession = "当庭认罪认罚。" if f["confession"] else "当庭否认诈骗故意。"
fact = (
f"{name},男。{prior}经审理查明:{name}{gang}{method}钱财,"
f"骗取财物共计人民币{amount}元。{surrender}{restitution}{confession}"
)
return {"id": f"fraud_{i:02d}", "charge": "诈骗罪", "fact": fact,
"gold": {"amount": amount, **f}, "label_months": months}
def main():
os.makedirs(DATA_DIR, exist_ok=True)
cases = []
for i in range(1, 25): # 24 盗窃
cases.append(gen_theft(i))
for i in range(1, 23): # 22 故意伤害
cases.append(gen_assault(i))
for i in range(1, 21): # 20 诈骗 -> 共 66 条
cases.append(gen_fraud(i))
random.shuffle(cases)
with open(OUT_PATH, "w", encoding="utf-8") as fh:
for c in cases:
fh.write(json.dumps(c, ensure_ascii=False) + "\n")
months = [c["label_months"] for c in cases]
print(f"已生成 {len(cases)} 条案例(盗窃/故意伤害/诈骗)-> {OUT_PATH}")
print(f"刑期范围: {min(months)}~{max(months)} 个月,均值 {sum(months)/len(months):.1f}")
if __name__ == "__main__":
main()
@@ -0,0 +1,4 @@
openai>=1.50.0
python-dotenv>=1.0.0
scikit-learn>=1.3.0
numpy>=1.24.0
@@ -0,0 +1,35 @@
"""advise() must not TypeError-unpack when the model has zero archetypes."""
import sys
import types
import pytest
# Stub openai/config client so LegalAdvisorAgent can be constructed offline.
import config as cfg
cfg.get_client = lambda: object()
from advisor_agent import LegalAdvisorAgent # noqa: E402
from archetypes import fit, nearest_archetype # noqa: E402
def _small_model():
schema = {"core_factors": [], "extensions": {"盗窃罪": [], "诈骗罪": []}}
results = [
{"extracted": {"charge": "盗窃罪"}, "label_months": 12},
{"extracted": {"charge": "诈骗罪"}, "label_months": 24},
]
return schema, fit(schema, results, save=False, verbose=False)
def test_nearest_returns_none_when_no_archetypes():
_, model = _small_model()
assert model["n_archetypes"] == 0
assert nearest_archetype(model, {"charge": "盗窃罪"}) is None
def test_advise_raises_clear_valueerror_not_typeerror():
schema, model = _small_model()
agent = LegalAdvisorAgent(schema, model)
with pytest.raises(ValueError, match="没有可用案件原型"):
agent.advise({"charge": "盗窃罪"})
@@ -0,0 +1,34 @@
"""Regression: fit must not unpack None when a charge has fewer than 3 samples."""
import sys
import types
import numpy as np
def _stub():
for name in ["sklearn", "sklearn.preprocessing", "sklearn.cluster", "sklearn.metrics"]:
sys.modules.setdefault(name, types.ModuleType(name))
# Minimal stubs so import can proceed if needed — prefer testing skip logic inline.
def test_best_none_skip_logic():
best = None
k_range = range(2, 5)
idx_len = 1
for k in k_range:
if k >= idx_len:
break
assert best is None
# fixed path: continue instead of unpack
if best is None:
skipped = True
else:
skipped = False
assert skipped is True
def test_source_guards_none_best():
from pathlib import Path
src = Path(__file__).with_name("archetypes.py").read_text()
assert "if best is None:" in src
assert "continue" in src.split("if best is None:")[1][:120]
@@ -0,0 +1,18 @@
from campaign import missing_extraction_rows
def test_missing_extraction_rows_finds_omissions_across_batches():
rows = [{"id": "case-a"}, {"id": "case-b"}, {"id": "case-c"}]
outputs = [
{"cases": [{"id": "case-a"}]},
{"cases": [{"id": "case-c"}]},
]
assert missing_extraction_rows(rows, outputs) == [{"id": "case-b"}]
def test_missing_extraction_rows_ignores_null_ids_and_empty_outputs():
rows = [{"id": "case-a"}]
outputs = [{"cases": [{"id": None}]}, {}, {"cases": None}]
assert missing_extraction_rows(rows, outputs) == rows
@@ -0,0 +1,35 @@
"""Regression tests: _normalize must coerce non-scalar LLM-provided JSON values
(e.g. lists/dicts for a numeric factor) to None instead of raising TypeError."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from extractor import _normalize
FACTORS = [{"key": "amount", "kind": "numeric", "values": []}]
def test_numeric_list_value_becomes_none():
out = _normalize({"amount": ["约10万元"]}, "盗窃罪", FACTORS)
assert out == {"charge": "盗窃罪", "amount": None}
def test_numeric_dict_value_becomes_none():
out = _normalize({"amount": {"value": 5}}, "盗窃罪", FACTORS)
assert out["amount"] is None
def test_numeric_string_extracts_digits():
out = _normalize({"amount": "约105000元"}, "盗窃罪", FACTORS)
assert out["amount"] == 105000
def test_numeric_plain_int_unchanged():
out = _normalize({"amount": 5000}, "盗窃罪", FACTORS)
assert out["amount"] == 5000
def test_numeric_null_stays_none():
out = _normalize({"amount": None}, "盗窃罪", FACTORS)
assert out["amount"] is None
@@ -0,0 +1,89 @@
{
"signature": "ab46f33e21eeef48d206a3320402cdb343db0f540b7d7b52118c86db208b07ca",
"parsed": {
"advice": "匹配依据:待预测案件与训练集原型“盗窃罪-prototype-1”共享部分定义特征,包括“theft_target=现金”“theft_location=公共场所”。但存在多处特征不匹配,如待预测案件中“theft_tool”“disposal_of_stolen_property”“stolen_property_disposal”“stolen_property_recovery”“return_of_stolen_property”等关键特征信息缺失或与原型定义特征(如“theft_tool=筷子”“disposal_of_stolen_property=全部发还”等)不一致,匹配距离为0.535。统计区间:该原型案件量为1,刑期统计区间为4.0个月(min=4.0max=4.0q25=4.0q75=4.0median=4.0)。不确定性:由于待预测案件与原型存在多个定义特征差异,且原型样本量较小(n=1),统计结果可能存在局限性,刑期预测存在不确定性。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-08f56c05f4baae51",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:41:43.942009+00:00",
"latency_ms": 23748.511,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"盗窃罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": false, \"criminal_record\": null, \"arrest_method\": \"被抓获\", \"return_of_stolen_property\": null, \"crime_count\": 1.0, \"crime_amount\": 2075.0, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": null, \"theft_count\": 1.0, \"theft_amount\": 2075.0, \"household_theft\": false, \"stolen_property_recovery\": null, \"destructive_means\": false, \"use_special_convenience\": \"工作便利\", \"theft_method\": \"爬窗\", \"theft_target\": \"现金\", \"theft_tool\": null, \"theft_location\": \"公共场所\", \"stolen_property_disposal\": null, \"drug_user_identity\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"盗窃罪-prototype-1\", \"charge\": \"盗窃罪\", \"prototype_size\": 1, \"sentence_months\": {\"median\": 4.0, \"q25\": 4.0, \"q75\": 4.0, \"min\": 4.0, \"max\": 4.0}, \"defining_features\": [{\"feature\": \"cat:theft_tool=筷子\", \"z\": 18.947295321496338}, {\"feature\": \"cat:disposal_of_stolen_property=全部发还\", \"z\": 6.633249580710821}, {\"feature\": \"cat:stolen_property_disposal=返还被害人\", \"z\": 5.916079783099585}, {\"feature\": \"cat:theft_target=现金\", \"z\": 3.6606010435446397}, {\"feature\": \"cat:stolen_property_recovery=全部追回\", \"z\": 3.584153156068214}, {\"feature\": \"cat:return_of_stolen_property=全部退赃\", \"z\": 3.2015621187164305}, {\"feature\": \"cat:theft_location=公共场所\", \"z\": 3.0964875737063133}, {\"feature\": \"cat:theft_method=趁人不备\", \"z\": 2.789352578086535}], \"match_distance\": 0.5352754821097585}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "021785386504177cf2d23e4a199b9c4bf1f6d914b47619df647d1",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待预测案件与训练集原型“盗窃罪-prototype-1”共享部分定义特征,包括“theft_target=现金”“theft_location=公共场所”。但存在多处特征不匹配,如待预测案件中“theft_tool”“disposal_of_stolen_property”“stolen_property_disposal”“stolen_property_recovery”“return_of_stolen_property”等关键特征信息缺失或与原型定义特征(如“theft_tool=筷子”“disposal_of_stolen_property=全部发还”等)不一致,匹配距离为0.535。统计区间:该原型案件量为1,刑期统计区间为4.0个月(min=4.0max=4.0q25=4.0q75=4.0median=4.0)。不确定性:由于待预测案件与原型存在多个定义特征差异,且原型样本量较小(n=1),统计结果可能存在局限性,刑期预测存在不确定性。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户提供的司法数据分析请求。首先,我要仔细阅读用户给出的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE ONLY的内容,然后根据要求生成JSON格式的advice。\n\n首先,用户的问题是关于盗窃罪的刑期预测,只能使用提供的训练集案件原型统计与已抽取因素,不能使用外部知识或自行给出刑期数字。需要解释匹配依据和统计区间,强调不确定性。\n\n先看HELDOUT EXTRACTED FACTORS中的因素:charge是盗窃罪,theft_target是现金,theft_location是公共场所,其他因素如theft_tool、disposal_of_stolen_property、stolen_property_disposal、stolen_property_recovery、return_of_stolen_property等都是null或未提及。\n\n然后看MATCHED TRAINING PROTOTYPEprototype_id是盗窃罪-prototype-1prototype_size是1sentence_months的统计区间是median=4.0q25=4.0q75=4.0min=4.0max=4.0。defining_features里有多个特征,其中与HELDOUT因素匹配的可能是theft_target=现金和theft_location=公共场所,这两个在defining_features中z值分别为3.66和3.09,属于较高的z值,说明这两个特征在原型中是重要的定义特征。不过HELDOUT中的theft_tool是null,而原型中的theft_tool是筷子,这是不匹配的;disposal_of_stolen_property等在HELDOUT中是null,而原型中是全部发还等,也不匹配。match_distance是0.535,说明有一定匹配度但不完全匹配。\n\n接下来,匹配依据需要说明HELDOUT与原型共享的特征,即theft_target=现金和theft_location=公共场所,这两个是原型的defining features。但同时要指出存在不匹配的特征,如theft_toolHELDOUT中为null,原型中为筷子)、disposal_of_stolen_property等(HELDOUT中未提及,原型中有具体值),这些差异可能影响匹配准确性。\n\n统计区间方面,原型的sentence_months的min、max、q25、q75、median都是4.0,所以统计区间是4.0个月(min=4.0max=4.0q25=4.0q75=4.0median=4.0)。\n\n需要强调不确定性,因为HELDOUT中存在多个原型定义特征未匹配(如theft_tool、disposal_of_stolen_property等),且原型样本量仅为1prototype_size=1),统计结果可能存在局限性。\n\n现在组织语言,确保包含匹配依据(共享的theft_target=现金和theft_location=公共场所,以及不匹配的特征)、统计区间(4.0个月的各分位数),并强调不确定性(样本量小、特征不匹配)。\n\n最后,按照要求返回JSON格式,键为\"advice\",值为上述内容的自然语言描述,不使用Markdown,保持简洁准确。"
}
}
],
"created": 1785386527,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 926,
"prompt_tokens": 827,
"total_tokens": 1753,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 704,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 926,
"prompt_tokens": 827,
"total_tokens": 1753,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 704,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "021785386504177cf2d23e4a199b9c4bf1f6d914b47619df647d1"
}
}
@@ -0,0 +1,89 @@
{
"signature": "5fa9744945f5a886ac9e551ae3a91a7045bad264708edf611621847aebe81a13",
"parsed": {
"advice": "匹配依据为案件的盗窃方法(趁人不备)、盗窃目标(手机)、利用特殊便利情况(无)、盗窃金额及盗窃次数等关键特征与训练原型“盗窃罪-prototype-4”的核心定义特征高度匹配(匹配距离0.1839)。该原型包含53个案件,统计刑期区间为6个月(Q25)至9个月(Q75),中位数为7个月。需注意,案件中存在的坦白情节(truthful_confession=true)未被该原型的定义特征显著纳入,且盗窃地点(网吧)与原型部分特征(户外)存在差异,可能对刑期评估产生不确定性。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-0b5790a5aacdfcab",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:41:43.938034+00:00",
"latency_ms": 26279.862,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"盗窃罪\", \"surrender\": null, \"truthful_confession\": true, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": false, \"criminal_record\": null, \"arrest_method\": null, \"return_of_stolen_property\": null, \"crime_count\": 1.0, \"crime_amount\": 7928.0, \"guilty_plea\": true, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": 2.0, \"theft_count\": 1.0, \"theft_amount\": 7928.0, \"household_theft\": false, \"stolen_property_recovery\": null, \"destructive_means\": false, \"use_special_convenience\": \"无\", \"theft_method\": \"趁人不备\", \"theft_target\": \"手机\", \"theft_tool\": null, \"theft_location\": \"网吧\", \"stolen_property_disposal\": null, \"drug_user_identity\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"盗窃罪-prototype-4\", \"charge\": \"盗窃罪\", \"prototype_size\": 53, \"sentence_months\": {\"median\": 7.0, \"q25\": 6.0, \"q75\": 9.0, \"min\": 2.0, \"max\": 72.0}, \"defining_features\": [{\"feature\": \"cat:theft_method=趁人不备\", \"z\": 1.6014813672139347}, {\"feature\": \"cat:theft_target=手机\", \"z\": 1.4372243817878076}, {\"feature\": \"cat:use_special_convenience=无\", \"z\": 1.433980681035021}, {\"feature\": \"num:theft_amount\", \"z\": 1.4051072801892825}, {\"feature\": \"num:theft_count\", \"z\": 1.186314536087199}, {\"feature\": \"cat:theft_tool=徒手\", \"z\": 1.1302013658008716}, {\"feature\": \"cat:theft_location=户外\", \"z\": 1.1116513579865053}, {\"feature\": \"cat:stolen_property_disposal=变卖挥霍\", \"z\": 1.0391716890512863}], \"match_distance\": 0.1838970299217689}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "0217853865041783b2b0407ffd677ecb32e5e07988358b983df9e",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据为案件的盗窃方法(趁人不备)、盗窃目标(手机)、利用特殊便利情况(无)、盗窃金额及盗窃次数等关键特征与训练原型“盗窃罪-prototype-4”的核心定义特征高度匹配(匹配距离0.1839)。该原型包含53个案件,统计刑期区间为6个月(Q25)至9个月(Q75),中位数为7个月。需注意,案件中存在的坦白情节(truthful_confession=true)未被该原型的定义特征显著纳入,且盗窃地点(网吧)与原型部分特征(户外)存在差异,可能对刑期评估产生不确定性。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,作为司法数据分析助手,根据提供的训练集案件原型统计和抽取的因素来生成建议。首先,我要仔细查看用户提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE ONLY的内容。\n\n首先,HELDOUT EXTRACTED FACTORS中的罪名是盗窃罪,关键因素包括盗窃方法是“趁人不备”,盗窃目标是“手机”,利用特殊便利是“无”,盗窃金额7928元,盗窃次数1次,犯罪形态是既遂,盗窃地点是网吧,其他如自首、坦白(这里truthful_confession是true,但需要看原型是否包含这个因素)、赔偿、谅解等都是null或false。\n\n然后看MATCHED TRAINING PROTOTYPE,匹配到的原型是“盗窃罪-prototype-4”,原型大小53个案件,刑期的中位数是7个月,四分位25%是6个月,75%是9个月,最小值2个月,最大值72个月。定义特征中,有几个关键的分类特征:盗窃方法=趁人不备(z值较高)、盗窃目标=手机、利用特殊便利=无、盗窃金额(数值型)、盗窃次数(数值型),还有盗窃工具=徒手(但用户的HELDOUT中theft_tool是null,可能这里有差异)、盗窃地点=户外(但用户的盗窃地点是网吧,这里可能有不匹配,但原型的定义特征中盗窃地点是户外,z值1.11,而用户的是网吧,可能这是一个差异点,但匹配距离0.1839,属于较近的匹配),以及赃物处置=变卖挥霍(用户的stolen_property_disposal是null,可能原型中这个特征有较高z值,但用户数据中没有这个因素)。\n\n接下来,需要确定匹配依据。原型的定义特征中,用户的HELDOUT因素里匹配了盗窃方法=趁人不备、盗窃目标=手机、利用特殊便利=无、盗窃金额(数值型)、盗窃次数(数值型),这些都是原型中z值较高的关键特征,说明匹配度较高,匹配距离0.1839也较小,表明相似性较好。\n\n然后,刑期统计区间方面,原型的刑期中位数7个月,q25=6,q75=9,所以统计区间应该是6-9个月,中位数7个月。需要注意的是,HELDOUT中的truthful_confession是true(坦白),但原型的定义特征中没有提到坦白这一因素,可能原型中该因素不显著或未包含,所以可能对刑期影响未在统计中体现,存在不确定性。另外,盗窃地点用户是网吧,原型是户外,这可能是一个差异点,也可能影响匹配的准确性,带来不确定性。\n\n需要强调匹配依据是关键特征如盗窃方法、目标、利用便利、金额、次数等与原型高度匹配,统计区间基于原型的四分位和中位数,同时指出不确定性,比如存在未被原型显著包含的因素(如坦白)以及盗窃地点的差异可能带来的影响。\n\n最后,按照要求,只返回JSON,格式是{\"advice\":\"...\"},内容要解释匹配依据、统计区间,强调不确定性,不使用外部知识,不给出其他刑期数字。\n\n现在组织语言:匹配依据为案件的盗窃方法(趁人不备)、盗窃目标(手机)、利用特殊便利情况(无)、盗窃金额及盗窃次数等关键特征与训练原型“盗窃罪-prototype-4”的核心定义特征高度匹配(匹配距离0.1839)。该原型包含53个案件,统计刑期区间为6个月(Q25)至9个月(Q75),中位数为7个月。需注意,案件中存在的坦白情节(truthful_confession=true)未被该原型的定义特征显著纳入,且盗窃地点(网吧)与原型部分特征(户外)存在差异,可能对刑期评估产生不确定性。\n\n检查是否符合要求:只使用提供的原型和因素,解释了匹配依据和统计区间,强调了不确定性,没有使用外部知识,格式正确。"
}
}
],
"created": 1785386530,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 1055,
"prompt_tokens": 818,
"total_tokens": 1873,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 907,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 1055,
"prompt_tokens": 818,
"total_tokens": 1873,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 907,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "0217853865041783b2b0407ffd677ecb32e5e07988358b983df9e"
}
}
@@ -0,0 +1,89 @@
{
"signature": "0d4f760790459e801ca99ca3e929be1187358f9d4ee0c9d0acf8e3122aea0789",
"parsed": {
"advice": "匹配依据:待匹配案件与训练集原型“盗窃罪-prototype-1”共享“theft_location=公共场所”“theft_method=趁人不备”两个关键特征,这两个特征在原型定义特征中具有较高z值(分别为3.096、2.789),表明与原型关联性较强。统计区间:该原型样本量为1,刑期中位数、四分位距(q25、q75)、最小值及最大值均为4.0个月。不确定性:待匹配案件存在“joint_crime=true”“crime_count=2.0”“crime_amount=11304.0”等原型未包含的因素,且原型关键特征如“theft_tool=筷子”“disposal_of_stolen_property=全部发还”等在待匹配案件中缺失或未提及,同时原型样本量较小,可能导致刑期参考存在偏差。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-1db01ff755764d92",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:41:43.941253+00:00",
"latency_ms": 23873.088,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"盗窃罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": true, \"criminal_record\": null, \"arrest_method\": null, \"return_of_stolen_property\": null, \"crime_count\": 2.0, \"crime_amount\": 11304.0, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": 2.0, \"theft_count\": 2.0, \"theft_amount\": 11304.0, \"household_theft\": false, \"stolen_property_recovery\": null, \"destructive_means\": false, \"use_special_convenience\": \"无\", \"theft_method\": \"趁人不备\", \"theft_target\": null, \"theft_tool\": null, \"theft_location\": \"公共场所\", \"stolen_property_disposal\": null, \"drug_user_identity\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"盗窃罪-prototype-1\", \"charge\": \"盗窃罪\", \"prototype_size\": 1, \"sentence_months\": {\"median\": 4.0, \"q25\": 4.0, \"q75\": 4.0, \"min\": 4.0, \"max\": 4.0}, \"defining_features\": [{\"feature\": \"cat:theft_tool=筷子\", \"z\": 18.947295321496338}, {\"feature\": \"cat:disposal_of_stolen_property=全部发还\", \"z\": 6.633249580710821}, {\"feature\": \"cat:stolen_property_disposal=返还被害人\", \"z\": 5.916079783099585}, {\"feature\": \"cat:theft_target=现金\", \"z\": 3.6606010435446397}, {\"feature\": \"cat:stolen_property_recovery=全部追回\", \"z\": 3.584153156068214}, {\"feature\": \"cat:return_of_stolen_property=全部退赃\", \"z\": 3.2015621187164305}, {\"feature\": \"cat:theft_location=公共场所\", \"z\": 3.0964875737063133}, {\"feature\": \"cat:theft_method=趁人不备\", \"z\": 2.789352578086535}], \"match_distance\": 0.04937220605779228}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "021785386504173d2e580f6bb6385a445f3c475c512e9ae977333",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待匹配案件与训练集原型“盗窃罪-prototype-1”共享“theft_location=公共场所”“theft_method=趁人不备”两个关键特征,这两个特征在原型定义特征中具有较高z值(分别为3.096、2.789),表明与原型关联性较强。统计区间:该原型样本量为1,刑期中位数、四分位距(q25、q75)、最小值及最大值均为4.0个月。不确定性:待匹配案件存在“joint_crime=true”“crime_count=2.0”“crime_amount=11304.0”等原型未包含的因素,且原型关键特征如“theft_tool=筷子”“disposal_of_stolen_property=全部发还”等在待匹配案件中缺失或未提及,同时原型样本量较小,可能导致刑期参考存在偏差。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户提供的司法数据分析请求。首先,我要仔细阅读用户给出的待匹配案件的抽取因素(HELDOUT EXTRACTED FACTORS)和匹配到的训练集原型(MATCHED TRAINING PROTOTYPE ONLY)。\n\n首先,待匹配案件的罪名是“盗窃罪”,关键因素包括:theft_location=公共场所,theft_method=趁人不备,joint_crime=truecrime_count=2.0crime_amount=11304.0household_theft=falsedestructive_means=falseuse_special_convenience=无,其他如surrender、truthful_confession等均为null。\n\n然后看匹配到的原型,prototype_id是“盗窃罪-prototype-1”,原型大小为1,刑期统计是median=4.0q25=4.0q75=4.0min=4.0max=4.0,也就是只有一个案件,刑期固定为4个月。定义特征中,有几个与待匹配案件可能相关的:theft_location=公共场所(z值3.096),theft_method=趁人不备(z值2.789),这两个特征在待匹配案件中也存在(theft_location=公共场所,theft_method=趁人不备),所以这两个特征是匹配的。但原型的其他定义特征,如theft_tool=筷子、disposal_of_stolen_property=全部发还、stolen_property_disposal=返还被害人、theft_target=现金、stolen_property_recovery=全部追回、return_of_stolen_property=全部退赃等,在待匹配案件中要么是null(如theft_tool、disposal_of_stolen_property、stolen_property_disposal、stolen_property_recovery、return_of_stolen_property等),要么没有提到(theft_target)。\n\n接下来,根据用户要求,只能使用给出的训练集案件原型统计与已抽取因素,不能使用外部知识或原始案件。需要解释匹配依据和统计区间,强调不确定性。匹配依据主要是待匹配案件与原型共享的两个特征:theft_location=公共场所和theft_method=趁人不备,这两个特征在原型的定义特征中z值较高,说明是原型的重要特征。但原型的其他关键特征(如theft_tool=筷子、退赃、发还等)在待匹配案件中缺失或未提及,可能影响匹配的准确性。统计区间方面,原型的刑期中位数、四分位距、 min和max均为4.0,但原型大小仅为1,样本量小,统计结果的可靠性可能较低。不确定性体现在待匹配案件存在joint_crime=true等原型未包含的因素,且缺失原型的部分关键特征,可能导致刑期预测存在偏差。\n\n最后,按照要求返回JSON,格式为{\"advice\":\"...\"},内容包括匹配依据、统计区间、不确定性说明,不使用外部知识,不给出其他刑期数字,只基于原型的统计。"
}
}
],
"created": 1785386527,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 853,
"prompt_tokens": 830,
"total_tokens": 1683,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 654,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 853,
"prompt_tokens": 830,
"total_tokens": 1683,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 654,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "021785386504173d2e580f6bb6385a445f3c475c512e9ae977333"
}
}
@@ -0,0 +1,89 @@
{
"signature": "cbe9a3b6c15b79998935e79fefdbd53421f84180542fd08a4d08a8e9c11f1fa0",
"parsed": {
"advice": "匹配依据:待匹配案件的盗窃方法“趁人不备”、盗窃目标“手机”、使用特殊便利“无”、盗窃工具“徒手”、盗窃金额及盗窃次数等关键特征,与训练集原型“盗窃罪-prototype-4”的定义特征高度匹配。该原型样本量为53,刑期统计区间为:中位数7个月,四分位25%为6个月,四分位75%为9个月,最小值2个月,最大值72个月。具体刑期可能因案件未纳入统计的其他因素存在波动,具有不确定性。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-1fbafbfb755a338c",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:41:43.940524+00:00",
"latency_ms": 19800.814,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"盗窃罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": false, \"criminal_record\": null, \"arrest_method\": \"被抓获\", \"return_of_stolen_property\": \"全部退赃\", \"crime_count\": 1.0, \"crime_amount\": 1600.0, \"guilty_plea\": true, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": 1.0, \"theft_count\": 1.0, \"theft_amount\": 1600.0, \"household_theft\": false, \"stolen_property_recovery\": \"全部追回\", \"destructive_means\": false, \"use_special_convenience\": \"无\", \"theft_method\": \"趁人不备\", \"theft_target\": \"手机\", \"theft_tool\": \"徒手\", \"theft_location\": \"公共场所\", \"stolen_property_disposal\": null, \"drug_user_identity\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"盗窃罪-prototype-4\", \"charge\": \"盗窃罪\", \"prototype_size\": 53, \"sentence_months\": {\"median\": 7.0, \"q25\": 6.0, \"q75\": 9.0, \"min\": 2.0, \"max\": 72.0}, \"defining_features\": [{\"feature\": \"cat:theft_method=趁人不备\", \"z\": 1.6014813672139347}, {\"feature\": \"cat:theft_target=手机\", \"z\": 1.4372243817878076}, {\"feature\": \"cat:use_special_convenience=无\", \"z\": 1.433980681035021}, {\"feature\": \"num:theft_amount\", \"z\": 1.4051072801892825}, {\"feature\": \"num:theft_count\", \"z\": 1.186314536087199}, {\"feature\": \"cat:theft_tool=徒手\", \"z\": 1.1302013658008716}, {\"feature\": \"cat:theft_location=户外\", \"z\": 1.1116513579865053}, {\"feature\": \"cat:stolen_property_disposal=变卖挥霍\", \"z\": 1.0391716890512863}], \"match_distance\": 0.10723022373168277}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "021785386504178a3a08778c75214a0d2bb1c008b2d20ec70c495",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待匹配案件的盗窃方法“趁人不备”、盗窃目标“手机”、使用特殊便利“无”、盗窃工具“徒手”、盗窃金额及盗窃次数等关键特征,与训练集原型“盗窃罪-prototype-4”的定义特征高度匹配。该原型样本量为53,刑期统计区间为:中位数7个月,四分位25%为6个月,四分位75%为9个月,最小值2个月,最大值72个月。具体刑期可能因案件未纳入统计的其他因素存在波动,具有不确定性。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,根据提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE来生成JSON格式的建议。首先,我要仔细分析这两个部分的内容。\n\n首先看HELDOUT EXTRACTED FACTORS,里面的罪名是盗窃罪,具体因素包括盗窃方法是“趁人不备”,盗窃目标是“手机”,使用特殊便利是“无”,盗窃金额1600元,盗窃次数1次,盗窃工具“徒手”,盗窃地点“公共场所”,全部退赃,全部追回,犯罪形态既遂,认罪认罚等。\n\n然后是MATCHED TRAINING PROTOTYPE,原型ID是“盗窃罪-prototype-4”,样本量53,刑期的中位数是7个月,四分位25%是6个月,75%是9个月,最小值2个月,最大值72个月。定义特征里有几个关键的分类特征,比如盗窃方法=趁人不备,盗窃目标=手机,使用特殊便利=无,盗窃工具=徒手,还有数值特征盗窃金额和盗窃次数。不过这里有一个定义特征是“cat:theft_location=户外”,而待匹配案件的盗窃地点是“公共场所”,可能存在一定差异,但整体匹配距离0.107,比较低,说明匹配度较高。\n\n接下来,我需要根据这些信息生成建议。根据规则,只能使用训练集案件原型统计与已抽取因素,不能用外部知识或自行给刑期数字。要解释匹配依据和统计区间,强调不确定性。\n\n匹配依据应该是待匹配案件的关键特征与原型的定义特征高度匹配,比如盗窃方法趁人不备、目标手机、无特殊便利、徒手、盗窃金额和次数等,这些都是原型中z值较高的定义特征。统计区间就是原型的刑期统计,中位数7个月,q25=6q75=9min=2max=72。需要说明这些统计值,并指出刑期可能在该区间内,但存在不确定性,因为实际案件可能有未纳入的因素等。\n\n需要注意不要使用原始训练案件或外部法律知识,只基于给出的原型统计。还要强调不确定性,比如“刑期可能存在一定波动”之类的表述。\n\n现在组织语言,先说明匹配依据是关键特征匹配,然后列出统计区间(中位数、四分位、 min、max),最后强调不确定性。确保用中文,并且只返回JSON格式的advice字段。\n\n检查是否有遗漏:是否提到了匹配依据(关键特征如趁人不备、手机、无特殊便利、徒手、盗窃金额和次数等与原型定义特征匹配),统计区间(中位数7个月,q25 6个月,q75 9个月,最小值2个月,最大值72个月),强调不确定性(如“具体刑期可能因案件具体情况存在波动”)。\n\n确认无误后,生成JSON。"
}
}
],
"created": 1785386523,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 756,
"prompt_tokens": 827,
"total_tokens": 1583,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 631,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 756,
"prompt_tokens": 827,
"total_tokens": 1583,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 631,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "021785386504178a3a08778c75214a0d2bb1c008b2d20ec70c495"
}
}
@@ -0,0 +1,89 @@
{
"signature": "c3f22d92a68a59a14b7d6ed17887ecd05796bc2314023f9391ed711f3e4d8af8",
"parsed": {
"advice": "匹配训练集原型为“故意伤害罪-prototype-1”(样本量100),匹配依据为案件关键因素与原型定义特征高度重合,包括injury_result=轻伤、minor_injury_count=1.0、injury_degree=轻伤二级、criminal_motive=琐事等(对应原型中z值较高的特征)。该原型刑期统计区间:中位数8个月,四分位区间6-12个月(q25=6.0q75=12.0),范围3-36个月。需注意,案件中自首、全额赔偿、被害人谅解、认罪认罚等从轻情节未被纳入原型定义特征,可能导致实际刑期与统计区间存在差异,不确定性较高。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-0317ae92dc821b04",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:03.759235+00:00",
"latency_ms": 26429.064,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"故意伤害罪\", \"surrender\": true, \"truthful_confession\": null, \"compensation_to_victim\": \"全额赔偿\", \"victim_forgiveness\": true, \"joint_crime\": null, \"criminal_record\": null, \"arrest_method\": \"自首\", \"return_of_stolen_property\": null, \"crime_count\": null, \"crime_amount\": null, \"guilty_plea\": true, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": 1.0, \"injury_degree\": \"轻伤二级\", \"weapon_type\": null, \"criminal_cause\": \"债务纠纷\", \"community_correction_eligibility\": null, \"victim_fault\": null, \"compensation_status\": \"全额赔偿\", \"mutual_fight\": null, \"injury_result\": \"轻伤\", \"criminal_motive\": \"琐事\", \"drunk_crime\": null, \"minor_injury_count\": 1.0, \"serious_injury_count\": 0.0}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"故意伤害罪-prototype-1\", \"charge\": \"故意伤害罪\", \"prototype_size\": 100, \"sentence_months\": {\"median\": 8.0, \"q25\": 6.0, \"q75\": 12.0, \"min\": 3.0, \"max\": 36.0}, \"defining_features\": [{\"feature\": \"cat:injury_result=轻伤\", \"z\": 1.5568442135769593}, {\"feature\": \"num:minor_injury_count\", \"z\": 1.4868344442139674}, {\"feature\": \"num:crime_amount\", \"z\": -1.1597831108065333}, {\"feature\": \"cat:injury_degree=轻伤二级\", \"z\": 1.1511080895758883}, {\"feature\": \"cat:criminal_cause=琐事争执\", \"z\": 1.0373867906329766}, {\"feature\": \"bool:mutual_fight\", \"z\": 0.8262918864452846}, {\"feature\": \"cat:criminal_motive=琐事\", \"z\": 0.7564756876813216}, {\"feature\": \"num:crime_count\", \"z\": -0.7017513783529625}], \"match_distance\": 0.15484050438307592}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "02178538652398866d4fff57f472cc97f9dc7178d7aa526f9b569",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配训练集原型为“故意伤害罪-prototype-1”(样本量100),匹配依据为案件关键因素与原型定义特征高度重合,包括injury_result=轻伤、minor_injury_count=1.0、injury_degree=轻伤二级、criminal_motive=琐事等(对应原型中z值较高的特征)。该原型刑期统计区间:中位数8个月,四分位区间6-12个月(q25=6.0q75=12.0),范围3-36个月。需注意,案件中自首、全额赔偿、被害人谅解、认罪认罚等从轻情节未被纳入原型定义特征,可能导致实际刑期与统计区间存在差异,不确定性较高。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户提供的司法数据分析请求。首先,我要仔细阅读用户给出的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE ONLY的内容,然后根据要求生成JSON格式的建议。\n\n首先,用户的问题是关于故意伤害罪的案件,需要根据匹配的训练集原型来给出刑期建议。根据系统提示,我只能使用给出的训练集案件原型统计与已抽取因素,不能使用外部法律知识或自行给出刑期数字。需要解释匹配依据和统计区间,并强调不确定性。\n\n先看HELDOUT EXTRACTED FACTORS中的关键因素:charge是故意伤害罪,injury_result是轻伤,minor_injury_count是1.0injury_degree是轻伤二级,criminal_cause是债务纠纷,criminal_motive是琐事,mutual_fight是null,还有surrender=truetruthful_confession=nullcompensation_to_victim=全额赔偿,victim_forgiveness=trueguilty_plea=true等。\n\n然后看MATCHED TRAINING PROTOTYPE ONLY,原型ID是故意伤害罪-prototype-1prototype_size是100sentence_months的统计数据是median=8.0q25=6.0q75=12.0min=3.0max=36.0。defining_features里有几个关键的匹配特征,比如cat:injury_result=轻伤(z值1.556)、num:minor_injury_countz值1.486)、cat:injury_degree=轻伤二级(z值1.151)、cat:criminal_motive=琐事(z值0.756)等。这些特征与HELDOUT中的因素有部分匹配,比如injury_result=轻伤、minor_injury_count=1.0、injury_degree=轻伤二级、criminal_motive=琐事,这些在原型的defining features中都有较高的z值,说明匹配度较高。不过criminal_cause在HELDOUT中是债务纠纷,而原型中的defining feature是“cat:criminal_cause=琐事争执”,这里可能存在差异,但用户提供的HELDOUT中criminal_cause是债务纠纷,而原型的defining feature里有“琐事争执”,不过匹配距离是0.1548,说明还是有一定匹配度的。\n\n接下来,匹配依据应该基于原型的defining features与HELDOUT因素的重合部分。比如injury_result=轻伤、minor_injury_count=1.0、injury_degree=轻伤二级、criminal_motive=琐事,这些在HELDOUT中都存在,而原型的defining features中这些特征的z值较高,说明这些是原型的关键特征,因此匹配依据是这些特征的一致性。\n\n然后是统计区间,原型的sentence_months给出了median=8.0q25=6.0q75=12.0min=3.0max=36.0。所以统计区间应该引用这些数据,即四分位区间为6-12个月,中位数为8个月,同时要说明这是基于匹配原型的统计结果。\n\n需要注意的是,HELDOUT中的一些因素在原型的defining features中可能有正负z值,比如num:crime_amount和num:crime_count是负z值,可能表示这些因素在原型中影响较小或缺失,而HELDOUT中这些因素也是null或未提及,可能影响不大。另外,HELDOUT中有surrender=true、compensation_to_victim=全额赔偿、victim_forgiveness=true、guilty_plea=true这些从轻情节,但匹配的原型defining features中没有这些特征,可能原型中未重点考虑这些因素,这可能导致统计区间的不确定性,需要指出。\n\n现在需要组织语言:首先说明匹配的原型是“故意伤害罪-prototype-1”,匹配依据是案件的关键因素与原型的定义特征高度重合,如injury_result=轻伤、minor_injury_count=1.0、injury_degree=轻伤二级、criminal_motive=琐事等。然后给出原型的刑期统计区间:中位数8个月,四分位区间6-12个月(q25=6.0q75=12.0),范围3-36个月。接着强调不确定性,因为案件中存在的自首、全额赔偿、被害人谅解、认罪认罚等从轻情节在匹配原型的定义特征中未被重点纳入,可能影响实际刑期与统计区间的差异。\n\n最后,按照要求返回JSON格式,键为\"advice\",值为上述解释内容。"
}
}
],
"created": 1785386550,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 1189,
"prompt_tokens": 811,
"total_tokens": 2000,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 1027,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 1189,
"prompt_tokens": 811,
"total_tokens": 2000,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 1027,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "02178538652398866d4fff57f472cc97f9dc7178d7aa526f9b569"
}
}
@@ -0,0 +1,89 @@
{
"signature": "3cf09794d7d3e6c800c5a0512fb5cfa77bbf9df5316f0507ea7c02a95f542211",
"parsed": {
"advice": "匹配依据:HELDOUT案件的伤害结果(轻伤)、轻伤数量(1.0)、伤害程度(轻伤二级)、犯罪原因(琐事争执)等特征与训练集原型“故意伤害罪-prototype-1”的关键定义特征(如cat:injury_result=轻伤、num:minor_injury_count、cat:injury_degree=轻伤二级、cat:criminal_cause=琐事争执)高度匹配。统计区间:该原型刑期中位数为8个月,四分位区间(Q25-Q75)为6-12个月,刑期范围3-36个月。因可能存在未纳入统计的其他影响因素,实际刑期存在不确定性。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-14123b59cc7ff466",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:07.708747+00:00",
"latency_ms": 18832.737,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"故意伤害罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": \"全额赔偿\", \"victim_forgiveness\": null, \"joint_crime\": null, \"criminal_record\": null, \"arrest_method\": null, \"return_of_stolen_property\": null, \"crime_count\": null, \"crime_amount\": null, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": 1.0, \"injury_degree\": \"轻伤二级\", \"weapon_type\": \"拳头\", \"criminal_cause\": \"琐事争执\", \"community_correction_eligibility\": null, \"victim_fault\": null, \"compensation_status\": \"全额赔偿\", \"mutual_fight\": null, \"injury_result\": \"轻伤\", \"criminal_motive\": null, \"drunk_crime\": null, \"minor_injury_count\": 1.0, \"serious_injury_count\": 0.0}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"故意伤害罪-prototype-1\", \"charge\": \"故意伤害罪\", \"prototype_size\": 100, \"sentence_months\": {\"median\": 8.0, \"q25\": 6.0, \"q75\": 12.0, \"min\": 3.0, \"max\": 36.0}, \"defining_features\": [{\"feature\": \"cat:injury_result=轻伤\", \"z\": 1.5568442135769593}, {\"feature\": \"num:minor_injury_count\", \"z\": 1.4868344442139674}, {\"feature\": \"num:crime_amount\", \"z\": -1.1597831108065333}, {\"feature\": \"cat:injury_degree=轻伤二级\", \"z\": 1.1511080895758883}, {\"feature\": \"cat:criminal_cause=琐事争执\", \"z\": 1.0373867906329766}, {\"feature\": \"bool:mutual_fight\", \"z\": 0.8262918864452846}, {\"feature\": \"cat:criminal_motive=琐事\", \"z\": 0.7564756876813216}, {\"feature\": \"num:crime_count\", \"z\": -0.7017513783529625}], \"match_distance\": 0.10249547587327176}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "021785386528027446ef7fb8513dceba8bbc8b237b7867ee15b69",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:HELDOUT案件的伤害结果(轻伤)、轻伤数量(1.0)、伤害程度(轻伤二级)、犯罪原因(琐事争执)等特征与训练集原型“故意伤害罪-prototype-1”的关键定义特征(如cat:injury_result=轻伤、num:minor_injury_count、cat:injury_degree=轻伤二级、cat:criminal_cause=琐事争执)高度匹配。统计区间:该原型刑期中位数为8个月,四分位区间(Q25-Q75)为6-12个月,刑期范围3-36个月。因可能存在未纳入统计的其他影响因素,实际刑期存在不确定性。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,根据提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE来生成建议。首先,我要仔细查看这两个部分的内容。\n\n首先,HELDOUT EXTRACTED FACTORS中的罪名是“故意伤害罪”,伤害结果是“轻伤”,轻伤数量是1.0,伤害程度是“轻伤二级”,犯罪原因是“琐事争执”,其他因素如自首、坦白、赔偿、谅解等都是null或未提及。\n\n然后,MATCHED TRAINING PROTOTYPE是“故意伤害罪-prototype-1”,原型大小100,刑期的中位数是8个月,四分位25%是6个月,75%是12个月,最小值3个月,最大值36个月。定义特征中,有几个关键特征与HELDOUT中的因素匹配,比如“injury_result=轻伤”、“minor_injury_count”、“injury_degree=轻伤二级”、“criminal_cause=琐事争执”,这些z值较高,说明是原型的重要特征。\n\n接下来,我需要确定匹配依据:HELDOUT中的伤害结果(轻伤)、轻伤数量(1.0)、伤害程度(轻伤二级)、犯罪原因(琐事争执)均与原型的关键定义特征匹配,因此匹配到该原型。\n\n然后,统计区间应使用原型的刑期统计数据,即中位数8个月,四分位区间6-12个月,同时要强调不确定性,因为可能存在其他未提及的因素影响。\n\n需要注意用户要求只使用训练集原型统计,不能用外部知识,解释匹配依据和统计区间,强调不确定性。所以建议部分应说明匹配到的原型,依据的关键特征,然后给出原型的刑期统计区间,并指出存在不确定性。\n\n现在组织语言:匹配依据为HELDOUT案件的伤害结果(轻伤)、轻伤数量(1.0)、伤害程度(轻伤二级)、犯罪原因(琐事争执)等特征与“故意伤害罪-prototype-1”的关键定义特征(如injury_result=轻伤、minor_injury_count、injury_degree=轻伤二级、criminal_cause=琐事争执)高度匹配。该原型统计的刑期中位数为8个月,四分位区间(Q25-Q75)为6-12个月,刑期范围3-36个月。由于案件可能存在未纳入统计的其他因素,实际刑期存在不确定性。\n\n最后,按照要求返回JSON格式的advice。"
}
}
],
"created": 1785386546,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 694,
"prompt_tokens": 810,
"total_tokens": 1504,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 546,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 694,
"prompt_tokens": 810,
"total_tokens": 1504,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 546,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "021785386528027446ef7fb8513dceba8bbc8b237b7867ee15b69"
}
}
@@ -0,0 +1,89 @@
{
"signature": "2f23690cad76a4ab3eaa8de092035e76f646a5e4e539ff9c37fd510e8f3659bd",
"parsed": {
"advice": "匹配依据:待分析案件的核心特征“injury_result=重伤”“serious_injury_count=1.0”“injury_degree=重伤二级”与训练集原型“故意伤害罪-prototype-0”的关键定义特征(z值较高的伤情相关特征)高度匹配,匹配距离为0.209,表明两者特征相似度较高。统计区间:该原型包含18个案件,刑期中位数为36个月,四分位区间(Q25-Q75)为24.0-38.25个月,刑期范围为9.0-96.0个月。不确定性:待分析案件存在“全额赔偿”“被害人谅解”“被害人过错”等因素,但原型的定义特征中未突出体现这些因素的影响,可能导致实际刑期与原型统计区间存在差异。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-1ff92ea20164c817",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:07.832489+00:00",
"latency_ms": 17280.584,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"故意伤害罪\", \"surrender\": false, \"truthful_confession\": null, \"compensation_to_victim\": \"全额赔偿\", \"victim_forgiveness\": true, \"joint_crime\": null, \"criminal_record\": null, \"arrest_method\": \"被抓获\", \"return_of_stolen_property\": null, \"crime_count\": null, \"crime_amount\": null, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": 1.0, \"injury_degree\": \"重伤二级\", \"weapon_type\": null, \"criminal_cause\": \"琐事争执\", \"community_correction_eligibility\": null, \"victim_fault\": true, \"compensation_status\": \"全额赔偿\", \"mutual_fight\": null, \"injury_result\": \"重伤\", \"criminal_motive\": \"琐事\", \"drunk_crime\": null, \"minor_injury_count\": 0.0, \"serious_injury_count\": 1.0}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"故意伤害罪-prototype-0\", \"charge\": \"故意伤害罪\", \"prototype_size\": 18, \"sentence_months\": {\"median\": 36.0, \"q25\": 24.0, \"q75\": 38.25, \"min\": 9.0, \"max\": 96.0}, \"defining_features\": [{\"feature\": \"cat:injury_result=重伤\", \"z\": 4.229908779144486}, {\"feature\": \"num:serious_injury_count\", \"z\": 4.229908779144443}, {\"feature\": \"cat:injury_degree=重伤二级\", \"z\": 3.8219021986682136}, {\"feature\": \"cat:injury_degree=重伤\", \"z\": 1.4201266762286806}, {\"feature\": \"num:crime_amount\", \"z\": -1.181082234467661}, {\"feature\": \"cat:arrest_method=自首\", \"z\": 1.0441851275732448}, {\"feature\": \"bool:surrender\", \"z\": 1.011423265985623}, {\"feature\": \"cat:weapon_type=卡刀\", \"z\": 1.0027816465415866}], \"match_distance\": 0.2090534943522655}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "021785386528064e542ae8f7c04736102ead861a601340cc20c09",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待分析案件的核心特征“injury_result=重伤”“serious_injury_count=1.0”“injury_degree=重伤二级”与训练集原型“故意伤害罪-prototype-0”的关键定义特征(z值较高的伤情相关特征)高度匹配,匹配距离为0.209,表明两者特征相似度较高。统计区间:该原型包含18个案件,刑期中位数为36个月,四分位区间(Q25-Q75)为24.0-38.25个月,刑期范围为9.0-96.0个月。不确定性:待分析案件存在“全额赔偿”“被害人谅解”“被害人过错”等因素,但原型的定义特征中未突出体现这些因素的影响,可能导致实际刑期与原型统计区间存在差异。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,根据提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE ONLY来生成司法数据分析建议。首先,我要仔细分析这两个部分的内容。\n\n首先看HELDOUT EXTRACTED FACTORS,里面的罪名是“故意伤害罪”,关键因素包括:injury_result=重伤,serious_injury_count=1.0injury_degree=重伤二级,arrest_method=被抓获,compensation_to_victim=全额赔偿,victim_forgiveness=truevictim_fault=true等。\n\n然后是MATCHED TRAINING PROTOTYPE ONLY,原型ID是“故意伤害罪-prototype-0”,原型大小18个案件,刑期统计是median=36.0q25=24.0q75=38.25min=9.0max=96.0。定义特征里,最重要的几个是injury_result=重伤、serious_injury_count、injury_degree=重伤二级等,这些和HELDOUT中的因素高度匹配,比如injury_result=重伤、serious_injury_count=1.0、injury_degree=重伤二级都是原型的关键特征,z值较高,说明这些特征对匹配贡献大。\n\n接下来,我需要解释匹配依据,即HELDOUT中的重伤结果、重伤二级的伤情程度、1人重伤数量等核心特征与原型的定义特征高度相关,匹配距离0.209较低,说明匹配度较高。然后要说明统计区间,即该原型案件的刑期中位数36个月,四分位区间24.0-38.25个月,最小值9个月,最大值96个月。同时要强调不确定性,因为HELDOUT中存在全额赔偿、被害人谅解、被害人过错等因素,而原型的定义特征中未包含这些因素(比如原型的定义特征里没有赔偿、谅解、被害人过错相关的z值高的特征),这些因素可能影响刑期,但原型统计未体现,所以存在不确定性。\n\n需要注意的是,用户要求只使用给出的训练集案件原型统计与已抽取因素,不能用外部知识或自行给刑期数字。所以要基于原型的统计数据(median、q25、q75等)来描述,同时说明匹配依据是核心伤情特征的匹配,以及存在的不确定性因素(赔偿、谅解、被害人过错等未在原型定义特征中突出体现)。\n\n最后,按照要求返回JSON格式,键为\"advice\",值为上述解释内容,强调匹配依据、统计区间和不确定性。"
}
}
],
"created": 1785386545,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 752,
"prompt_tokens": 810,
"total_tokens": 1562,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 570,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 752,
"prompt_tokens": 810,
"total_tokens": 1562,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 570,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "021785386528064e542ae8f7c04736102ead861a601340cc20c09"
}
}
@@ -0,0 +1,89 @@
{
"signature": "e076f0ecb946316a3c191bd1cb2de039d1b125ab5ae0c825080974ce6253370e",
"parsed": {
"advice": "匹配依据:待预测案件罪名“故意伤害罪”与训练集原型“故意伤害罪-prototype-0”罪名一致。但待预测案件缺失该原型的核心定义特征,包括“injury_result=重伤”“serious_injury_count”“injury_degree=重伤二级”“injury_degree=重伤”“weapon_type=卡刀”等(上述特征在原型中z值均较高,对刑期影响显著),且待预测案件的“arrest_method=被抓获”与原型中具有正向影响的“arrest_method=自首”特征不匹配。统计区间:该原型案件的刑期中位数为36.0个月,四分位区间(Q25-Q75)为24.0-38.25个月,刑期范围为9.0-96.0个月。不确定性:由于待预测案件缺失对刑期影响显著的核心伤害结果及程度相关特征,且关键情节特征(如抓捕方式)与原型存在差异,上述统计区间可能无法准确反映其实际刑期,预测结果存在较大不确定性。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-264bc811294ca62f",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:10.235103+00:00",
"latency_ms": 25672.718,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"故意伤害罪\", \"surrender\": null, \"truthful_confession\": true, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": null, \"criminal_record\": null, \"arrest_method\": \"被抓获\", \"return_of_stolen_property\": null, \"crime_count\": null, \"crime_amount\": null, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": \"既遂\", \"disposal_of_stolen_property\": null, \"victim_count\": 1.0, \"injury_degree\": null, \"weapon_type\": null, \"criminal_cause\": \"琐事争执\", \"community_correction_eligibility\": null, \"victim_fault\": null, \"compensation_status\": null, \"mutual_fight\": null, \"injury_result\": null, \"criminal_motive\": null, \"drunk_crime\": null, \"minor_injury_count\": null, \"serious_injury_count\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"故意伤害罪-prototype-0\", \"charge\": \"故意伤害罪\", \"prototype_size\": 18, \"sentence_months\": {\"median\": 36.0, \"q25\": 24.0, \"q75\": 38.25, \"min\": 9.0, \"max\": 96.0}, \"defining_features\": [{\"feature\": \"cat:injury_result=重伤\", \"z\": 4.229908779144486}, {\"feature\": \"num:serious_injury_count\", \"z\": 4.229908779144443}, {\"feature\": \"cat:injury_degree=重伤二级\", \"z\": 3.8219021986682136}, {\"feature\": \"cat:injury_degree=重伤\", \"z\": 1.4201266762286806}, {\"feature\": \"num:crime_amount\", \"z\": -1.181082234467661}, {\"feature\": \"cat:arrest_method=自首\", \"z\": 1.0441851275732448}, {\"feature\": \"bool:surrender\", \"z\": 1.011423265985623}, {\"feature\": \"cat:weapon_type=卡刀\", \"z\": 1.0027816465415866}], \"match_distance\": 0.13492562791224796}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "0217853865304671c17ef9c173e8babb4a0658490f0169ed398e3",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待预测案件罪名“故意伤害罪”与训练集原型“故意伤害罪-prototype-0”罪名一致。但待预测案件缺失该原型的核心定义特征,包括“injury_result=重伤”“serious_injury_count”“injury_degree=重伤二级”“injury_degree=重伤”“weapon_type=卡刀”等(上述特征在原型中z值均较高,对刑期影响显著),且待预测案件的“arrest_method=被抓获”与原型中具有正向影响的“arrest_method=自首”特征不匹配。统计区间:该原型案件的刑期中位数为36.0个月,四分位区间(Q25-Q75)为24.0-38.25个月,刑期范围为9.0-96.0个月。不确定性:由于待预测案件缺失对刑期影响显著的核心伤害结果及程度相关特征,且关键情节特征(如抓捕方式)与原型存在差异,上述统计区间可能无法准确反映其实际刑期,预测结果存在较大不确定性。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,根据提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE来生成建议。首先,我要仔细看一下这两个部分的内容。\n\n首先,HELDOUT EXTRACTED FACTORS里的罪名是“故意伤害罪”,其他因素包括:surrender为nulltruthful_confession为truecompensation_to_victim为nullvictim_forgiveness为nulljoint_crime为nullcriminal_record为nullarrest_method是“被抓获”,return_of_stolen_property为nullcrime_count为nullcrime_amount为nullguilty_plea为nullfirst_offense为nullrecidivism为nullcriminal_form是“既遂”,disposal_of_stolen_property为nullvictim_count是1.0injury_degree为nullweapon_type为nullcriminal_cause是“琐事争执”,community_correction_eligibility为nullvictim_fault为nullcompensation_status为nullmutual_fight为nullinjury_result为nullcriminal_motive为nulldrunk_crime为nullminor_injury_count为nullserious_injury_count为null。\n\n然后看MATCHED TRAINING PROTOTYPE,原型ID是“故意伤害罪-prototype-0”,罪名也是故意伤害罪,原型大小18个案件,刑期的中位数是36.0个月,Q25是24.0Q75是38.25min9.0max96.0。定义特征里有几个关键的z值较高的特征,比如injury_result=重伤、serious_injury_count、injury_degree=重伤二级等,这些是原型的主要定义特征。匹配距离是0.1349,看起来匹配度还可以,但需要注意的是,HELDOUT里很多关键特征是缺失的,比如injury_result、serious_injury_count、injury_degree、weapon_type等,而这些正是原型的核心定义特征(比如injury_result=重伤、serious_injury_count、injury_degree=重伤二级等z值很高)。\n\n接下来,用户要求只使用给出的训练集案件原型统计与已抽取因素,不能用外部知识或自行给刑期数字。需要解释匹配依据和统计区间,强调不确定性。\n\n首先,匹配依据:HELDOUT的罪名是“故意伤害罪”,与原型罪名一致。但HELDOUT中多个关键定义特征缺失,如injury_result(原型z值最高的特征)、serious_injury_count(原型第二高z值)、injury_degree(原型第三、四高z值)、weapon_type(原型第七高z值)等。同时,HELDOUT的arrest_method是“被抓获”,而原型中“arrest_method=自首”是正z值特征,可能存在不匹配。\n\n统计区间:原型的sentence_months中位数36.0Q25 24.0Q75 38.25min9.0max96.0。\n\n不确定性:由于HELDOUT缺失原型的核心定义特征(如伤害结果、重伤数量、伤害程度等),这些特征对刑期影响显著(z值高),且arrest_method与原型中的自首特征不匹配,可能导致匹配结果存在较大不确定性,实际刑期可能偏离原型统计区间。\n\n需要把这些整合到advice里,用中文,结构清晰,说明匹配依据(罪名一致但关键特征缺失、arrest_method不匹配),统计区间(原型的四分位和中位数等),强调不确定性(核心特征缺失导致影响显著)。"
}
}
],
"created": 1785386556,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 1047,
"prompt_tokens": 797,
"total_tokens": 1844,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 813,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 1047,
"prompt_tokens": 797,
"total_tokens": 1844,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 813,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "0217853865304671c17ef9c173e8babb4a0658490f0169ed398e3"
}
}
@@ -0,0 +1,89 @@
{
"signature": "f617ba509df58a15ddfc766c0a82b4b5bf4c173c9b9170563efbd7f7301319a5",
"parsed": {
"advice": "匹配依据:待匹配案件的“disposal_of_stolen_property=挥霍”“booty_squandered=true”“fraud_target=个人”及“fraud_amount”等特征与匹配原型“诈骗罪-prototype-0”的定义特征相符。该原型包含22个案件,刑期统计区间为:中位数45.0个月,四分位25%为11.25个月,四分位75%为58.5个月,最小值3.0个月,最大值168.0个月。不确定性:待匹配案件中“restitution_situation”“return_of_stolen_property”等原型定义特征为null,可能影响匹配准确性,刑期存在波动可能。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-00d660cf28f916f3",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:25.131371+00:00",
"latency_ms": 18902.945,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"诈骗罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": null, \"criminal_record\": null, \"arrest_method\": null, \"return_of_stolen_property\": null, \"crime_count\": null, \"crime_amount\": 163800.0, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": null, \"disposal_of_stolen_property\": \"挥霍\", \"victim_count\": null, \"fraud_count\": null, \"fraud_amount\": 163800.0, \"fraud_method\": \"虚构事实\", \"restitution_situation\": null, \"fraud_target\": \"个人\", \"proceeds_use\": \"挥霍\", \"impersonate_identity\": null, \"fraud_means\": null, \"fraud_victim_count\": null, \"booty_squandered\": true, \"fraud_middleman\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"诈骗罪-prototype-0\", \"charge\": \"诈骗罪\", \"prototype_size\": 22, \"sentence_months\": {\"median\": 45.0, \"q25\": 11.25, \"q75\": 58.5, \"min\": 3.0, \"max\": 168.0}, \"defining_features\": [{\"feature\": \"cat:proceeds_use=挥霍\", \"z\": 3.726229148285477}, {\"feature\": \"bool:booty_squandered\", \"z\": 3.629934841284343}, {\"feature\": \"cat:disposal_of_stolen_property=挥霍\", \"z\": 2.823242590137243}, {\"feature\": \"cat:restitution_situation=部分退赃\", \"z\": 2.149983260084719}, {\"feature\": \"num:fraud_victim_count\", \"z\": 1.7078406967199085}, {\"feature\": \"cat:fraud_target=个人\", \"z\": 1.6821971448205932}, {\"feature\": \"num:fraud_amount\", \"z\": 1.6088028147438735}, {\"feature\": \"cat:return_of_stolen_property=部分退赃\", \"z\": 1.5149027385089702}], \"match_distance\": 0.05566992861242549}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "02178538654535828a04e35a69bbadd2802274f74f3d7f07cd2a4",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待匹配案件的“disposal_of_stolen_property=挥霍”“booty_squandered=true”“fraud_target=个人”及“fraud_amount”等特征与匹配原型“诈骗罪-prototype-0”的定义特征相符。该原型包含22个案件,刑期统计区间为:中位数45.0个月,四分位25%为11.25个月,四分位75%为58.5个月,最小值3.0个月,最大值168.0个月。不确定性:待匹配案件中“restitution_situation”“return_of_stolen_property”等原型定义特征为null,可能影响匹配准确性,刑期存在波动可能。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,根据提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE来生成建议。首先,我要仔细分析这两个部分的内容。\n\n首先看HELDOUT EXTRACTED FACTORS,里面的罪名是诈骗罪,犯罪金额(fraud_amount)是163800.0元,处置赃物方式(disposal_of_stolen_property)是“挥霍”,赃款挥霍(booty_squandered)是true,诈骗对象(fraud_target)是“个人”,其他很多因素如自首、坦白、赔偿、谅解等都是null。\n\n然后看MATCHED TRAINING PROTOTYPE,原型ID是“诈骗罪-prototype-0”,样本量22个,刑期的中位数是45.0个月,四分位25%是11.2575%是58.5,最小值3.0,最大值168.0。定义特征里有几个关键的,比如“proceeds_use=挥霍”、“booty_squandered”、“disposal_of_stolen_property=挥霍”、“fraud_target=个人”、“fraud_amount”这些,而待匹配案件中正好有“disposal_of_stolen_property=挥霍”、“booty_squandered=true”、“fraud_target=个人”、“fraud_amount=163800.0”,这些特征是匹配的。不过定义特征里还有“restitution_situation=部分退赃”和“return_of_stolen_property=部分退赃”,但待匹配案件中这两个因素是null,可能存在不匹配的地方。\n\n接下来需要解释匹配依据,就是待匹配案件的“disposal_of_stolen_property=挥霍”、“booty_squandered=true”、“fraud_target=个人”、“fraud_amount”等特征与原型的定义特征相匹配。统计区间就是原型的刑期统计,中位数45.0q25=11.25q75=58.5min=3.0max=168.0。同时要强调不确定性,因为待匹配案件中缺少“restitution_situation”和“return_of_stolen_property”等原型中的特征,可能影响匹配准确性。\n\n最后按照要求,只返回JSON,格式是{\"advice\":\"...\"},里面要包含匹配依据、统计区间和不确定性说明。需要注意不能使用外部法律知识,只能基于给出的训练集原型统计和抽取因素。"
}
}
],
"created": 1785386564,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 715,
"prompt_tokens": 815,
"total_tokens": 1530,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 560,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 715,
"prompt_tokens": 815,
"total_tokens": 1530,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 560,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "02178538654535828a04e35a69bbadd2802274f74f3d7f07cd2a4"
}
}
@@ -0,0 +1,89 @@
{
"signature": "7c6d7ba6bdba99791a1a163845edb81c2561c1224d80256f90401de55b557859",
"parsed": {
"advice": "匹配依据:待匹配案件的“fraud_target=个人”与训练原型“诈骗罪-prototype-0”的定义特征“cat:fraud_target=个人”相匹配。该原型统计的刑期区间为:最小值3.0个月,25%分位数11.25个月,中位数45.0个月,75%分位数58.5个月,最大值168.0个月。不确定性:待匹配案件中,原型的其他定义特征(如proceeds_use=挥霍、booty_squandered、disposal_of_stolen_property=挥霍、restitution_situation=部分退赃、fraud_victim_count、fraud_amount、return_of_stolen_property=部分退赃等)均无对应因素(显示为null),仅单一特征匹配,匹配度有限,刑期预测存在不确定性。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-13dd35638f8b54b0",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:26.558117+00:00",
"latency_ms": 19823.007,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"诈骗罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": true, \"criminal_record\": null, \"arrest_method\": \"被抓获\", \"return_of_stolen_property\": null, \"crime_count\": null, \"crime_amount\": null, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": null, \"disposal_of_stolen_property\": null, \"victim_count\": null, \"fraud_count\": null, \"fraud_amount\": null, \"fraud_method\": \"冒充身份\", \"restitution_situation\": null, \"fraud_target\": \"个人\", \"proceeds_use\": null, \"impersonate_identity\": null, \"fraud_means\": null, \"fraud_victim_count\": null, \"booty_squandered\": null, \"fraud_middleman\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"诈骗罪-prototype-0\", \"charge\": \"诈骗罪\", \"prototype_size\": 22, \"sentence_months\": {\"median\": 45.0, \"q25\": 11.25, \"q75\": 58.5, \"min\": 3.0, \"max\": 168.0}, \"defining_features\": [{\"feature\": \"cat:proceeds_use=挥霍\", \"z\": 3.726229148285477}, {\"feature\": \"bool:booty_squandered\", \"z\": 3.629934841284343}, {\"feature\": \"cat:disposal_of_stolen_property=挥霍\", \"z\": 2.823242590137243}, {\"feature\": \"cat:restitution_situation=部分退赃\", \"z\": 2.149983260084719}, {\"feature\": \"num:fraud_victim_count\", \"z\": 1.7078406967199085}, {\"feature\": \"cat:fraud_target=个人\", \"z\": 1.6821971448205932}, {\"feature\": \"num:fraud_amount\", \"z\": 1.6088028147438735}, {\"feature\": \"cat:return_of_stolen_property=部分退赃\", \"z\": 1.5149027385089702}], \"match_distance\": 0.258570130426063}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "02178538654688143cc1cf6ced7e437334ae832707c282616f6fe",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待匹配案件的“fraud_target=个人”与训练原型“诈骗罪-prototype-0”的定义特征“cat:fraud_target=个人”相匹配。该原型统计的刑期区间为:最小值3.0个月,25%分位数11.25个月,中位数45.0个月,75%分位数58.5个月,最大值168.0个月。不确定性:待匹配案件中,原型的其他定义特征(如proceeds_use=挥霍、booty_squandered、disposal_of_stolen_property=挥霍、restitution_situation=部分退赃、fraud_victim_count、fraud_amount、return_of_stolen_property=部分退赃等)均无对应因素(显示为null),仅单一特征匹配,匹配度有限,刑期预测存在不确定性。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,作为司法数据分析助手,根据提供的训练集案件原型统计和抽取的因素来生成建议。首先,我需要仔细查看用户提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE ONLY的内容。\n\n首先,HELDOUT EXTRACTED FACTORS中的信息包括罪名是诈骗罪,共同犯罪为true,逮捕方法是被抓获,诈骗方法是冒充身份,诈骗目标是个人,其他因素如自首、坦白、赔偿、谅解、前科、犯罪次数、犯罪金额、认罪认罚、初犯、累犯、犯罪形式、赃物处置、被害人数量、诈骗次数、诈骗金额、退赃情况、赃款挥霍、诈骗中间人等均为null。\n\n然后,MATCHED TRAINING PROTOTYPE ONLY显示匹配的原型是“诈骗罪-prototype-0”,原型大小为22个案件,刑期的中位数是45.0个月,四分位25%是11.25个月,四分位75%是58.5个月,最小值3.0个月,最大值168.0个月。定义特征中,有一个特征是“cat:fraud_target=个人”,z值1.6821971448205932,这与待匹配案件的“fraud_target=个人”是匹配的。其他定义特征如proceeds_use=挥霍、booty_squandered、disposal_of_stolen_property=挥霍、restitution_situation=部分退赃、fraud_victim_count、fraud_amount、return_of_stolen_property=部分退赃等,待匹配案件中这些因素均为null,没有匹配。\n\n接下来,根据要求,只可使用给出的训练集案件原型统计与已抽取因素,解释匹配依据和统计区间,强调不确定性。匹配依据应该是待匹配案件的“fraud_target=个人”与原型的定义特征“cat:fraud_target=个人”相匹配,其他定义特征在待匹配案件中无对应因素(均为null)。统计区间就是原型的sentence_months的min、q25、median、q75、max。需要强调不确定性,因为只有一个特征匹配,其他关键因素(如诈骗金额、退赃情况、被害人数量等)缺失,可能影响结果的准确性。\n\n最后,组织语言,生成JSON格式的advice,包含匹配依据(fraud_target=个人匹配)、统计区间(min 3.0q25 11.25median 45.0q75 58.5max 168.0个月),并说明不确定性(其他定义特征缺失,匹配度有限)。"
}
}
],
"created": 1785386566,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 778,
"prompt_tokens": 797,
"total_tokens": 1575,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 593,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 778,
"prompt_tokens": 797,
"total_tokens": 1575,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 593,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "02178538654688143cc1cf6ced7e437334ae832707c282616f6fe"
}
}
@@ -0,0 +1,89 @@
{
"signature": "0b07d52ed7b1455d3fcac43d68278287862bba4276ab0eaffbfb53cb17b2bab1",
"parsed": {
"advice": "匹配依据为本案诈骗金额(130000元)、诈骗目标(个人)、诈骗次数(1次)、诈骗被害人数量(1人)、犯罪金额(130000元)等因素与训练原型“诈骗罪-prototype-1”的高权重特征(z值较高)存在关联。该原型包含98个案件,其刑期统计区间为:中位数18个月,四分位区间8-37.5个月,最小值4个月,最大值180个月。需注意,本案退赃情况、坦白、赔偿谅解等情节未知,且诈骗方法(伪造证件)与原型定义特征中的“虚构事实”不完全一致,刑期存在不确定性。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-2c6e52cdfbb2c177",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:30.206458+00:00",
"latency_ms": 25060.362,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"诈骗罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": null, \"criminal_record\": null, \"arrest_method\": \"被抓获\", \"return_of_stolen_property\": null, \"crime_count\": null, \"crime_amount\": 130000.0, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": null, \"criminal_form\": null, \"disposal_of_stolen_property\": null, \"victim_count\": 1.0, \"fraud_count\": 1.0, \"fraud_amount\": 130000.0, \"fraud_method\": \"伪造证件\", \"restitution_situation\": null, \"fraud_target\": \"个人\", \"proceeds_use\": null, \"impersonate_identity\": null, \"fraud_means\": \"伪造合同\", \"fraud_victim_count\": 1.0, \"booty_squandered\": null, \"fraud_middleman\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"诈骗罪-prototype-1\", \"charge\": \"诈骗罪\", \"prototype_size\": 98, \"sentence_months\": {\"median\": 18.0, \"q25\": 8.0, \"q75\": 37.5, \"min\": 4.0, \"max\": 180.0}, \"defining_features\": [{\"feature\": \"num:fraud_amount\", \"z\": 1.2307636843371674}, {\"feature\": \"cat:fraud_target=个人\", \"z\": 1.0781858944170049}, {\"feature\": \"cat:fraud_method=虚构事实\", \"z\": 1.023056629094547}, {\"feature\": \"num:fraud_count\", \"z\": 0.9259512058187134}, {\"feature\": \"num:fraud_victim_count\", \"z\": 0.7729058128215746}, {\"feature\": \"num:crime_amount\", \"z\": 0.7680126997226981}, {\"feature\": \"cat:restitution_situation=全部退赃\", \"z\": 0.6567069704900581}, {\"feature\": \"num:theft_amount\", \"z\": -0.6458056132900002}], \"match_distance\": 0.43279151303600627}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "021785386550444081222809c6b24dcd35df0bef1acd71d22a061",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据为本案诈骗金额(130000元)、诈骗目标(个人)、诈骗次数(1次)、诈骗被害人数量(1人)、犯罪金额(130000元)等因素与训练原型“诈骗罪-prototype-1”的高权重特征(z值较高)存在关联。该原型包含98个案件,其刑期统计区间为:中位数18个月,四分位区间8-37.5个月,最小值4个月,最大值180个月。需注意,本案退赃情况、坦白、赔偿谅解等情节未知,且诈骗方法(伪造证件)与原型定义特征中的“虚构事实”不完全一致,刑期存在不确定性。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户的请求,作为司法数据分析助手,只使用给出的训练集案件原型统计与已抽取因素,不能用外部知识或自行给刑期数字。首先,我要仔细看用户提供的HELDOUT EXTRACTED FACTORS和MATCHED TRAINING PROTOTYPE ONLY。\n\n首先,HELDOUT的因素是诈骗罪,犯罪金额130000,诈骗金额130000,诈骗目标个人,诈骗方法伪造证件,诈骗手段伪造合同,诈骗次数1,诈骗被害人1人,其他很多因素都是null,比如退赃情况、自首、坦白等都没有。\n\n然后看匹配的训练原型:prototype_id是诈骗罪-prototype-1,原型大小98个案件,刑期的中位数18个月,四分位25%是8,75%是37.5,最小4,最大180。定义特征里有几个z值,比如诈骗金额z=1.23,诈骗目标个人z=1.08,诈骗方法虚构事实z=1.02,诈骗次数z=0.93,诈骗被害人数量z=0.77,犯罪金额z=0.77,退赃情况全部退赃z=0.66,盗窃金额z=-0.65。匹配距离0.43。\n\n接下来要解释匹配依据和统计区间,强调不确定性。匹配依据应该是HELDOUT中的因素与原型的定义特征的匹配,比如诈骗金额、诈骗目标个人、诈骗次数、诈骗被害人数量、犯罪金额这些在HELDOUT中存在且与原型的高z值特征相关。但要注意HELDOUT的诈骗方法是“伪造证件”,而原型的诈骗方法是“虚构事实”,这可能有差异,但可能“伪造证件”属于“虚构事实”的一种?不过用户提供的HELDOUT明确写的是“伪造证件”,而原型是“虚构事实”,这里可能存在不匹配,但原型的定义特征里诈骗方法是虚构事实,z值1.02,而HELDOUT的诈骗方法是伪造证件,可能这是一个差异点,但匹配距离0.43,说明有一定匹配度。\n\n统计区间就是原型的刑期统计:中位数18个月,四分位区间Q25=8Q75=37.5,即8-37.5个月,最小值4,最大值180。需要说明这是基于匹配原型的统计结果,且HELDOUT中缺乏原型里的“全部退赃”等有利因素(HELDOUT的restitution_situation是null),可能影响刑期,所以存在不确定性。\n\n需要组织语言,只返回JSON,advice里包含匹配依据(关键因素如诈骗金额、目标个人、次数、被害人数量与原型高z值特征匹配)、统计区间(中位数18,四分位8-37.5,min4max180),强调不确定性(如HELDOUT缺乏退赃等因素,诈骗方法与原型不完全一致等)。\n\n注意不能使用外部知识,只基于给出的信息。所以最终的advice应该是:匹配依据为本案诈骗金额、诈骗目标(个人)、诈骗次数、诈骗被害人数量、犯罪金额等因素与训练原型“诈骗罪-prototype-1”的高权重特征(z值较高)存在关联。该原型包含98个案件,其刑期统计区间为:中位数18个月,四分位区间8-37.5个月,最小值4个月,最大值180个月。需注意,本案缺乏原型中“全部退赃”等有利情节(本案退赃情况未知),且诈骗方法(伪造证件)与原型定义特征中的“虚构事实”不完全一致,刑期可能存在不确定性。"
}
}
],
"created": 1785386575,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 979,
"prompt_tokens": 813,
"total_tokens": 1792,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 824,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 979,
"prompt_tokens": 813,
"total_tokens": 1792,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 824,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "021785386550444081222809c6b24dcd35df0bef1acd71d22a061"
}
}
@@ -0,0 +1,89 @@
{
"signature": "b2a585ba80d75b1dccd6678db8f101a42858aeae5e72a2d35d43852b73f89558",
"parsed": {
"advice": "匹配依据:待预测案件的“proceeds_use=挥霍”“booty_squandered=true”“disposal_of_stolen_property=挥霍”“fraud_target=个人”等特征与匹配原型“诈骗罪-prototype-0”的关键定义特征(z值较高)相符。该原型包含22个案件,刑期统计区间为四分位区间11.25个月至58.5个月,中位数为45.0个月。需注意,案件中“restitution_situation”“return_of_stolen_property”等特征为null,与原型中部分定义特征(如“restitution_situation=部分退赃”)存在差异,可能影响刑期预测,故最终刑期存在不确定性,可能落在上述统计区间内。"
},
"receipt": {
"purpose": "3-12 held-out prototype-grounded advice cail2018-2e21e0717140020b",
"provider": "ark",
"endpoint": "https://ark.cn-beijing.volces.com/api/v3",
"started_at": "2026-07-30T04:42:35.925809+00:00",
"latency_ms": 18766.357,
"request": {
"model": "doubao-seed-1-6-250615",
"messages": [
{
"role": "system",
"content": "你是司法数据分析助手。只可使用给出的训练集案件原型统计与已抽取因素,不得使用原始训练案件、外部法律知识或自行给出其他刑期数字。解释匹配依据和统计区间,强调不确定性。只返回 JSON{\"advice\":\"...\"}。不要写免责声明,系统会统一附加。"
},
{
"role": "user",
"content": "HELDOUT EXTRACTED FACTORS:\n{\"charge\": \"诈骗罪\", \"surrender\": null, \"truthful_confession\": null, \"compensation_to_victim\": null, \"victim_forgiveness\": null, \"joint_crime\": null, \"criminal_record\": true, \"arrest_method\": null, \"return_of_stolen_property\": null, \"crime_count\": 1.0, \"crime_amount\": 20000.0, \"guilty_plea\": null, \"first_offense\": null, \"recidivism\": true, \"criminal_form\": null, \"disposal_of_stolen_property\": \"挥霍\", \"victim_count\": 1.0, \"fraud_count\": 1.0, \"fraud_amount\": 20000.0, \"fraud_method\": \"虚构事实\", \"restitution_situation\": null, \"fraud_target\": \"个人\", \"proceeds_use\": \"挥霍\", \"impersonate_identity\": null, \"fraud_means\": \"虚假贷款\", \"fraud_victim_count\": 1.0, \"booty_squandered\": true, \"fraud_middleman\": null}\n\nMATCHED TRAINING PROTOTYPE ONLY:\n{\"prototype_id\": \"诈骗罪-prototype-0\", \"charge\": \"诈骗罪\", \"prototype_size\": 22, \"sentence_months\": {\"median\": 45.0, \"q25\": 11.25, \"q75\": 58.5, \"min\": 3.0, \"max\": 168.0}, \"defining_features\": [{\"feature\": \"cat:proceeds_use=挥霍\", \"z\": 3.726229148285477}, {\"feature\": \"bool:booty_squandered\", \"z\": 3.629934841284343}, {\"feature\": \"cat:disposal_of_stolen_property=挥霍\", \"z\": 2.823242590137243}, {\"feature\": \"cat:restitution_situation=部分退赃\", \"z\": 2.149983260084719}, {\"feature\": \"num:fraud_victim_count\", \"z\": 1.7078406967199085}, {\"feature\": \"cat:fraud_target=个人\", \"z\": 1.6821971448205932}, {\"feature\": \"num:fraud_amount\", \"z\": 1.6088028147438735}, {\"feature\": \"cat:return_of_stolen_property=部分退赃\", \"z\": 1.5149027385089702}], \"match_distance\": 0.16728335313876955}"
}
],
"temperature": 0,
"seed": 37,
"max_tokens": 900,
"response_format": {
"type": "json_object"
}
},
"response": {
"id": "021785386556164c85c17782ebc45169ced08d4429f63da29d0fa",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "{\"advice\":\"匹配依据:待预测案件的“proceeds_use=挥霍”“booty_squandered=true”“disposal_of_stolen_property=挥霍”“fraud_target=个人”等特征与匹配原型“诈骗罪-prototype-0”的关键定义特征(z值较高)相符。该原型包含22个案件,刑期统计区间为四分位区间11.25个月至58.5个月,中位数为45.0个月。需注意,案件中“restitution_situation”“return_of_stolen_property”等特征为null,与原型中部分定义特征(如“restitution_situation=部分退赃”)存在差异,可能影响刑期预测,故最终刑期存在不确定性,可能落在上述统计区间内。\"}",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null,
"reasoning_content": "\n我现在需要处理用户提供的司法数据分析请求。首先,我要仔细查看用户给出的待预测案件的抽取因素(HELDOUT EXTRACTED FACTORS)和匹配的训练集原型(MATCHED TRAINING PROTOTYPE ONLY)。\n\n首先,待预测案件的罪名是诈骗罪,关键因素包括:proceeds_use=挥霍,booty_squandered=truedisposal_of_stolen_property=挥霍,fraud_target=个人,fraud_amount=20000.0fraud_victim_count=1.0,同时有criminal_record=true和recidivism=true,但这些在匹配的原型中可能不是定义特征。\n\n匹配的原型是“诈骗罪-prototype-0”,原型大小为22个案件,刑期统计包括中位数45.0个月,Q25=11.25Q75=58.5min=3.0max=168.0。定义特征中,前三个都是与“挥霍”相关的:proceeds_use=挥霍、booty_squandered(布尔值)、disposal_of_stolen_property=挥霍,这些待预测案件都匹配(proceeds_use=挥霍,booty_squandered=truedisposal_of_stolen_property=挥霍)。另外,fraud_target=个人也在定义特征中,待预测案件也是个人。fraud_amount和fraud_victim_count是数值型定义特征,待预测案件有这些数值(20000.0和1.0)。不过,原型的定义特征中还有“restitution_situation=部分退赃”和“return_of_stolen_property=部分退赃”,但待预测案件中restitution_situation和return_of_stolen_property都是null,这可能存在不匹配,但匹配距离是0.167,相对较低,说明整体匹配度尚可。\n\n接下来,根据要求,只能使用训练集原型的统计数据,不能用外部知识或原始案件。需要解释匹配依据、统计区间,并强调不确定性。匹配依据主要是待预测案件的proceeds_use=挥霍、booty_squandered=true、disposal_of_stolen_property=挥霍、fraud_target=个人等特征与原型的定义特征匹配。统计区间应使用原型的四分位区间(Q25=11.25Q75=58.5)和中位数45.0。需要说明刑期可能在该区间内,但存在不确定性,因为案件可能存在未匹配的特征(如restitution_situation为null,而原型中有部分退赃的特征)。\n\n最后,按照格式要求返回JSON,键为\"advice\",值为上述解释内容,注意不要使用Markdown,保持自然语言,强调不确定性,不给出具体刑期数字,只提统计区间。"
}
}
],
"created": 1785386574,
"model": "doubao-seed-1-6-250615",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": null,
"usage": {
"completion_tokens": 775,
"prompt_tokens": 827,
"total_tokens": 1602,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 609,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
},
"usage": {
"completion_tokens": 775,
"prompt_tokens": 827,
"total_tokens": 1602,
"completion_tokens_details": {
"accepted_prediction_tokens": null,
"audio_tokens": null,
"reasoning_tokens": 609,
"rejected_prediction_tokens": null
},
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
},
"response_model": "doubao-seed-1-6-250615",
"response_id": "021785386556164c85c17782ebc45169ced08d4429f63da29d0fa"
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
{
"batch_order": [
3,
1,
2,
0,
6,
5,
7,
9,
4,
11,
8,
10
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More