-
通过爬山法评估优化你的提示词
了解比较评估的实用技巧,从而引导你完善提示工程,并为你的 App 选择最合适的模型。探索如何为性能建立基准、扩展评估策略,并将结果转换为 JSON 格式以便与其他工具集成。了解不同提示策略的适用场景,以及如何迭代优化提示词以获得最佳结果。
章节
- 0:00 - Introduction
- 2:42 - BookTracker's tagging problem
- 5:27 - Analyzing the evaluation results
- 8:26 - Drift between judge and human
- 9:37 - Measuring drift with Cohen's kappa
- 12:26 - Building a judge alignment evaluation
- 15:16 - Analyzing alignment failures
- 17:16 - Comparative evaluation: control vs experimental
- 19:12 - Refining the scoring dimensions
- 21:23 - Adding few-shot examples to the judge
- 23:38 - Going beyond prompts: adding a tool
- 27:17 - Next steps
资源
-
搜索此视频…
我叫 Marcus 是 Evaluations 框架团队的经理 很高兴向你展示 如何使用 Evaluations 改善你的智能功能 你现在可能已经知道 在 App 中使用 AI 是一种强大的方式 可为用户提供 全新的个性化体验 这项技术能为你的 App 增添一定深度 这是传统软件 之前无法实现的 但是 如何判断你的智能功能 在所有情况下是否 都能按预期运行 也是一项挑战 为此我们推出了 Evaluations 框架 为你提供所需工具 让你满怀信心地发布 满怀信心地发布 不仅仅需要一个框架 Evaluations 框架还支持 爬坡优化 这是一个迭代改进的过程 以评估分数为指引 持续提升功能质量 爬坡优化从开发阶段开始 即做出你希望与 现有功能对比衡量的变更 完成所有变更后 你需要运行评估 查看结果是否 符合你的预期 然后分析结果 以了解 你的功能还有 哪些可以进一步改善 充分利用爬坡优化流程 是系统化改进功能 的好方法 但有效的爬坡优化 不只是遵循循环流程 还需要一点点……科学思维 因此在本视频中 我将带你了解 如何改进提示词 通过遵循爬坡优化循环 同时在过程中融入 一些科学思维 接下来我将带你了解 如何进行比较性 Evaluations 让爬坡优化的过程 更加轻松 最后我们将超越 单纯修改提示词 通过改进智能功能 的其他方面来提升质量 但在继续之前 本视频讲解的是对 现有评估进行爬坡优化的过程 这意味着你已经编写了 评估流程的基础部分 能够全面了解你的 智能功能的优势 和不足之处 如果你还不熟悉如何做到这一点 请查看我们的另一个视频 "认识 Evaluations 框架" 该视频涵盖了你需要了解的 构建出色评估流程 所需的一切知识 介绍完这些 我们开始吧 在"认识 Evaluations 框架" 视频中我们介绍了 Book Tracker 如果你忘了 Book Tracker 可让 读者对书籍进行整理和评价 最近我读了很多经典著作 已将这些书添加到我的书目中 事实上我刚读完 《金银岛》 这是一本发人深省的读物 探讨了忠诚与背叛 之间的张力 Book Tracker 的新功能之一 是标签服务 使用模型根据 读者评价生成标签 虽然本次评价的标签 涵盖了书籍的整体主题 但我感觉 少了些什么 我本以为会看到像 "紧张"或"道德模糊"这样的标签 这些标签能体现 故事的主题 《小妇人》生成的标签 也有类似的问题 "感人"这样的标签更多反映 的是读者的感受 而非书籍的内容 书评中的情感表达很好 但不应该出现 在标签列表中 另外像"沉静稳重"这样的标签 直接摘自评价内容 当我日后想搜索书库时 这样的标签用处不大 看来 Book Tracker 的标签生成器 还没有达到我认为应有的水准 幸运的是我的同事 在开发这个功能时 编写了一个 Evaluation 用于根据一组标准 衡量标签质量 这是我针对 Book Tracker 书籍标签的 Evaluation 我特别想了解我们是如何 评判标签质量的 所以我向下滚动来查看 App 的定性方面 由分数维度类型来捕捉 相关性追踪标签对 书籍情节信息的代表程度 包括故事情节 主题或其他相关信息 实用性衡量标签 作为搜索词的质量
ModelJudgeEvaluator 使用分数维度 和提示词为每组 标签生成分数 我的计划是将这两本书 添加到我的 Evaluation 中 并查看返回的标签 这也将是一个好机会 了解我的评分与 模型评判者评分的对比情况 希望改进智能功能 的某个部分 是爬坡优化流程的起点 因此要启动循环 你需要从开发阶段开始 在这里你对功能和 Evaluation 进行所需的任何更改 在这个案例中 我将把我对《金银岛》的评价 添加到我的 Evaluation 数据集中 我对《小妇人》 也做了同样的处理 现在这两个条目已在 我的数据集中 我想运行我的 Evaluation 看看模型生成什么标签 以及评判者如何评分 但为了到达那一步 我们需要先问一问运行的 Evaluation 是否符合预期 提醒一下 你可以使用 Swift Testing 的 expect 宏来定义预期 这样你就能通过 测试是否通过 来判断预期是否达成
在这个案例中我的 Evaluation 满足了所有预期 但由于我知道标签 还没有达到我希望的水准 我需要进一步调查 这就进入了分析阶段 Xcode 的新评估报告 为我提供了深入信息 关于我上次 Evaluation 运行的情况 要查看更多详情我可以 点击我的 BookTaggingEvaluation 运行记录 这会打开评估详情视图 顶部显示的是 汇总指标图表 下方是结果表格
我现在想做的是 比较模型的响应 与我之前生成的 预期标签列表 我可以打开 Assistant Editor 来完成 现在我可以看到详细信息 关于数据集中每个条目 生成的标签 我想关注的是 模型生成结果 与我预期之间的差异 我可以在这张表中详细查看 这组词条还不错 但遗漏了一些关键细节 这些细节来自故事 用户可能会搜索 因此我会给这些标签 相关性打 4 分 实用性打 2 分 我的模型评判也给标签 相关性打了 4 分 很好 但它给实用性也打了 4 分 这是不对的 我应该看看对于我的 《小妇人》书评标签 这些标签并没有包含 我期望的所有有用信息 结果我也不认同 评判在这里给出的分数 再次强调 我认为相关性应为 4 分 实用性应为 2 分 这次分析让我清楚地看到 我的模型评判与我在 评估标签时存在差异 模型与人类之间的这种差异 被称为偏差 这是所有开发者 都面临的问题 在评估智能功能时 原因如下 假设我有一个包含 10 个样本的评估 我让模型评判和人工 分别对每个样本打分 模型和人工按照 1 到 4 的分数进行评分 最后我们对分数取平均值 以生成汇总结果 如果模型和人类在 评分上倾向于不一致 那么他们的平均分将会 彼此偏离 这就是偏差 随着数据集不断增长 偏差也会越来越大 到那时你将很难判断 你的功能是否得到了 正确的评估 为了解决这个问题 你可以将评判对齐到 专家的意见 现在我们知道偏差是个问题 我们需要一种方法 来了解 我们的模型评判偏离 专家评分的程度 一种实现方式 是将专家的评分 并排对齐 并标记两者匹配的位置 然后用此生成百分比 这个百分比 称为准确率 这是衡量对齐度的好方法 前提是评分标准中 每个值出现概率相等 然而你的数据集更可能 包含分数分布不均的值 分数分布不均匀 想想看 数据集通常包含 高质量输出的示例 因此通常情况下 人工评分者 往往会给数据集中的 条目打较高的分数 如果模型恰好对 较小的数据集 给出了高分 看起来两者似乎是对齐的 但当应用于 更大的数据集时 分数变化更多 它倾向于高分的习惯 仍然会导致偏差 所以我们需要准确率的替代方案 一种能够考虑到 数据集加权特性的方案 以及模型可能 猜对答案的概率 幸运的是有解决方案 Cohen's kappa 系数 是一个数学公式 由统计学家兼心理学家 Jacob Cohen 于 1960 年提出 Cohen's kappa 衡量对齐度 即两个评分者 多久达成一致 为此我们需要知道 评分者达成一致的比例 即准确率 这正是之前准确率指标 所计算的内容 但现在我们需要计算一个新值 巧合概率 表示 某个评分者 可能碰巧达成一致的机会 这种运气会根据概率 进行加权 某些答案更有可能出现 那么问题来了 如何计算它 要计算对齐度 我们从准确率分数开始 从准确率分数中 减去两个评分者 随机达成一致的可能性 最后将差值除以 随机一致的倒数 即两个评分者 有意达成一致的概率 结果就是对齐度 Cohen's kappa 是衡量 对齐度的强大方法 用于衡量模型评判 与专家意见之间的一致性 我可以用它来 逐步提升对齐分数 在我和我的模型评判之间 现在我们回到 爬山循环的起点 进入开发阶段 为此我将设置一个评估 将我的评分与评判对比 并生成对齐分数 为此我需要编写一个评估 由四个组件构成 首先是数据集 然后是评估的对象 接下来我需要定义评估器 最后我需要 汇总结果 那么我们从数据集开始 为了让评估正常工作 我的模型评判和我需要 评估完全相同的数据集 本例中模型评判审查标签 所以我需要生成一组通用标签 供评判和我共同审查 我正好有完美的数据集 我之前的评估 包含一组书评和标签 因为我在测试中运行了这个评估 Xcode 生成了一个附件 包含所有生成的 评估数据 我可以获取该附件 并提取摘要和标签对 提取摘要和标签对之后 我需要添加我的评分 完成之后 我可以将此文件的内容 作为评估的输入 接下来 我需要捕获 评估对象 通常 subject 方法用于 调用与功能相关的 API 与功能相关的 API 但由于生成的模型响应 是数据集的一部分 我们可以直接返回 已生成的标签 现在 我需要定义我的评估器 你可能已经猜到了 我的评估器与书籍标签评估中 完全相同的模型裁判评估器 与书籍标签评估中的相同 裁判在此处 提供其评分 最后 我需要汇总结果 在这里 我们将我的评分 与裁判的评分进行比较 为此 我们需要 计算 Cohen's kappa 我可以通过自定义 聚合方法来实现 除了 Cohen's kappa 之外 我还会计算均值 以及每个评分维度 的标准差 这有助于了解 裁判的评分 是升高还是降低 现在 我可以将测试 与评估进行关联 对于此测试 我设置了预期 我的评分与裁判的评分 应产生 0.6 的对齐分数 我们选择这个数字 是因为据统计学家所言 0.6 的对齐分数代表 有意义的一致程度 现在 是时候进行评估了 并为我们的对齐 获取基准 然后确定我的评估 是否符合预期 测试似乎失败了 这意味着我的预期 未能得到满足 因此 再次需要详细 分析结果 我现在了解到我的对齐分数 未能达到预期 我可以前往评估报告 获取更多信息 正如我所预期的 实用性和相关性的分数 都相当低 这意味着我的模型裁判 与我不一致 现在 我想获取更多信息 了解数据集中每个样本 的表现 为此 我需要打开助手 并详细查看结果 在浏览结果时 这篇《科学怪人》的评论 引起了我的注意 我可以看到我对标签的评分 与裁判的评分 存在相当大的差异 裁判似乎认为 self-help 之类的标签 和 self-improvement 与故事相关 psychological 也是 一个可以接受的搜索词 但用户不太可能 搜索这个词 然后我开始查看 数据集中的其他条目 存在类似问题的条目 并发现了 这篇《拉玛坚》的评论 裁判和我都认为 这些词语是有帮助的 且与书籍内容相关 我们的分歧在于实用性 visual-dimension 和 quaint-dignity 过于具体 那么问题出在哪里呢 我认为模型本身 没有足够的知识 来区分好标签 与坏标签 这可能是因为我的裁判提示 没有提供足够的上下文 为此 我需要 开发一个新提示 这样我就可以比较 当前提示的对齐分数 与新提示的分数 幸运的是 在 Xcode 27 中 我们可以相互比较 两次评估的结果 在进行比较时 一些科学思维 会大有裨益 在科学实验中 有两个组 对照组 代表基准 以及实验组 代表我们试图 比较的变化 我们可以用同样的方式 来理解两个版本的提示 其中对照组 由基础提示表示 实验组由我们 新修改的提示表示 我现在需要创建 第二版评估 使用实验性提示 作为基准 我们将使用与之前相同的评估 和相同的模型裁判提示 对于我们的实验性提示 我撰写了更详尽的描述 说明如何评判这组标签 首先为裁判提供 关于 App 的上下文 以及即将评判的内容 然后给出好标签的示例 以及识别坏标签的方法 两个提示都写好后 我可以将两个评估 都添加到测试套件中 这将运行两个评估 我现在就运行该套件 并比较结果 评估完成后 我可以返回评估报告 我的相关性对齐分数 似乎有所提升 而实用性对齐分数 则大幅下降 平衡这样的权衡很棘手 因此我需要仔细考虑 如何推进 但在深入分析之前 先检查是否通过 我的测试证实了 这一点 我们没有通过 进一步思考之后 我打算保留这次提示修改 并将下一轮迭代的重点 放在提升实用性分数上 因此 审查结果 最有效的方式 是将两位裁判的 实用性分数相互比较 为此 我可以使用评估报告 中新的比较视图 在评估报告中 我可以点击比较按钮 并打开我的基准评估 在这里 我可以并排查看 两个提示的分数 有一件事立刻 引起了我的注意 就是实用性分数 之间的差异 这篇《道林·格雷的画像》 的评论中 模型在有用性上 评分似乎过于苛刻 实验评估中的有用性列 似乎印证了我的猜测 我注意到所有分数 不是3分就是2分 这评分实在太严格了 我认为有一个方法 可以改善这个问题 就是更具体地说明 如何给每个维度打分 为此 我需要对 实验评估做一些修改 但在修改实验评估之前 我将新的提示词应用 从实验评估 导入到我的基准测试 这样可以确保只有 一个不同的变量 也就是对评分维度 所做的修改 对于相关性 我提供了 一个稍长的描述 强调了需要包含 类型标签的要求 这是有用性的描述 强调对过于具体的标签 要更加严格批判 我再次等待 评估运行完成 两项分数相比基准 都大幅提升了 看来这些 具体的评分维度 将会更有帮助 但我们还没完全 达成对齐目标 所以现在 我需要 再做一次对比 来找出还有哪些地方 可以进一步改进 为了深入分析 我回到了 实验评估环节 我想详细审查结果 所以我打开了助手视图 翻看结果时我找到了 《白鲸》的评价 我的相关性得分 开始趋于对齐了 但我的有用性得分 还需要继续改进 部分结果看起来不错 另一些仍相差甚远 《科学怪人》这篇评价 仍然让模型评判困难 我认为模型评判现在需要 一些我的判断方式示例 以便为它提供一个 按我标准评判的参考模式 这意味着我们需要 再进行一轮爬坡优化 我已经将新的评分维度 添加到基准评估中 现在 我重新调整了 主评判提示词 以便为它提供更多 关于目标的细节 关于标签生成功能 帮助模型深入理解 问题所在的情境 在此基础上 我编写了 一系列示例 供模型用作 评审的参考指南 我只给了模型 少量示例 如果给它更长的列表 我容易使对齐分数过拟合 这样就很难判断 模型评判是否真正 与我的标准对齐 现在对比已经公平了 我需要运行评估 并查看结果 现在 我的分数终于 超过了预期值 这意味着我终于通过了 可以退出循环了 这意味着我可以确信 当模型评判提供评分时 我可以有把握地说 标签是否符合我的标准 这意味着我现在可以 让模型评判开始工作了 来评估 Book Tracker 的 Book Tagging Service 到目前为止 我们已经了解了 如何对提示词进行爬坡优化 让它们逐步 越来越好 现在我想展示给你看 如何改进你的功能 通过提示词之外的方式 来实现提升 为了生成标签 Book Tracker 使用了 设备端模型 我们使用它是因为读者在 整理书籍时常处于各种环境 使用设备端模型可以确保 他们能够生成标签 无论身处何地 我想做的是为模型提供 更多关于书籍的上下文 用于生成标签 我认为额外的上下文将帮助 模型生成更相关 且更有用的标签 更好的是 Book Tracker 已经拥有所需的数据 因为我们存储了作者姓名 和书名 在用户 写评价时就会记录 因此 为了帮助标签生成器 我创建了一个工具 来获取书籍的更多信息 它可以在信息可用时 提供书名和作者 添加这个工具 是一种爬坡优化 因为我们在尝试通过 渐进式改变来提升 功能的质量 对于这次评估 我们将使用 书籍标签评估 现在配合了改进后的 模型评判 但我需要一种方法来比较 我的功能不带工具时 与带工具时 的质量差异 为此 我需要对 Book Tagging Service 做修改 BookTaggingService 现在 接受工具列表作为输入 我还将默认值设为空数组 这样我现有的评估 就不需要做任何改动 但现在我需要编写 一个新的评估 来比较带工具和不带工具时 服务的表现差异 这是我编写的新评估 它与另一个评估 完全相同 唯一区别是我现在将 新的查找工具传入工具数组 所以我只需定义 两个评估实例 一个不带工具 一个带工具 现在 我们来评估它 并判断是否可以发布 我使用工具的服务 满足了所有预期 情况看起来不错 但是 我的 Book Tracker 数据集 只包含13对书籍和评价 这并不能覆盖 各种各样的书籍和评价 用户可能提交的内容 此外 我在查看我的服务评估 结果时发现 带工具的情况 我能看出带工具的服务 表现更好 但看起来我的工具 并没有在所有我认为 需要的地方被调用 我真正需要的是一种方法 来判断 我的工具是否在 正确的情况下被调用 幸运的是 Evaluations 框架 可以帮助解决这两个问题 要了解我们的 API 用于评估工具使用情况 以及生成全面数据集的方法 请参阅《为智能体 App 构建 稳健评估》视频 你将了解工具调用 Evaluators 的相关内容 以及如何使用 Sample Generator API 测试你的 App 可能 遇到的各种用例 在结束之前 我想 回顾一下今天的内容 爬坡法最有效的方式 是每次只做一处改动 为此 请将循环的 每次迭代视为科学实验 能够隔离你的改动 有助于你理解 功能的每个部分 对整体质量的贡献 了解每个部分的 单独工作原理 也有助于你判断 在哪些地方需要改动 以解决后续出现的 缺陷或不良模式
其次 这个过程 需要时间 并非每次改动 都能带来正向变化 但失败的实验与 成功的实验同样有价值 第三 好的实验 需要创造力 在智能功能中 有很多可以改变的地方 在你的功能中 你可以更改 指令 工具 以及用于生成响应 的一个或多个模型 在评估方面 你可以更改数据集 聚合方法 甚至可以更改 Evaluators 本身 一切皆可尝试 在考虑如何爬坡时 请务必考虑 所有这些因素 最后 要注意漂移 评估你的 Evaluators 可能感觉有点绕 但经过良好调优的 模型 Evaluator 能为你节省长期时间 模型生成评分的速度 远快于人工评分 因此通过保持对齐 随着数据集扩展覆盖 更多用例 你能获得 有用的信号 如果你想进一步了解 今天所涵盖的内容 可以查看我一直在使用 的 Book Tracker App 以及用于对齐 模型评判器的评估内容 你还可以获取关于 所有新 API 的完整说明 请访问开发者文档网站 感谢你花时间学习 如何通过爬坡法 提升你的评估分数 你的付出终将得到回报 你将为用户带来 高质量的体验 感谢收看 祝爬坡愉快
-
-
3:54 - The BookTaggingEvaluation
// MARK: - Evaluation struct BookTaggingEvaluation: Evaluation { func subject(from sample: ModelSample<BookTags>) async throws -> ModelSubject<BookTags> { let result = try await BookTaggingService.generateTags(for: sample.promptDescription) return ModelSubject(value: result) } // MARK: - Dataset var dataset = ArrayLoader(samples: Book.sampleBooks.map { book in ModelSample(prompt: book.review, expected: BookTags(tags: book.tags)) } ) // MARK: - Evaluators & Metrics var tagCount = Metric("Tag Count") let hasGenreTag = Metric("Has Genre Tag") let noDuplicates = Metric("No Duplicates") let relevance = ScoreDimension( "Relevance", description: """ Whether each tag describes a quality, theme, or tone of the book itself rather than incidental details or the reader's personal reactions. """, scale: .numeric([ 4: "Every tag describes the book itself", 3: "Most tags describe the book, one picks up a reader reaction or minor detail", 2: "Most tags are surface details or personal reactions, not book descriptors", 1: "Tags don't meaningfully describe the book" ]) ) let usefulness = ScoreDimension( "Usefulness", description: """ Whether tags are at the right granularity for browsing — broad enough that multiple books could share the tag, specific enough to help filter. """, scale: .numeric([ 4: "Every tag could group multiple books while still narrowing a search", 3: "Most tags are at the right level, one is either too broad or too narrow", 2: "Most tags are too broad to filter or too narrow to group", 1: "Tags would not help with browsing" ]) ) var evaluators: Evaluators { // 1. Tag count is within the required 3–8 range Evaluator { _, subject in let count = subject.value.tags.count if (count >= 3 && count <= 8) { return tagCount.passing(rationale: "\(count) tags") } return tagCount.failing(rationale: "Got \(count) tags, expected 3–8") } // 2. At least one tag identifies the genre or literary form Evaluator { _, subject in let tags = subject.value.tags.map { $0.lowercased() } let knownGenres = await BookTaggingService.knownGenres for tag in tags { if knownGenres.contains(tag) { return hasGenreTag.passing(rationale: "Matched \(tag)") } } return hasGenreTag.failing() } // 3. No duplicate tags Evaluator { _, subject in let uniqueCount = Set(subject.value.tags.map { $0.lowercased() }).count if (subject.value.tags.count - uniqueCount) > 0 { return noDuplicates.failing(rationale: "Found \(subject.value.tags.count - uniqueCount) duplicates") } return noDuplicates.passing() } // 4. Overall tag quality — groundedness, coverage, specificity ModelJudgeEvaluator( judge: .default, dimensions: [relevance, usefulness], prompt: ModelJudgePrompt( instructions: """ You are evaluating automatically generated tags for Shelf, a personal book tracking app. Users write a short summary of their reading experience, and the app generates tags to make their library browsable. A good tag describes the book itself — its genre, themes, tone, or setting. A bad tag picks up incidental details or the reader's personal reactions that don't describe the book. """, evaluationTarget: { output in output.tags.joined(separator: ", ") }, reference: { input, _ in ["Expected Tags": input.expected?.tags.joined(separator: ", ") ?? ""] } ) ) } // MARK: - Analysis func aggregateMetrics(using aggregator: inout MetricsAggregator) { aggregator.group("Heuristics") { group in group.computeMean(of: tagCount) group.computeMean(of: hasGenreTag) group.computeMean(of: noDuplicates) } aggregator.group("Quality") { group in group.computeMean(of: relevance.metric) group.computeMean(of: usefulness.metric) } } } -
4:05 - Refined Relevance & Usefulness score dimensions
let relevance = ScoreDimension( "Relevance", description: """ Whether each tag describes the book itself — its genre, themes, tone, or setting — rather than the reader's reactions, meta- commentary about the review, or facts about the author. A book can be "suspenseful" (a property of the text); a reader is "exhausted" (a reaction). Mis-labeling the genre is a serious failure. """, scale: .numeric([ 4: "Every tag describes the book itself", 3: "Most tags describe the book, one picks up a reader reaction or minor detail", 2: "Most tags are surface details or personal reactions, not book descriptors", 1: "Tags don't meaningfully describe the book" ]) ) let usefulness = ScoreDimension( "Usefulness", description: """ Whether tags work as library shelf labels — broad enough that several books could plausibly share the tag, specific enough to meaningfully narrow a search. Standard genre and theme tags work; made-up phrases, character names, hyper-specific descriptors, and overly generic words like "interesting" don't. """, scale: .numeric([ 4: "Every tag could group multiple books while still narrowing a search", 3: "Most tags are at the right level, one is either too broad or too narrow", 2: "Most tags are too broad to filter or too narrow to group", 1: "Tags would not help with browsing" ]) ) -
11:56 - The alignment dataset, extracted to JSON
// Model judge alignment dataset [ { "input": "I have read this book more times than I can count…", "response": "[\"literary-fiction\", \"historical-fiction\", \"family-drama\", \"romantic-drama\", \"character-driven\", \"emotional-intensity\", \"multigenerational-narrative\", \"penned-by-a-woman\"]" } // ... add your expert ratings to each entry ] -
12:31 - The judge alignment evaluation: dataset, subject, evaluator
// Model judge alignment evaluation struct BookTagJudgmentCalibration: Evaluation { // MARK: Dataset — load the extracted summary/tag pairs static let samples: [ModelSample<BookTagJudgmentValue>] = { guard let url = Bundle(for: BundleToken.self).url( forResource: "BookTaggingEvaluation-extracted", withExtension: "json"), let data = try? Data(contentsOf: url) else { return [] } // Build ModelSample array (adding expert ratings) // ... }() var dataset: some Loader { ArrayLoader(samples: Self.samples) } // MARK: Capture Subject — tags are already generated, so just return them func subject(from sample: ModelSample<BookTagJudgmentValue>) async throws -> ModelSubject<BookTagJudgmentValue> { ModelSubject(value: sample.expected ?? BookTagJudgmentValue( tags: [], expertRelevanceScore: 0, expertUsefulnessScore: 0)) } // MARK: Evaluators — the same model judge as the book-tags evaluation var evaluators: Evaluators { ModelJudgeEvaluator( judge: .default, dimensions: [relevance, usefulness], prompt: ModelJudgePrompt( instructions: "You are evaluating automatically generated tags for Book Tracker…", evaluationTarget: { output in output.tags.joined(separator: ", ") }, reference: { input, _ in ["Expected Tags": input.expected?.tags.joined(separator: ", ") ?? ""] } ) ) } } -
13:00 - Cohen's kappa aggregation
func aggregateMetrics(using aggregator: inout MetricsAggregator) { let expertRelevance = Self.samples.map { Double($0.expected?.expertRelevanceScore ?? 0) } let expertUsefulness = Self.samples.map { Double($0.expected?.expertUsefulnessScore ?? 0) } aggregator.group("Relevance") { group in group.computeMean(of: relevance.metric) group.computeStandardDeviation(of: relevance.metric) group.custom(of: relevance.metric, label: "Relevance Alignment Score") { judge in cohensKappa(ratings1: expertRelevance, ratings2: judge) ?? 0 } } aggregator.group("Usefulness") { group in group.computeMean(of: usefulness.metric) group.computeStandardDeviation(of: usefulness.metric) group.custom(of: usefulness.metric, label: "Usefulness Alignment Score") { judge in cohensKappa(ratings1: expertUsefulness, ratings2: judge) ?? 0 } } } -
13:24 - The judge calibration test
// Model judge alignment tests @Suite("Book Tag Judge Calibration") struct BookTagJudgmentCalibrationTests { static let evaluation = BookTagJudgmentCalibration() @Test("Judge Calibration", .evaluates(evaluation)) func evaluateJudgeCalibration() async throws { let result = EvaluationContext.current.result let usefulnessMetric = BookTagJudgmentCalibrationTests.evaluation.usefulness.metric let relevanceMetric = BookTagJudgmentCalibrationTests.evaluation.relevance.metric #expect(result.aggregateValue(.custom(label: "Relevance: Judge vs Expert")) > 0.6) #expect(result.aggregateValue(.custom(label: "Usefulness: Judge vs Expert")) > 0.6) } } -
16:33 - The experimental judge prompt
// Experimental evaluation struct BookTagJudgmentCalibrationExperimental: Evaluation { var evaluators: Evaluators { ModelJudgeEvaluator( judge: .default, dimensions: [relevance, usefulness], prompt: ModelJudgePrompt( instructions: """ You are an experienced reader and librarian evaluating tags automatically generated for Book Tracker... Score the tag set on two independent dimensions: Relevance and Usefulness. ## What a good tag looks like - Genre/form, theme/subject, tone/atmosphere, setting/era ## Common failure modes - Reader reactions, meta-commentary, author facts, genre contradictions """, // ← full prompt is ~40 lines; abbreviated here evaluationTarget: { output in output.tags.joined(separator: ", ") }, reference: { input, _ in ["Book Review": input.promptDescription, "Tags Generated for the Review": input.expected?.tags.joined(separator: ", ") ?? ""] } ) ) } } -
20:12 - Few-shot worked examples in the judge prompt
struct ExperimentalBookTagJudgmentCalibration: Evaluation { var evaluators: Evaluators { ModelJudgeEvaluator( judge: SystemLanguageModel(), dimensions: [relevance, usefulness], prompt: ModelJudgePrompt( instructions: """ You are calibrating with an expert librarian who scores automatically generated tags for Book Tracker... Your goal is to match how the librarian scores. Use the worked examples to calibrate. ## Worked examples ### Example A — clean fit (Pride and Prejudice) Tags: romance, historical-fiction, love, redemption, passion Librarian: Relevance 4, Usefulness 4 ### Example E — flat genre contradiction (Frankenstein) Tags: horror, science-fiction, ... self-help, self-improvement Librarian: Relevance 2, Usefulness 3 ... (6 examples A–F; keep the set small to avoid overfitting) """, // ← full prompt is ~60 lines; abbreviated here evaluationTarget: { output in output.tags.joined(separator: ", ") }, reference: { input, _ in ["Book Review": input.promptDescription, "Tags Generated for the Review": input.expected?.tags.joined(separator: ", ") ?? ""] } ) ) } } 9. The BookLookupTool — slides 166–167 -
22:03 - The BookLookupTool
// Book Information Lookup Tool struct BookLookupTool: Tool { let name = "lookupBook" let description = "Looks up the title and author of a book given distinguishing details — such as character names, settings, quoted lines, or notable plot points — extracted from a reader's review." @Generable struct Arguments { @Guide(description: "Distinguishing details from the review that identify the book, such as character names, settings, quoted lines, or notable plot points.") var details: String } @Generable struct Output { @Guide(description: "The title of the identified book, or an empty string if no match was found.") var title: String @Guide(description: "The author of the identified book, or an empty string if no match was found.") var author: String } func call(arguments: Arguments) async throws -> Output { let needles = arguments.details .lowercased() .split(whereSeparator: { !$0.isLetter && !$0.isNumber }) .map(String.init) .filter { $0.count >= 4 } let best = Book.sampleBooks .map { book -> (book: Book, score: Int) in let review = book.review.lowercased() let score = needles.reduce(0) { partial, needle in partial + (review.contains(needle) ? 1 : 0) } return (book, score) } .max(by: { $0.score < $1.score }) guard let match = best, match.score > 0 else { return Output(title: "", author: "") } return Output(title: match.book.title, author: match.book.author) } } -
22:36 - BookTaggingService with a tools parameter
// Book Tagging Service struct BookTaggingService { static func generateTags(for review: String, tools: [any Tool] = []) async throws -> BookTags { let prompt = tagsPrompt(review: review) let session = LanguageModelSession( model: SystemLanguageModel(guardrails: .permissiveContentTransformations), tools: tools, instructions: instructions ) let response = try await session.respond(to: prompt, generating: BookTags.self) return response.content } } -
22:57 - Evaluation with the lookup tool
// Evaluation of tags with tool struct BookTaggingWithLookupEvaluation: Evaluation { func subject(from sample: ModelSample<BookTags>) async throws -> ModelSubject<BookTags> { let result = try await BookTaggingService.generateTags( for: sample.promptDescription, tools: [BookLookupTool()] ) return ModelSubject(value: result) } // ... same dataset, evaluators, and aggregation as BookTaggingEvaluation } -
23:09 - Compare with/without the tool in one suite
@Suite("Book Tag Evaluations") struct BookTagEvaluationTests { static let evaluation = BookTaggingEvaluation() static let lookupEvaluation = BookTaggingWithLookupEvaluation() @Test("Book Tag Evaluations", .evaluates(evaluation, info: evaluationInfo)) func evaluateBookTagging() async throws { let result = EvaluationContext.current.result let rangeMetric = BookTagEvaluationTests.evaluation.tagCount let dupeMetric = BookTagEvaluationTests.evaluation.noDuplicates #expect(result.aggregateValue(.mean(of: rangeMetric)) >= 0.8) #expect(result.aggregateValue(.mean(of: dupeMetric)) == 1) } @Test("Book Tag Evaluations (with BookLookupTool)", .evaluates(lookupEvaluation, info: lookupEvaluationInfo)) func evaluateBookTaggingWithLookup() async throws { let result = EvaluationContext.current.result let rangeMetric = BookTagEvaluationTests.lookupEvaluation.tagCount let dupeMetric = BookTagEvaluationTests.lookupEvaluation.noDuplicates #expect(result.aggregateValue(.mean(of: rangeMetric)) >= 0.8) #expect(result.aggregateValue(.mean(of: dupeMetric)) == 1) } }
-
-
- 0:00 - Introduction
Hill-climbing — iteratively improving an intelligence feature using evaluation scores as a guide (develop, run, analyze) — framed around bringing scientific thinking to that loop. Assumes you've already built an evaluation pipeline (see "Meet the Evaluations framework").
- 2:42 - BookTracker's tagging problem
Revisits BookTracker, whose tag generator produces tags that miss key themes or reflect the reader's feelings rather than the book. The existing evaluation judges tag quality via score dimensions (Relevance, Usefulness) and a ModelJudgeEvaluator.
- 5:27 - Analyzing the evaluation results
Adds two reviews to the dataset, runs the evaluation (Swift Testing #expect), and uses the Xcode evaluation report and assistant editor to compare generated tags against expected ones, revealing the human and model judge disagree on usefulness.
- 8:26 - Drift between judge and human
That disagreement is drift, the divergence between a model judge's ratings and an expert's. As the dataset grows, drift widens, making it hard to trust the evaluation, so the judge must be aligned to expert opinion.
- 9:37 - Measuring drift with Cohen's kappa
Accuracy alone misleads on unevenly-distributed scores (a high-scoring judge looks aligned by luck). Cohen's kappa coefficient measures true alignment by subtracting the chance of random agreement from accuracy and normalizing, a robust drift metric.
- 12:26 - Building a judge alignment evaluation
Builds an evaluation comparing the presenter's ratings to the judge's over a shared dataset: extract summary/tag pairs from the prior run's attachment, add human ratings, reuse the same ModelJudgeEvaluator as subject, and aggregate Cohen's kappa (plus mean and standard deviation), targeting an alignment of 0.6.
- 15:16 - Analyzing alignment failures
The alignment test fails. Drilling into the report (for example Frankenstein, The Ramakien) shows the judge rating overly-specific or off-theme tags too highly, the judge's prompt lacks the context to tell a good tag from a bad one.
- 17:16 - Comparative evaluation: control vs experimental
Xcode 27 can compare two evaluations like a controlled experiment: a baseline (control) prompt versus an experimental prompt that adds app context plus examples of good and bad tags. Running both shows relevance improved while usefulness dropped, a tradeoff to weigh.
- 19:12 - Refining the scoring dimensions
Keeping the prompt change, the side-by-side comparison view reveals the judge grading usefulness too harshly. Applying the new prompt to the baseline to isolate one variable, the ScoreDimension descriptions are sharpened (emphasizing genre tags; being critical of overly-specific ones), improving both scores.
- 21:23 - Adding few-shot examples to the judge
Still short of the goal, the judge prompt is grounded with the feature's purpose and a few worked examples of how the presenter rates, deliberately few to avoid overfitting the alignment score. Scores finally exceed expectations, so the judge is trusted and the loop exits.
- 23:38 - Going beyond prompts: adding a tool
Hill-climbing isn't only prompts: to give the on-device tag model more context, a BookLookupTool supplies the title and author. BookTaggingService gains a tools parameter (defaulting empty), and a second evaluation compares the feature with versus without the tool, the tool version scores better, though the small 13-sample dataset and unobserved tool calls point to "Create robust evaluations for agentic apps."
- 27:17 - Next steps
Think like a scientist (one change at a time), invest the time (failed experiments still inform), be creative (instructions, tools, models, datasets, aggregations, and evaluators are all fair game), and watch for drift. Download the Book Tracker sample and review the documentation.