Phoenix TypeScript 合成测试数据生成:基于维度组合构建评估实验集
【免费下载链接】phoenixAI Observability & Evaluation项目地址: https://gitcode.com/gh_mirrors/phoenix13/phoenix
合成测试数据是 LLM 应用评估体系中的关键一环:当真实生产数据有限、涉及敏感信息或难以采集时,你可以通过引导模型生成结构化样本,快速覆盖常见场景、复杂多步骤用例以及错别字、越界查询等边界情况。本文基于 Phoenix 开源仓库中 TypeScript 侧的评估实践文档,系统讲解"维度驱动"的合成数据生成方法——先定义变化轴(dimensions),再生成组合元组,最后逐条转换为自然语言查询——并展示如何与@arizeai/phoenix-client的数据集(Dataset)与实验(Experiment)能力衔接,把合成数据变成可复现、可量化的评估流程。读完本文,你将掌握:维度设计、两步生成管线、面向失败模式的定向生成、质量控制与样本量规划,以及将合成数据上传 Phoenix 并运行实验的完整 TypeScript 代码路径。
何时使用合成数据:与真实数据的取舍
合成数据并非万能替代品,它与真实数据各有适用场景。评估文档给出了清晰的取舍标准:
| 使用合成数据 | 使用真实数据 |
|---|---|
| 生产数据有限 | 已有充足 traces |
| 测试边界与边缘情况 | 验证实际行为 |
| 上线前(pre-launch)评估 | 上线后(post-launch)监控 |
核心判断依据是目的:合成数据服务于"系统性覆盖",适合在发布前对评估器(Evaluator)进行压力测试,确保它能覆盖正常、边界、噪声等不同行为角度;而真实数据服务于"真实性验证",用于确认系统在实际流量下的表现。实践中两者是互补的——先用合成数据跑通评估管线、校准评估器,再切换到真实 traces 做持续监控。
维度驱动方法:定义变化轴
合成数据生成的第一步,是为你的领域定义"变化轴"(dimensions)。每个轴代表输入空间中的一个变化维度,轴上的取值枚举了该维度的代表性状态:
const dimensions = { issueType: ["billing", "technical", "shipping"], customerMood: ["frustrated", "neutral", "happy"], complexity: ["simple", "moderate", "complex"], };以上例而言,issueType刻画问题类型,customerMood刻画用户情绪,complexity刻画问题复杂程度。三个轴形成 3×3×3 = 27 种组合空间,每一组取值对应一种可预期的输入形态。
这种设计的价值在于可枚举、可追溯:每个合成样本都能回溯到它覆盖了哪些维度组合,评估结果出现偏差时,可以精确定位是哪个维度组合暴露了问题。这正是 Phoenix 评估方法论中"错误分析优先"(Error analysis first)的延伸——你无法自动化评估从未观察到的行为,而维度组合让"未观察到的行为"变成一张显式的清单。
面向已知失败模式的维度设计
维度不应凭空设计,而应来源于错误分析。将线上错误分析(可参考仓库中 axial-coding.md 描述的把开放笔记聚合成结构化失败分类法的流程)发现的失败类型直接映射为维度取值:
// 来自错误分析的发现 const dimensions = { timezone: ["EST", "PST", "UTC", "ambiguous"], // 已知失败:模糊时区 dateFormat: ["ISO", "US", "EU", "relative"], // 已知失败:日期格式混用 };例如,若线上数据显示模型在解析"相对日期"(如"last Tuesday")或模糊时区时频繁出错,就把ambiguous、relative显式列为维度取值。这样合成数据集就变成了对已知缺陷的回归测试集——每次修改 Prompt 或 Agent 逻辑后重跑,即可验证缺陷是否被修复、是否复发。
两步生成管线:从元组到自然语言
维度组合只是抽象的取值元组,真正用于评估的是贴近真实用户习惯的自然语言输入。因此生成过程分两步:
- 生成元组:组合维度取值,得到结构化的
Tuple; - 转换为自然查询:对每个元组发起一次独立的 LLM 调用,将其"翻译"成真实、多样、非公式化的用户消息。
import { generateText } from "ai"; import { openai } from "@ai-sdk/openai"; // Step 1: 创建元组 type Tuple = [string, string, string]; const tuples: Tuple[] = [ ["billing", "frustrated", "complex"], ["shipping", "neutral", "simple"], ]; // Step 2: 将元组转换为自然语言查询 async function tupleToQuery(t: Tuple): Promise<string> { const { text } = await generateText({ model: openai("gpt-4o"), prompt: `Generate a realistic customer message: Issue: ${t[0]}, Mood: ${t[1]}, Complexity: ${t[2]} Write naturally, include typos if appropriate. Don't be formulaic.`, }); return text; }该代码使用 Vercel AI SDK 的generateText与@ai-sdk/openai提供方。仓库中 setup-typescript.md 明确了 TypeScript 侧的环境要求:@arizeai/phoenix-evals2.x 需要Node.js >= 22.12与 AI SDKv7(ai@^7),且使用的模型提供方必须与 AI SDK v7 兼容(如@ai-sdk/openaiv4)。安装命令如下:
# 使用 npm npm install @arizeai/phoenix-client @arizeai/phoenix-evals @arizeai/phoenix-otel npm install @ai-sdk/openai # LLM-as-judge 评估器所需的提供方 # 使用 pnpm pnpm add @arizeai/phoenix-client @arizeai/phoenix-evals @arizeai/phoenix-otel两点关键设计值得注意:
- 独立 LLM 调用:每个元组单独生成,避免一次生成多个样本时模型"模式化"地套用同一句式;提示词中明确要求"Write naturally, include typos if appropriate. Don't be formulaic",主动注入错别字等噪声,提升样本真实性。
- 结构保留:元组中的维度信息可作为样本的 metadata 一并存储,评估时可按维度维度切片分析,实现"Per-dimension"的细粒度归因。
质量控制:验证、去重、平衡
批量生成后必须经过质量控制,否则合成数据的"覆盖性"会被低质量样本稀释。文档给出三项核心检查:
- Validate(验证):检查是否存在占位符文本、是否满足最小长度;
- Deduplicate(去重):使用 embedding 相似度移除近似重复的查询;
- Balance(平衡):确保各维度取值在数据集中覆盖均衡,避免某类组合过量、某类缺失。
其中"验证"可以通过确定性代码实现,无需额外 LLM 调用:
function validateQuery(query: string): boolean { const minLength = 20; const hasPlaceholder = /\[.*?\]|<.*?>/.test(query); return query.length >= minLength && !hasPlaceholder; }该函数用正则\[.*?\]|<.*?>捕获常见的占位符残留(如[insert text]、<name>),并强制 20 字符的最小长度阈值,剔除过短或无意义的生成结果。去重与平衡则需要结合 embedding 相似度计算与维度统计,可在生成循环之后作为批量校验步骤执行。质量控制应与上文的"确定性优先"原则呼应——能用代码完成的校验(长度、占位符、格式)就不要交给 LLM,确定性逻辑先行,LLM 只负责需要语义理解的环节。
样本量规划
合成数据集的规模取决于用途,文档给出了三档经验值:
| 用途 | 规模 |
|---|---|
| 初步探索(Initial exploration) | 50–100 |
| 全面评估(Comprehensive eval) | 100–500 |
| 每维度组合(Per-dimension) | 每个组合 10–20 |
规模选择要结合评估目标权衡:初步探索阶段用小样本快速验证评估管线和 Prompt 方向;全面评估需要更大样本以获得统计稳定的分数。这里需要特别强调稳定性问题——仓库中 experiments-running-typescript.md 明确指出:当任务或评估器是非确定性的(LLM 调用、工具使用、流式输出、LLM-as-judge),单次运行的分数是带噪声的,在小数据集上这种逐次噪声会淹没 Prompt 变更带来的真实信号。因此运行实验时,可以配合repetitions参数对每个样本重复执行多次并对分数取均值:
const experiment = await runExperiment({ client, experimentName: "synthetic-eval-v1", dataset: { datasetName: "customer_support_queries" }, task, evaluators, repetitions: 3, // 每个样本运行 3 次,平滑 LLM 采样噪声 maxConcurrency: 5, // 限制并发执行数 });判断原则是:当任务或评估器涉及 LLM 且数据集较小时优先使用 repetitions;当任务与评估器均为确定性逻辑(如与 ground truth 做字符串比对)时,单次运行即为答案。不要轻信基于单个 10 样本运行做出的调优决策——repetitions: 1(默认值)只是"静默依赖单次运行"而已。
将合成数据落地 Phoenix:数据集与实验
生成并质检后的合成样本,需要通过@arizeai/phoenix-client的数据集 API 上传到 Phoenix,再通过实验 API 驱动评估。仓库 experiments-datasets-typescript.md 与 createDataset.ts 源码 详细说明了这一链路。
创建数据集:upsert 语义
import { createClient } from "@arizeai/phoenix-client"; import { createDataset } from "@arizeai/phoenix-client/datasets"; const client = createClient(); const { datasetId } = await createDataset({ client, name: "customer_support_synthetic", examples: [ { input: { query: "ughh my order never arrived can you check it" }, output: { intent: "order_status", classification: "correct" }, metadata: { issueType: "shipping", complexity: "simple" }, }, // ... 其余合成样本 ], });源码层面的关键语义是upsert(不存在则创建,存在则更新):createDataset会先按名称匹配已有数据集,若同名数据集已存在,则更新为与本次传入的 examples 一致;用相同输入重复调用是无操作(no-op)。从 createDataset.ts 的实现可以看到,函数会将 examples 拆分为inputs、outputs、metadata、splits四组并行上传。此外,每个 example 支持携带稳定 ID(id)——服务器会更新匹配行而非新增行;也可以携带spanId将样本关联回源 trace,这在用真实 traces 采样建数据集时尤为有用。
Example类型的完整结构如下:
interface Example { input: Record<string, unknown>; // 任务输入 output?: Record<string, unknown> | null; // 期望输出 metadata?: Record<string, unknown> | null; // 附加上下文(如来源、类别) splits?: string | string[] | null; // 划分("train"、["train", "easy"] 等) spanId?: string | null; // 关联源 trace 的 OTEL span ID id?: string | null; // 稳定 ID,服务器更新匹配行 }将合成样本的维度取值写入metadata,即可在评估结果中按维度切片归因——这正是"维度驱动"方法论在 Phoenix 数据模型中的落点。
运行实验:把数据集变成评估证据
数据集创建完成后,用runExperiment定义任务函数与评估器即可执行评估。任务函数接收每个 example 的 input,返回模型输出;评估器通过asExperimentEvaluator包装为代码评估器,接收{ output, expected }并返回{ score, label }:
import { runExperiment, asExperimentEvaluator, } from "@arizeai/phoenix-client/experiments"; const task = async (example: { input: Record<string, unknown> }) => { return await callLLM(example.input.query as string); }; const intentMatch = asExperimentEvaluator({ name: "intent_match", kind: "CODE", evaluate: async ({ output, expected }) => ({ score: output === expected?.intent ? 1.0 : 0.0, label: output === expected?.intent ? "match" : "no_match", }), }); const experiment = await runExperiment({ client, experimentName: "synthetic-customer-support-v1", dataset: { datasetId }, task, evaluators: [intentMatch], });若任务内部调用 AI SDK 的generateText/streamText,需注意一个与仓库版本相关的细节:@arizeai/phoenix-client7.x 要求 AI SDK v7,而 AI SDK v7不会自行通过全局 tracer provider 发出 OpenTelemetry spans。正确做法是在任务函数内部构造@ai-sdk/otel的OpenTelemetry集成并逐调用传入telemetry选项,因为该集成在构造时绑定当前激活的 tracer provider,而runExperiment只在任务运行期间挂载实验专属的 provider——若在启动时一次性registerTelemetry,绑定发生在 provider 存在之前,任务 spans 会丢失。这与普通应用 tracing 的"启动时注册"习惯正好相反,是实验场景特有的注意事项。来自@arizeai/phoenix-evals的评估器会被自动追踪,无需额外 telemetry 配置。
合成数据生成的通用最佳实践
综合仓库中 cookbook 合成数据教程 与评估技能文档 phoenix-evals/SKILL.md 的要点,合成数据生成应遵循以下实践:
- 设定明确目标:先定义要覆盖的场景、边界情况和失败模式,再动手生成。维度的来源是错误分析,而不是拍脑袋;
- 结构化提示词:使用 JSON schema、校验规则和显式输出格式约束生成结果,要求模型"只输出合法 JSON、无代码围栏、无多余文本",降低解析失败率;
- 确保覆盖均衡:混合正/反例(正确与错误分类)、边界条件与多样输入,让评估器接受全面压力测试;
- 验证数据质量:检查 schema 合规性、逻辑一致性与真实感——Placeholder 残留、重复样本、维度失衡都会污染评估结论;
- 迭代优化:先跑一轮实验发现覆盖缺口,再针对性补充维度或调整生成提示词,形成"生成→评估→补缺→再生成"的闭环。
对于 Agent 类系统,合成数据集还应显式划分类别以保证均衡覆盖:happy-path(简单常见请求)、complex/multi-step(多步推理)、edge cases(歧义或不完整输入)、adversarial/refusal(越界或不安全请求)、noise(错别字、俚语、多语言)。这与 axial-coding.md 中 Agent 失败分类法的思想一脉相承——先定义行为边界(in-scope 与 out-of-scope),再为每个边界组织覆盖样本,才能系统验证 Agent 的可靠性、安全性与鲁棒性。
小结
维度驱动的合成数据生成,为 Phoenix 评估实验提供了一条可枚举、可追溯、可回归的数据供给路径:以错误分析为依据设计维度轴,用"元组 → 自然语言"两步管线批量产出贴近真实的样本,以验证、去重、平衡三道质量控制把关,最后按用途规模规划样本量,通过createDataset(upsert 语义)与runExperiment(含 repetitions 与并发控制)无缝接入 Phoenix 的实验评估闭环。对尚无充足生产数据的项目而言,这是在上线前建立评估基线、校准 LLM-as-a-Judge 最经济且最可控的方式。
【免费下载链接】phoenixAI Observability & Evaluation项目地址: https://gitcode.com/gh_mirrors/phoenix13/phoenix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考