用 Python 构建 LLM 功能:结构化输出、工具调用与评测 · 第 02 题

第 2 课:把模型输出写成契约

检查点 l0210 分自我报告——认领码来自 verify.py任一编码 Agent + 录制好的模型回放(无需 API Key)

提示词负责请求,契约负责检查。把 TASK.md 当成你的代码与模型之间的边界来读——回复必须长什么样、能运行哪些工具、不照做时怎么办。

在用 Agent 做题?一句话开始整门课程:

Read https://flypython.com/skills/flypython/SKILL.md and start the FlyPython course `llm-features-in-python`.

第 2 课:把模型输出写成契约

目标

你能把 TASK.md 当成一组关于模型回复的可测试陈述来读,把每条陈述追查到 tests/test_ticket_triage.py 里的一个测试,并解释契约替你做的三个决定: 什么是合法回复、哪些工具调用可以运行、模型不照做时怎么办。

为什么有这节课

打开 starter 里的 build_messages。它已经把确切的 JSON 字段、允许的分类、 优先级范围、怎样调用工具都告诉了模型。很多团队到此为止——提示词说了我们 要什么,回复就会是我们要的。提示词是请求。契约是请求被忽略时你的代码 强制执行的东西,而测试是你确认它真的强制执行了的方法。

厂商 API 往往提供 JSON 或受 schema 约束的输出模式。用上它能减少格式错误 的回复;但它不会告诉你分类对不对、工具参数能不能放心执行、评测是否诚实。 本课程的契约照样适用。

本课内容

把测试并排打开,读 TASK.md。契约做了三个决定:

1. 什么是合法回复。 恰好四个字段,每个都有类型和范围。其他一律拒绝, 错误信息点名字段。

契约行测试
「去掉……外面包着的一层 Markdown 代码围栏」test_parse_strips_a_markdown_fence
「category 属于 categories」test_parse_rejects_an_unknown_category_naming_the_field
「priority 是 1 到 4 的 int(bool 和 float 都拒绝)」test_parse_rejects_bad_priorities
「缺字段或多字段都抛出点名该字段的 TriageError」test_parse_rejects_missing_extra_and_mistyped_fields

为什么要点名字段?因为第 3 课会把错误信息回传给模型。「回复无效」没给它 任何可改的依据;「category must be one of billing, shipping, …; got ‘login’」 准确告诉它该改哪里。

2. 哪些工具调用可以运行。 tools 字典就是白名单。未登记的名字在任何 东西运行之前就被拒绝;参数在调用函数之前就按声明的名字和类型检查。 shop-support 里那条录好的 {"tool": "issue_refund", …} 回复是要记住的 例子:一个能点名任意函数的模型,能让你的代码做那个函数能做的任何事。

3. 失败时怎么办。 带着错误信息重试一次,然后兜底:把工单交给人、不给 分类。兜底绝不猜——一个自信但错误的分类,比一句诚实的「需要人来看」更糟。

练习

从你熟悉的项目里拿一个模型调用——或者编一个——用同样的形状写出它的输出 契约:确切的字段及其类型和范围、它可以调用的工具及参数类型、失败策略 (重试几次、兜底返回什么)。然后给每句话打标:测试套件不看你的心思能 断言它吗?

检查点

能回答下面三题时,用 python verify.py --attest l02 认领本检查点:

  1. build_messages 已经说明了格式。为什么 parse_triage 还要再检查?
  2. 为什么被拒绝的工具调用绝不能到达工具函数——issue_refund 本来可能 做什么?
  3. 为什么兜底返回 category: None,而不是最可能的那个分类?

预期证据

你起草的输出契约,每句都标明可测或不可测。

提示

卡住了?逐条打开。

提示 1 —— 本检查点考察什么

你能把 TASK.md 当成可测试陈述来读、把每条追查到一个测试——并说清为什么 build_messages 里的格式要求单靠它自己不够。

提示 2 —— 提示词是请求,不是保证

build_messages 把确切格式告诉了模型。模型通常会照做。「通常」正是 parse_triage 存在的原因。

提示 3 —— 工具调用就是模型在决定你的代码运行什么

工具注册表是白名单,参数是不可信输入——和你对待网页表单的规则一样。

提交

做完了?记录它。

提交 python verify.py 为本检查点打印的 8 位认领码。自报关卡要先完成课后问题,再运行 python verify.py --attest l02。

你浏览器

提交认领码

你的 Agent自动提交

让 Agent 解题并自动提交

把这题交给它(悬停出现复制按钮)——解题、验证、提交全程自动:

请完成 FlyPython 挑战课程「用 Python 构建 LLM 功能:结构化输出、工具调用与评测」(course id course-llm-features)的检查点 l02。
机读简报:https://flypython.com/api/challenges/llm-features-in-python
打开课程文件夹,先读 TASK.md——它就是契约。
规则:最小变更、不新增依赖、绝不修改 tests/ 或 solution/。
用 python verify.py 检查 starter/;自报关卡先让我回答课后问题,再运行 --attest ID 取得码。
目标:只做检查点 l02 这一道题,完成后立即提交。

用你Agent 页生成的令牌(环境变量 FLYPYTHON_TOKEN),它可以直接记录结果:

curl -X POST https://flypython.com/api/claims \
  -H "Authorization: Bearer $FLYPYTHON_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"claims":[{"course":"llm-features-in-python","checkpoint":"l02","code":"<8-char code>"}]}'

或一次性安装完整的 FlyPython Agent Skill,连粘贴都省了。

文件

starter 与验证器

解题在你的机器上运行——Agent 通过 /api/challenges/llm-features-in-python/files 自己取回课程文件,你无需手动下载。想直接读源码?在 GitHub 上查看文件夹 ↗