DataGen——合成数据生成器:把一句任务描述变成可校验的训练数据

项目地址: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 告诉你的场景和踩到的坑。

相关推荐
冬奇Lab1 小时前
Code Agent 解剖(23):从零扩展——接入 MCP 外部工具生态
人工智能·agent
不是株1 小时前
AI Agent 记忆系统全解:从 Markdown、SQLite、向量检索到 GraphRAG
sqlite·知识图谱·agent·memory·graphrag·harness
用户8082598666873 小时前
从零手写 ReAct Agent:不用任何框架,200 行代码跑通 Agent 核心循环
agent
明月_清风3 小时前
AI 时代,为什么架构师又开始谈"本体论"?
人工智能·后端·agent
阿里云大数据AI技术5 小时前
闲鱼「鱼卖卖」的长期记忆实践:用阿里云 Milvus 记住卖家的经营偏好
人工智能·agent
ch8565 小时前
别再让 RAG 翻车在「入库」这一步!1200 行保姆级离线入库全流程(PDF → 可检索)
agent
DreamLife☼6 小时前
Agent的典型应用场景深度解析
自动驾驶·agent·iot·工业·智慧交通·医疗健康
liulilittle6 小时前
为什么需要回程闲置保护?
ai·llm·prompt·agent·tools·subagent·opencode
七牛开发者7 小时前
HarnessDev:让 LLM 自己创建并迭代 Agent Harness
chatgpt·llm·agent