《AI Agent 工程实战》1.1 需求与用例:3 个核心用例与验收标准

本节为 ResearchCopilot 立下 3 个可验收的核心用例:单跳事实问答、多跳研究并输出带引用报告、后台周期 digest。每个用例都给出量化验收标准与落地章节映射,并明确列出不做通用聊天、不做 MCP Server 实现、不做多租户计费这三条边界,把项目范围钉死在可交付的一小块上。

1.1 需求与用例:3 个核心用例与验收标准

卷一结束时,我们手里有一个能跑的 Agent 引擎 minagent:消息协议、工具注册、朴素循环、停止条件、事件流、会话与检查点,全都写通了。但一个引擎不等于一个产品。引擎回答的是「怎么让模型和工具循环起来」,产品要回答的是「谁在什么时候用它、它必须做到什么程度才算合格」。这一节是《AI Agent 工程实战》的起点,我们把这两个问题用用例 + 验收标准的方式钉死,后面 17 章都围绕它展开。

本节把 ResearchCopilot 推进到「有明确范围」:定下 3 个核心用例、给每个用例写可判真假的验收标准、划出明确不做的三件事,并给出用例到后续章节的映射表。

1.1.1 为什么先写用例,而不是先写代码

工程实战最常见的失败不是「代码写不出来」,而是「写了一大堆,最后说不清到底做完了没有」。一个典型的对话是这样的:

  • 产品:「做个 AI 研究助手。」
  • 工程:「好的,支持多轮对话、联网搜索、写报告。」
  • 三周后:「效果怎么样?」——「感觉还行。」

「感觉还行」不是验收标准,它是一个无法判真假的句子。它导致两个后果:一是范围失控,每加一个功能都「顺手」,项目永远收不了尾;二是无法回归,改了一处代码,没人知道是变好了还是变差了。

用例(use case)与验收标准(acceptance criteria)解决的就是这两个问题。用例回答「谁、在什么场景、要达成什么目标」;验收标准回答「怎么算达成」。两者合起来,把一句模糊的「做个研究助手」翻译成一组可以写进测试的断言。

这也正是卷一反复强调的「确定性」在工程层的延续:卷一把模型行为抽成可断言的 FakeModel,本卷把需求抽成可断言的验收标准。前者让循环可回归,后者让产品可回归。

1.1.2 三个核心用例

ResearchCopilot 是一个「研究助理」:用户提问,Agent 多跳检索本地语料、调用工具(含本地 Postgres 查询)、生成带引用的报告;后台 worker 做周期 digest。我们只做三件事,每一件都对应一类真实用户动作。

编号用例一句话目标谁在用
UC1单跳事实问答一次检索就能回答的确定性问题日常使用者
UC2多跳研究并输出报告需要跨多份材料综合的研究问题研究员 / 分析师
UC3后台周期 digest定时把新增语料汇总成摘要系统自身

三者的关系是递进的:UC1 是最短路径,验证「检索 + 回答」这条主链;UC2 在 UC1 之上加多跳、加工具、加报告组装;UC3 把 UC2 的能力从「用户触发」搬到「定时触发」。先做 UC1 打底,UC2 才有地基,UC3 才有可复用的报告生成器。

1.1.3 每个用例的输入、输出与失败模式

一个用例要能被实现,光有一句话目标还不够,还得写清三件事:输入长什么样、输出长什么样、什么情况算失败。三者写全,用例才算「可交付」——因为失败模式直接决定了要写哪些护栏(第 15 章)。

UC1 单跳事实问答

项内容
输入一个自然语言问题,例如「ResearchCopilot 用的 Postgres 是哪个版本?」
输出一段简短回答 + 至少 1 条 chunk 级引用
成功答案正确,且引用能回溯到语料里的真实片段
失败模式① 语料里根本没有,却编了个答案(幻觉);② 答对了却没给引用;③ 引用指向不存在的 chunk

UC1 是三条里最短的路径,但它的失败模式最典型:幻觉。注意失败模式 ① 和 ③ 的区别——前者是「内容错」,后者是「引用错」。很多系统只防前者,结果回答是对的、引用却是编的,用户一查就露馅。所以 UC1 的验收里「引用必须可回溯」和「答案必须正确」是两条独立标准。

UC2 多跳研究并输出带引用报告

项内容
输入一个需要综合多份材料的研究问题,例如「对比 A、B 两个方案在延迟上的取舍」
输出一份结构化报告:结论 + 分点论证 + 每点的引用
成功报告里的每条论断都能回链到引用;总步数不超过预算
失败模式① 只查了一次就下结论(退化成 UC1);② 报告有论断没引用;③ 步数爆预算被 max_steps 截断

UC2 的关键失败模式是 ①——退化成 UC1。模型偷懒只检索一次就写报告,表面看「有输出」,实际没做到「多跳」。所以 UC2 的验收里有一条硬指标「至少 2 次检索」,它是用来逼出多跳的,而不是走过场。这也正说明验收标准为什么要量化:如果只写「要做多跳研究」,模型退化成单跳时你根本发现不了。

UC3 后台周期 digest

项内容
输入无用户输入,由定时器触发;隐含输入是「近 24 小时新增语料」
输出一份摘要 + 落库的 digest 记录
成功按时触发、覆盖窗口完整、重复触发不产生重复 digest
失败模式① 漏触发(定时器挂掉);② 重复触发导致同一窗口两份 digest;③ 生成失败后无重试

UC3 和前两个最大的不同是没有用户盯着。前两个用例出错,用户当场能看到;UC3 出错是静默的——digest 没生成,没人知道。所以它的验收标准里「成功率 ≥ 99%」和「失败可重试 3 次」不是锦上添花,而是唯一的观测手段:没有这两条,你连「digest 是不是在工作」都答不上来。

三个用例的失败模式汇总起来,正好对应第 15 章要建的三类护栏:

用例主要失败模式对应护栏
UC1幻觉、引用造假输出护栏(15.2)
UC2退化单跳、无引用评测门禁(12)、输出护栏(15.2)
UC3静默漏触发、重复可观测 + 告警(13)

这张表说明了「失败模式」为什么必须写进需求:它直接告诉你后面要建什么护栏。需求阶段偷懒不写,护栏阶段就会漏项。

1.1.4 验收标准必须能被判真假

给每个用例写验收标准时,规则只有一条:每一条标准都必须带一个可判定的阈值或明确的布尔条件。凡是「效果好」「响应快」「尽量准确」这类词,一律不允许进入标准列表。

把这条规则写成代码,比写在文档里更管用——它可以在 CI 里跑,防止有人在需求评审时又塞进一条模糊标准:

from __future__ import annotations

from dataclasses import dataclass, field


@dataclass
class UseCase:
    uid: str
    name: str
    criteria: list[str]
    chapters: list[str] = field(default_factory=list)


def is_verifiable(text: str) -> bool:
    """验收标准必须带可判定的阈值(数字)或明确的布尔条件。"""
    return any(ch.isdigit() for ch in text)


CASES: list[UseCase] = [
    UseCase(
        uid="UC1",
        name="单跳事实问答",
        criteria=[
            "命中语料时首字延迟 P50 < 1.2s、P95 < 3.0s",
            "每条回答至少带 1 条 chunk 级引用",
            "语料无命中时 100% 回复固定文案,编造率 0",
        ],
        chapters=["3.1", "3.2", "6.1", "15.2"],
    ),
    UseCase(
        uid="UC2",
        name="多跳研究并输出带引用报告",
        criteria=[
            "一次 run 至少触发 2 次检索、1 次 Postgres 查询",
            "报告内每条论断都能回链到 >= 1 条引用",
            "端到端 P95 < 45s,失败率 < 2%",
        ],
        chapters=["3.1", "5.3", "8.1", "11.2", "15.2"],
    ),
    UseCase(
        uid="UC3",
        name="后台周期 digest",
        criteria=[
            "每 6 小时触发一次,单次覆盖近 24 小时新增语料",
            "digest 生成成功率 >= 99%,失败可重试 3 次",
            "同一窗口重复触发 0 次产生重复 digest(幂等键去重)",
        ],
        chapters=["7.3", "7.2", "6.3"],
    ),
]

for c in CASES:
    bad = [t for t in c.criteria if not is_verifiable(t)]
    print(f"{c.uid} {c.name}: {len(c.criteria)} 条验收标准, 不可判定 {len(bad)}, 章节 {','.join(c.chapters)}")
    assert not bad, f"{c.uid} 存在不可判定的标准: {bad}"
print("全部用例的验收标准均可判定:通过")

用 venv 里的 Python 3.14.6 跑出来:

UC1 单跳事实问答: 3 条验收标准, 不可判定 0, 章节 3.1,3.2,6.1,15.2
UC2 多跳研究并输出带引用报告: 3 条验收标准, 不可判定 0, 章节 3.1,5.3,8.1,11.2,15.2
UC3 后台周期 digest: 3 条验收标准, 不可判定 0, 章节 7.3,7.2,6.3
全部用例的验收标准均可判定:通过

这个 is_verifiable 检查有个真实的副作用:它一开始确实把 UC1 的「语料无命中时回复固定文案,不编造事实」判成了不可判定——因为那句话里一个数字都没有。这不是检查太严,而是那句话本来就不够硬:什么叫「不编造」?后来改成「编造率 0」,它才变成一条能写进评测集的指标。检查器逼着我们把话说清楚,这正是它存在的意义。

1.1.5 明确不做:三条边界

范围失控的根源往往不是「加了什么」,而是「没说清不加什么」。ResearchCopilot 明确不做三件事,遇到诱惑时直接引用这一节。

不做为什么不做想要的人该去哪
通用聊天聊天是另一个产品,会和「带引用研究」的目标打架自己接模型 SDK 即可
MCP Server 实现本卷只做 MCP 的 client(把 MCP 当工具源)站内 /posts/mcp/ 专题
多租户计费计费是商业系统,和 Agent 工程正交独立的账单服务

第一条尤其重要。一个「既能闲聊、又能查资料」的助手,会在提示词、上下文预算、评测口径上同时承受两套需求的拉扯,最后两边都做不好。先做窄,再做深,是这一卷贯穿始终的取舍。

第二条的边界来自分工:本卷第 10.1 节只写「作 client 把 MCP 当工具源」,协议规范、Server 实现、传输与 OAuth 一律不碰,需要时直接链到 /posts/mcp/。第三条的边界来自正交性:租户隔离我们做(第 4.2 节),但计费不做,因为它是账单系统的事,不是 Agent 服务的事。

1.1.6 用例到章节的映射

定完用例和边界,最后一步是把它们落到章节。这张表是全书的路标:读到任何一章,都能知道它在实现哪个用例的哪一部分。

用例主要实现章节涉及的横切能力
UC1 单跳问答3.1 REST、3.2 SSE、6.1 Redis 缓存、15.2 输出护栏配置(2)、鉴权(4)、评测(12)
UC2 多跳报告3.1 REST、5.3 查询优化、8.1 引擎并发、11.2 记忆注入、15.2 输出护栏队列(7)、可观测(13)、成本(14)
UC3 周期 digest7.3 定时任务、7.2 重试死信、6.3 限流配额存储(5)、发布(16/17)
三者共用1.3 仓库与 CI、2.1 分层配置、10.1 MCP 客户端容器化(16)、CI/CD(17)

读表的方法:竖着看是一个用例从接口到护栏的完整链路;横着看是一类横切能力(配置、鉴权、评测……)如何服务所有用例。本卷的章节顺序大致按「先立骨架(1–4)、再补数据与队列(5–8)、再做前端与 MCP(9–10)、然后记忆与评测(11–12)、最后可观测/成本/安全/发布(13–18)」推进,这张表就是那条推进线的另一种读法。

1.1.7 反例:哪些请求不该被受理

「不做什么」划的是功能边界,这里再划一条运行时边界:有些请求虽然形式上落在三个用例里,却应该被明确拒绝或降级,而不是硬着头皮处理。把这些反例写出来,能避免后面在 API 层(第 3 章)反复纠结。

请求处理方式理由
空问题 / 纯空白400 拒绝无信息量,浪费一次 run
超过 4000 字符的问题拒绝并提示拆分上下文预算(第 2 章)扛不住
要求「帮我写代码 / 翻译」拒绝,回固定文案超出「研究助理」范围
问语料里没有的事回复「未找到」,不给编造UC1 失败模式 ① 的护栏
同一幂等键重复提交返回既有 run_id避免重复计费与重复跑

最后一行是幂等:用户网络抖动重发同一个请求,不该跑两次。这条在第 3.3 节会用一个幂等键实现。前四行则属于「输入护栏」,第 15.1 节会讲怎么在入口处拦下提示注入与越界请求。

这些反例的意义在于:它们让「拒绝」变成需求的一部分,而不是 bug。一个从不拒绝的助手,会在每一条反例上消耗真实的 token 与算力;而明确写下的拒绝规则,本身就是省钱的护栏。

1.1.8 真实环境下的验收会怎样

上面 9 条标准都是「可判定」的,但可判定不等于好测。真实环境里,它们会分成三类:

  • 能直接写成断言的:比如「至少 2 次检索」「重试 3 次」。这些用卷一的 FakeModel + 录制夹具就能在 CI 里跑,第 12 章会搭这套评测集。
  • 要压测才能测的:比如「P95 < 45s」「失败率 < 2%」。这些要真跑并发,第 14.3 节的吞吐压测正是为此。
  • 要人工/评测模型判的:比如「每条论断都能回链到引用」。这类要用评分器,第 12.1 节会给出具体做法。

所以验收标准不是「写下来就完事」,它决定了后面要建哪些测试设施。这也是为什么把用例放在第 1 章:它决定了整卷的技术栈要往哪投。

把三类标准放一起看,会发现一个规律:越靠近「用户能感知的指标」(延迟、失败率),越依赖真跑;越靠近「内容质量」(引用是否成立),越依赖评测。前者要压测机与并发夹具,后者要评分器与评测集。本卷把评测(第 12 章)和压测(第 14.3 节)都放在后半程,正是因为它们是前 11 章攒下来的验收标准的「兑现工具」——需求先立,工具后到。

还有一点值得提醒:验收标准是会被修改的。跑起来之后你会发现「P95 < 45s」太松或太紧,这时候应该改标准,而不是假装它一直是对的。但无论怎么改,规则不变——改完的标准仍然必须可判定。这正是把 is_verifiable 写成代码的价值:标准可以变,标准「必须可判定」这条纪律不能变。

本节的自检清单:

  • 3 个用例都能用一句话说清目标,且互不重叠
  • 9 条验收标准每条都带数字或布尔条件(is_verifiable 全过)
  • 三条「不做」边界写明,且给出了替代去处
  • 每个用例都能在映射表里找到落地的章节
  • 每个用例的输入 / 输出 / 失败模式都已写明
  • 反例请求的处理方式(拒绝 / 降级 / 幂等)已明确

需求定完,下一步是把它翻译成结构。下一节我们画 ResearchCopilot 的四层架构,说明引擎、服务、worker、存储各自管什么、不该管什么。想回看引擎契约本身,翻卷一的 3.1 朴素循环 。

阅读导航:上一节:目录 · 下一节:1.2 架构总览:引擎 / 服务 / worker / 存储边界 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「agent」更多文章

  1. 《AI Agent 高级编程》目录
  2. 《AI Agent 高级编程》11.3 全景回顾:从零到框架的完整路径与展望
  3. 《AI Agent 高级编程》11.2 真实 API 冒烟:假模型 vs 真模型量化差距