Highlight
Apple 在 Xcode 27 的 Evaluations 框架中新增了合成数据生成和工具调用轨迹验证能力,让开发者可以用代码批量生成评估数据集,并精确检查智能体应用中模型调用工具的顺序、参数和意图是否符合预期。
核心内容
手写评估数据的困境
你做了一个 AI 功能,比如根据书评自动打标签。写了 13 个测试用例,全部通过,分数很漂亮。上线后用户反馈却一团糟。
问题出在哪?13 个样本覆盖不了真实世界的复杂度。书的种类成千上万,书评风格千差万别,有的简短有的冗长,有的含糊其辞。小规模数据集给出的高分,往往是一种误导。(00:47)
扩充数据集需要大量手工编写,耗时且难以保证多样性。Apple 的解法是:让模型帮你生成数据,同时用代码规则把关质量。
合成数据生成:从 13 条到 100 条
Evaluations 框架提供了 makeSamples API,只需要三个参数:一个 prompt、一个初始数据集、一个目标总数。框架会基于你的种子数据,自动生成新的样本流。(03:31)
对于更复杂的场景,SampleGenerator 提供了完整配置能力。你可以指定使用哪个模型(比如 PrivateCloudComputeLanguageModel 获取更大上下文),自定义系统指令,选择采样策略(随机采样或滑动窗口),并定义验证规则拦截不合格样本。(05:53)
验证生成的数据
生成数据不等于生成好数据。模型可能产出过短的书评、数量不对的 tag、大小写混乱的标签。SampleGenerator 的 validator 闭包让你在生成过程中实时过滤,通过验证的进入 samples,失败的进入 invalidSamples,两者都可以随时访问。(10:37)
用扩充后的数据集重新跑评估,分数通常会下降。这反而是好事——它暴露了你之前没测到的问题。你可以在 Xcode 27 的 Evaluations Report 中直接对比两次结果,定位问题所在。(11:12)
工具调用评估:检查”怎么做”,而用调代替”做了什么”
智能体应用(agentic apps)的模型不会直接输出答案,用一系列工具:搜索、获取详情、查找相似项。最终输出看起来正确,但中间路径可能全错——比如模型根本没调用搜索工具,而是靠幻觉编了一个答案。(13:44)
Apple 引入了 TrajectoryExpectation(轨迹期望)来解决这个问题。它检查模型会话记录中的工具调用顺序、参数值和意图匹配。你可以指定工具调用的顺序(ordered)、无序出现(unordered)、甚至明确禁止某些调用(disallowed)。(16:04)
参数匹配支持多种方式:精确匹配(.exact)、自然语言意图匹配(.naturalLanguage)、包含检查(.contains)、范围匹配(.range)等。这让评估既能保持代码的严谨性,又能兼容大模型输出的模糊性。(17:07)
详细内容
用 makeSamples 快速扩充数据集
(05:16)
最基础的合成数据生成只需要几行代码:
let prompt = Prompt("""
Generate diverse range of book reviews and corresponding tags.
Cover a wide range of genres, time periods, cultures, and
reader personas. Do not repeat books already in the dataset.
""")
let dataset = Book.sampleBooks.map { book in
ModelSample(prompt: book.review, expected: BookTags(tags: book.tags))
}
let targetCount = 100
var expandedDataset = dataset
for try await sample in dataset.makeSamples(prompt, targetCount: targetCount) {
expandedDataset.append(sample)
print("Generated \(expandedDataset.count) samples so far.")
}
关键点:
prompt描述生成任务,越具体产出的数据质量越高dataset是种子数据,用ModelSample包装,包含输入(prompt)和期望输出(expected)targetCount是最终数据集的总大小(包含种子),这里会生成 87 条新数据makeSamples返回AsyncStream,可以实时处理每个生成样本
自定义 SampleGenerator 配置
(05:53)
需要更精细控制时,用 SampleGenerator:
let generator = SampleGenerator<ModelSample<BookTags>>(
prompt,
samples: dataset,
targetCount: targetCount,
sessionProvider: {
LanguageModelSession(
model: PrivateCloudComputeLanguageModel(),
instructions: """
You are a synthetic data generator for a book-tracking app's evaluation suite.
Your job is to produce realistic, diverse book entries that will stress-test
a tagging system.
Rules:
- Review must be at least 100 characters long.
- Review should cover a mix of genre, mood/tone, and themes.
- Reviews should vary in length.
- Create between 3 and 8 tags.
- Tags must be lowercase.
"""
)
}
)
关键点:
sessionProvider是一个返回LanguageModelSession的闭包,控制用哪个模型、什么系统指令- 框架自动处理 batch size,但会复用同一个 session。如果上下文窗口耗尽,会重新调用
sessionProvider获取新 session - 系统指令必须是自包含的,不能依赖”保持和之前一致”这类上下文依赖
- 默认使用设备端模型,可以切换到
PrivateCloudComputeLanguageModel获得更大上下文
验证生成的样本
(10:37)
let generator = SampleGenerator<ModelSample<BookTags>>(
prompt,
samples: dataset,
targetCount: targetCount,
sessionProvider: { /* ... */ },
validator: { sample in
guard let book = sample.expected else { return false }
// Review must be at least 100 characters
guard sample.promptDescription.count >= 100 else { return false }
// Must have between 3 and 8 tags
guard (3...8).contains(book.tags.count) else { return false }
// All tags must be lowercase
guard book.tags.allSatisfy({ $0 == $0.lowercased() }) else { return false }
return true
}
)
关键点:
validator对每个生成样本独立执行,没有跨样本的上下文- 适合用硬编码规则验证:长度检查、数量范围、格式约束
- 不适合验证需要跨样本判断的规则(如”多样性”),这类规则应放在系统指令里引导生成
访问验证结果
(10:58)
// During iteration
for try await sample in generator.run() {
expandedDataset.append(sample)
}
// After iteration
let allSamples = await generator.samples
let invalidSamples = await generator.invalidSamples
print("Generated \(allSamples.count) new samples. Total: \(expandedDataset.count)")
关键点:
generator.run()启动生成,返回异步流generator.samples和generator.invalidSamples实时更新,随时可访问- 验证失败的数据不会丢失,可以人工 review 后调整规则重新生成
定义工具的 @Generable 参数
(15:30)
工具参数用 @Generable 宏标记,让模型知道如何填充:
@Generable
struct SearchBooksArguments {
@Guide(description: "A freeform search term to match against titles, reviews, or tags")
var query: String?
@Guide(description: "Filter results to books with this specific tag")
var tag: String?
@Guide(description: "Filter results by mood")
var mood: String?
@Guide(description: "Filter results by genre")
var genre: String?
@Guide(description: "Maximum number of results to return. Defaults to 5.")
var limit: Int?
}
关键点:
- 所有参数都是 Optional,让模型根据用户意图自主选择填充哪些字段
@Guide提供参数说明,帮助模型理解每个参数的用途- 必填参数可能导致模型在模糊指令下产生幻觉,强行凑参数
基础轨迹期望:精确匹配参数
(16:37)
TrajectoryExpectation(
unordered: [
ToolExpectation(
"searchBooks",
arguments: [
.exact(argumentName: "tag", value: .string("gothic"))
]
)
]
)
关键点:
unordered表示不限制工具调用的时间顺序,只要求出现.exact要求参数值完全匹配指定字符串- 适用于参数值明确、可精确预期的场景
自然语言意图匹配
(17:07)
TrajectoryExpectation(
"searchBooks",
arguments: [
.naturalLanguage(
argumentName: "mood",
criteria: "Should relate to uplifting, hopeful, or positive feelings"
)
]
)
关键点:
.naturalLanguage用自然语言描述期望的意图,而非精确值- 适合评估模糊概念:情绪、风格、主题等
- 后台会触发额外的模型推理,评估耗时更长
有序工具调用期望
(17:34)
TrajectoryExpectation(
ordered: [
ToolExpectation(
"searchBooks",
arguments: [
.exact(argumentName: "tag", value: .string("gothic"))
]
),
ToolExpectation(
"getBookDetails",
arguments: [
.keyOnly(argumentName: "bookId")
]
)
]
)
关键点:
ordered要求工具按指定顺序调用- 先搜索再获取详情是合理顺序,反过来就是 bug
.keyOnly只检查参数是否存在,不限制具体值
禁止特定工具调用
(17:55)
TrajectoryExpectation(
unordered: [
ToolExpectation(
"searchBooks",
arguments: [
.naturalLanguage(
argumentName: "genre",
criteria: "Should refer to science fiction")
]
)
],
disallowed: [
ToolExpectation("findSimilarBooks")
]
)
关键点:
disallowed列出不允许出现的工具调用- 用户说”不要找相似的书”,模型如果调了
findSimilarBooks就是失败 - 测试模型对否定指令的遵循能力
完整的工具调用评估
(18:14)
let samples = SampleArrayLoader(samples: [
ModelSample(
prompt: "Find all the books tagged with 'gothic'.",
instructions: "Help the user explore their book collection.",
expectations: TrajectoryExpectation(
unordered: [
ToolExpectation(
"searchBooks",
arguments: [
.exact(argumentName: "tag", value: .string("gothic"))
]
)
]
)
)
])
struct BookLibraryToolCallEval: Evaluation {
var dataset = samples
let pass = Metric("All Passed")
let percent = Metric("Percentage Passed")
var evaluators: Evaluators {
ToolCallEvaluator(allPass: pass, percentagePass: percent)
}
}
关键点:
ModelSample包含用户 prompt、系统指令和轨迹期望ToolCallEvaluator自动运行模型会话,捕获工具调用记录,与期望对比- 评估结果直接显示在 Xcode 的 Evaluations Report 中
为工具评估生成合成数据
(19:20)
let prompt = Prompt("""
Generate diverse user queries for a personal book library assistant.
Each sample needs a prompt (what the user says), and a trajectory
expectation describing which tools should be called and in what order.
""")
let instructions = """
AVAILABLE TOOLS:
- searchBooks(query?, tag?, mood?, genre?, limit?): search the library
- getBookDetails(bookId): full details for one book
- findSimilarBooks(bookId, maxResults?): find books sharing tags
ORDER REQUIREMENTS:
- searchBooks must comes before getBookDetails or findSimilarBooks
- Use TrajectoryExpectation(ordered:) when sequence matters, else (unordered:)
USE THESE ARGUMENT MATCHERS:
- .exact for precise values, .naturalLanguage for fuzzy matching
- .keyOnly when any value is acceptable, .range for numeric constraints
- .contains/.hasPrefix/.hasSuffix for partial string matching
"""
关键点:
TrajectoryExpectation也遵循Generable协议,可以自动生成- 系统指令必须明确列出可用工具、调用顺序规则和参数匹配器用法
- 模型不知道你定义了哪些工具,所有上下文都要写在 instructions 里
验证工具评估的合成样本
(19:51)
validator: { sample in
// Must have expectations defined
guard sample.output.expectations != nil else { return false }
let expectations = sample.output.expectations!
// Must reference at least one tool
let totalExpectations = expectations.ordered.count + expectations.unordered.count
guard totalExpectations > 0 else { return false }
// All tool names must be from the valid set
let validTools: Set<String> = ["searchBooks", "getBookDetails", "findSimilarBooks"]
let allExpectations = expectations.ordered + expectations.unordered + expectations.disallowed
for expectation in allExpectations {
guard validTools.contains(expectation.name) else { return false }
}
return true
}
关键点:
- 验证合成数据是否包含轨迹期望
- 确保至少引用了一个工具
- 检查所有工具名都在预定义集合中,防止模型编造不存在的工具
核心启发
1. 给现有 AI 功能补一套数据驱动的评估
做什么:把你现有的 AI 功能(如智能回复、内容分类、推荐系统)接入 Evaluations 框架,用 SampleGenerator 从几十个种子样本扩充到几百个。
为什么值得做:大多数开发者靠”感觉”判断 AI 功能好坏。有了量化评估,每次改 prompt 或换模型都能看到分数变化,避免”越改越差”。
怎么开始:把现有的测试用例转成 ModelSample,写一个描述生成任务的 prompt,设置 targetCount 为 100,用 validator 过滤明显垃圾的数据,然后在 Xcode 里跑评估看分数。
2. 给多步骤 AI 工作流加上轨迹检查
做什么:如果你的 App 里有模型调用多个工具完成的复杂任务(搜索+筛选+排序+展示),用 TrajectoryExpectation 验证每一步。
为什么值得做:模型可能绕过你的业务逻辑直接给出答案,看起来对但实际没走正确流程。轨迹检查能抓出这类”结果对、路径错”的隐蔽 bug。
怎么开始:列出你的工具清单和合理调用顺序,为每个用户意图写 TrajectoryExpectation,用 ToolCallEvaluator 跑起来。从 .exact 匹配开始,模糊参数再用 .naturalLanguage。
3. 用合成数据做压力测试
做什么:用 SampleGenerator 生成大量边缘 case 数据——超长输入、空输入、多语言混合、特殊字符等。
为什么值得做:手工写边缘 case 很枯燥,容易遗漏。让模型生成可以覆盖你没想到的场景,暴露 prompt 或功能的脆弱点。
怎么开始:在 prompt 里明确要求生成”极端情况”的样本,比如”生成一条 5000 字符的书评”、“生成只包含表情符号的输入”,用 validator 确保格式正确,然后跑评估看功能是否崩溃。
4. 把评估接入 CI 流水线
做什么:把 Evaluations 测试写成 Swift Testing 用例,在每次代码提交或模型更新时自动运行。
为什么值得做:AI 功能的回归测试比传统代码更难察觉。模型版本升级、prompt 微调都可能影响行为。自动化评估能在问题进入生产环境前拦截。
怎么开始:在 Swift Testing 中定义 Evaluation 结构体,用 swift test 命令运行。把评估结果输出到文件,在 CI 中对比基线分数,下降超过阈值时阻断合并。
5. 构建模型即裁判(model-as-judge)的评分体系
做什么:对于难以用规则量化质量的输出(如创意写作、对话质量),用另一个模型当裁判打分。
为什么值得做:有些评估维度(如”书评 tag 是否贴切”)很难写代码判断。让模型来评模型,可以覆盖这类主观质量维度。
怎么开始:参考 Resources 中的 “Scoring with model-as-judge evaluators” 文档,定义评分维度和标准,用 Evaluations 框架的 model-as-judge evaluator 替代硬编码 validator。
关联 Session
- Improve your prompts by hill climbing with Evaluations — 用 Evaluations 框架通过 hill climbing 方法迭代优化 prompt,与本次 Session 的合成数据和工具评估形成完整的评估工作流
- Build agentic app experiences with Foundation Models — 构建智能体应用的基础,本次 Session 的 tool calling 评估正是针对这类应用的测试方案
- Meet the Evaluations framework — Evaluations 框架入门,介绍基础概念和 hill-climbing 流程,建议先观看
- Foundation Models on device — 了解设备端模型的能力边界,有助于设计合理的评估策略和选择合适的生成模型
- Swift Testing — Evaluations 框架与 Swift Testing 集成,学习如何编写和运行评估测试用例
评论
GitHub Issues · utterances