WWDC Quick Look 💓 By SwiftGGTeam
Create robust evaluations for agentic apps

Create robust evaluations for agentic apps

观看原视频

Highlight

Apple 在 Xcode 27 的 Evaluations 框架中新增了合成数据生成和工具调用轨迹验证能力,让开发者可以用代码批量生成评估数据集,并精确检查智能体应用中模型调用工具的顺序、参数和意图是否符合预期。

核心内容

手写评估数据的困境

你做了一个 AI 功能,比如根据书评自动打标签。写了 13 个测试用例,全部通过,分数很漂亮。上线后用户反馈却一团糟。

问题出在哪?13 个样本覆盖不了真实世界的复杂度。书的种类成千上万,书评风格千差万别,有的简短有的冗长,有的含糊其辞。小规模数据集给出的高分,往往是一种误导。(00:47

扩充数据集需要大量手工编写,耗时且难以保证多样性。Apple 的解法是:让模型帮你生成数据,同时用代码规则把关质量。

合成数据生成:从 13 条到 100 条

Evaluations 框架提供了 makeSamples API,只需要三个参数:一个 prompt、一个初始数据集、一个目标总数。框架会基于你的种子数据,自动生成新的样本流。(03:31

对于更复杂的场景,SampleGenerator 提供了完整配置能力。你可以指定使用哪个模型(比如 PrivateCloudComputeLanguageModel 获取更大上下文),自定义系统指令,选择采样策略(随机采样或滑动窗口),并定义验证规则拦截不合格样本。(05:53

验证生成的数据

生成数据不等于生成好数据。模型可能产出过短的书评、数量不对的 tag、大小写混乱的标签。SampleGeneratorvalidator 闭包让你在生成过程中实时过滤,通过验证的进入 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.samplesgenerator.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

评论

GitHub Issues · utterances