项目地址:seanzhang-zhichen/datagen
本文基于 Datagen
0.1.0的当前实现编写。
做信息抽取和文本分类的项目时,最先卡住的往往不是模型,是数据。
真实数据要么不够,要么标注太贵,要么带着不能直接用的隐私信息。大模型出现之后,"生成一批训练样本"这件事一下子变得很轻。最开始的想法也简单------写一段 Prompt,让模型批量吐 JSON,不就行了?
真跑起来才发现没那么顺。标签分布歪了,实体在原文里根本找不到,JSON 结构一会儿一个样,样本还一堆重复。更麻烦的是,一旦有样本被否掉,不知道该去补哪一类。模型是"生成"了数据,可离能稳定进后续流程,还差着一截。
所以我做了 Datagen。
这是一个基于 LangGraph、LiteLLM 和 Pydantic 的合成数据生成框架。输入可以是自然语言任务描述,也可以是明确的任务规格;系统会依次完成规划、生成、审校和本地校验,最后输出通用 JSONL 数据与一份运行报告。

它要解决什么问题
Datagen 没有被框成某一种标注工具。目前它支持四类常见任务,也可以塞进同一条输入里混着来:
| 任务 | 输出内容 | 典型场景 |
|---|---|---|
| 命名实体识别(NER) | 按实体类型组织的原文片段 | 人名、机构、产品、证件号抽取 |
| 单标签或多标签分类 | 任务、候选标签和真实标签 | 情感、意图、风险类型判断 |
| 关系抽取 | 关系名及其字段 | 任职关系、交易关系、产品归属 |
| JSON 结构化抽取 | 一个或多个命名结构 | 工单、订单、事件信息抽取 |
最终记录统一用 input / output 的结构。比如一条 NER 和分类组合的样本长这样(省掉了值为空的可选分类字段,方便看):
json
{
"input": "客户周宁反馈星云路由器到货后无法启动,希望办理退货。",
"output": {
"entities": {
"person": ["周宁"],
"product": ["星云路由器"]
},
"classifications": [
{
"task": "issue_type",
"labels": ["退款", "配送", "质量问题"],
"true_label": ["质量问题"],
"multi_label": false
}
]
}
}
这里有条硬约束:实体、关系参数、JSON 里抽出来的文本,必须能在 input 里原样找到。这件事在本地做校验,而不是听模型说自己生成对了。
从一次 Prompt,变成一条数据流水线
合成数据的质量,不能押在某一小段"神奇 Prompt"上。所以 Datagen 的核心不是一次模型调用,而是一个带状态的生成闭环:
text
自然语言描述 / TaskSpec
|
v
任务分解
|
v
约束规划
|
v
样本生成 ---> 标签审校 ---> 本地校验与统计
| |
存在缺口 达到目标
| |
v v
动态补缺 JSONL + 报告
|
+-----> 样本生成
整条工作流用 datagen/workflow/graph.py 里的 LangGraph 状态图串起来。每个节点只管一类事,运行状态里存着已接受的样本、失败原因、待重试的约束和统计信息。
1. 把自然语言需求变成任务契约
自然语言用来表达意图很顺,但当稳定的数据接口就差了点。所以第一步,是让 Datagen 把描述拆成 Pydantic 的 TaskSpec:任务类型、语言、实体类型、分类标签、关系字段、JSON 字段,还有可选的领域和生成提示,都定下来。
比如"生成中文客服工单,抽取人名和产品,并判断问题类型",就不再是一句话了,而是一份后面所有节点都照着来的 schema。配置不完整,Pydantic 会直接拦下:NER 得给实体类型,分类得给任务和标签,关系和 JSON 抽取也得有各自的结构定义。
两种用法都留了:标签体系还在摸索的时候,让模型自己去拆;等数据要进稳定训练流程了,就交一份写死的 TaskSpec。后者会跳过拆解模型,多跑几次用的都是同一套 schema。
2. 为每条样本单独规划,而不是只写一个大 Prompt
schema 定好之后,规划器会为每一条目标样本生成一个 ExampleConstraint。里面不止有领域、子主题、语气、视角、长度、句法复杂度这些,还能指定要不要否定、要不要歧义、实体几个、关系几个,以及这条样本必须覆盖到的分类标签、实体类型、关系类型或者 JSON 枚举值。
这样设计,是想把"请再多样一点"这句含糊的要求,变成一份能看得见的计划。分类标签和抽取类型按配额轮换,其余维度靠循环或者有界采样去变。设了 DATAGEN_SEED 之后,规划和 few-shot 抽样也更容易复现。
更关键的一点:均衡不是只停在最初的计划上。每批候选过完校验,系统会按已经通过的样本的真实分布,重新排下一批的约束------把生成机会优先给那些覆盖还不够的标签、实体类型、关系类型和枚举值。就算某类样本被否掉了,它留下的缺口照样有机会被补回来。
3. LLM 审校与确定性校验各做擅长的事
候选样本生成后,默认先过一遍标签审校。审校模型只回答一件事:这个标注有没有被原文、任务规格和当前约束撑起来。它不改样本,也不评价文风。
过了审校的候选,还得过一轮本地确定性检查:
- 输出是否符合任务 schema,是否出现未声明的类型或字段;
- 计划要求的标签、实体类型、关系类型和枚举值是否真正得到覆盖;
- 实体、关系参数和抽取值是否能在原文中落地;
- 关系字段在批次内是否保持一致;
- 样本是否与本次运行中已有记录完全重复;
- 同类型实体值是否被跨样本过度复用。
这两层检查划得很清楚:需要语义判断的标注对不对,交给 LLM;能用代码精确写出来的结构、集合、计数、字符串约束,交给代码。就算把标签审校关掉,本地校验也照跑。
4. 把失败变成下一轮输入,而不是静默丢弃
模型调用失败、JSON 解析失败、审校被拒、本地校验不过------这几种情况,原始的约束和失败原因都会留着。工作流先重试失败的约束,不够的数量再用新约束补;已经验收过的样本不会整批重来。
CLI 这边,每过完一个小批次的校验,就把新增的有效样本立刻追加到 JSONL 里。哪怕长任务中途断了,已经落盘的结果还在。正常跑完,旁边会生成一份 Markdown 报告,写清楚最终 TaskSpec、有效和无效的数量、重试、重复、标签和类型分布、实体值多样性,还有各阶段的 token 用量。
如何在五分钟内跑通一次生成
Datagen 要求 Python 3.10 及以上,建议用 uv 管环境。克隆仓库后装依赖:
bash
git clone https://github.com/seanzhang-zhichen/datagen.git
cd datagen
uv sync --all-groups
把环境变量模板复制一份,在 .env 里填上 LiteLLM 支持的模型名和对应 API Key:
bash
cp .env.example .env
Windows PowerShell 使用:
powershell
Copy-Item .env.example .env
模型配置就绪后,就能直接用自然语言描述任务了:
bash
uv run python -m datagen \
--task-description "从中文客服工单中抽取人名、产品名,并分类为退款、配送或质量问题" \
-n 20 \
-o outputs/support.jsonl
标签和字段都定好了的话,更推荐用仓库自带的 TaskSpec 模板:
bash
uv run python -m datagen \
--spec examples/task_specs/multi_task.json \
-n 20 \
-o outputs/multi-task.jsonl
也可以启动本地可视化工作台:
bash
uv run python fronted/server.py
浏览器打开 http://127.0.0.1:8765,就能编辑自然语言任务或完整的 TaskSpec,看各阶段状态、预览通过校验的记录、下载 JSONL。前端走的是同一套 DataGenerator 工作流,不会绕过后端校验。
在 Python 里用也很直接:
python
from datagen import DataGenerator
records = DataGenerator().generate(
"从中文科技新闻中抽取组织和产品,并判断文章情感",
n=10,
)
print(records[0])
想看任务规格、约束和统计,调 generate_state() 就能拿到完整的工作流状态。
它适合什么场景
更准确地说,Datagen 是"可控的候选数据生产器",而不是那种拿来就能当金标准、不用人看的数据源。它比较适合:
- 在真实标注数据不足时,为 NER、分类、关系或结构化抽取补充冷启动样本;
- 快速验证一个标签体系或输出 schema 是否可用;
- 为低频标签和边界场景定向补数据;
- 用固定
TaskSpec和运行报告记录一批合成数据是如何产生的; - 将统一的生成与校验能力嵌入现有数据工程流程。
要是任务牵涉医疗、金融、法律这类高风险决策,还是建议让领域专家把合成样本过一遍。本地的 grounding 校验只能证明抽取值确实出现在文本里,证明不了文本里的事实和专业结论是对的。
写在最后
做完这一版,对合成数据想明白了一件事:真正难的从来不是让模型输出一段看着挺像样的 JSON,而是定义"什么样的数据算合格",以及在失败之后怎么继续往目标上靠。
在 Datagen 里,自然语言需求先收敛成任务契约,多样性拆成逐样本的约束,再靠语义审校、确定性校验、动态补缺串成一个反馈闭环。它照样依赖模型的能力,也老老实实留着需要人工评估的边界。但跟一次性批量 Prompt 比起来,这条流水线让数据的结构、覆盖、失败、成本,都变得看得见、管得着。
代码、示例规格和完整文档都在 GitHub 上:https://github.com/seanzhang-zhichen/datagen。欢迎试用,也欢迎用 Issue 或 Pull Request 告诉你的场景和踩到的坑。