为什么单独开这一篇:前面 18 篇案例都在「已有明确任务」的前提下谈架构、测试、度量;但真实项目的第一道坎是——需求根本没说清楚,也没法验收。本篇处理的正是这个最上游的环节——把「自然语言需求 → 结构化规格 → 验收标准」这条链路完整走一遍。

阅读路径:§1 知识地图,§2–§3 讲清「智能软件需求为什么更难、怎么分类」,§4–§5 是核心方法(提示词转规格、AC 可验证性),§6 治理规格漂移,§7 是可运行的抽取 + 校验 + drift 比对代码,§8 度量与结果,§9 复现清单。


1. 案例覆盖的知识要点

按关联强度(●核心 / ◐强关联 / ○可延伸)给出落点:

知识主题本案例对应的技术点关联度
智能软件需求工程功能 / 非功能之外,新增「模型能力 / 数据 / 交互护栏」三层需求●
智能软件需求工程把结构化提示词还原成一份编号 SRS(FR / NFR / AC / 负面清单)●
智能软件需求工程歧义 / 缺失 / 无法客观验证三类缺陷的显式标注与消解●
智能软件需求工程需求 / 模型 / 数据三侧漂移(drift)的定义、检测信号与治理流程◐
智能软件测试(pr05)每条 AC 必须映射到至少一个可执行判据(含无真值时的属性 / 阈值判据)◐
版本管理(pr15)SRS 版本化 + 漂移 diff,作为回归门禁的输入◐
实证研究(pr14)需求覆盖率 / 可验证率 / 漂移频率作为过程指标○
Agent 构造(pr03)Agent 的目标函数与护栏,本质来自 SRS 的 FR / 负面清单○

一句话结论:核心落点是需求工程,向下承接 pr02 的「提示词即规格」,向上为 pr05(测试)、pr15(版本)、pr14(度量)提供可验证的需求基线。没有这一环,后面的测试与度量都在「验收一个没定义清楚的东西」。


2. 智能软件的需求工程,难在哪三处

把「需求没写清」这件事在智能软件里放大,是因为它同时踩中三个结构性难点。这不是「多写点文档」能糊过去的,得在方法论上承认并处理:

①
无唯一真值

同一输入,模型可能给出多个「都算对」的输出。传统「输入 x ⇒ 期望输出 y」的断言失效,需求必须改写成属性 / 约束 / 阈值形式才可验证。

②
需求藏在 Prompt 里

自然语言提示词事实上承担了需求规格,但它天生有歧义、有遗漏、有不可测的形容词。需求工程要做的第一件事,是把它显式结构化。

③
三要素同时漂移

需求文本、模型权重、训练/检索数据各自独立演化。任一处改动都可能让「当初能验收的规格」今天悄悄失效——这就是规格漂移。

需求文本 / Prompt人写的规格会改 模型权重版本换 / 量化 / 微调 数据 / 知识分布语料、检索库漂移 实际能力边界三者共同决定 验收结论AC 是否仍成立 规格漂移:任一支变化 ⇒ 实际能力移动 ⇒ 老 AC 可能失效
图 2-1 · 需求可验证性由「文本 × 模型 × 数据」三侧共同支撑,任一漂移都会撼动验收结论

关键视角:把需求当成跨三个版本轴成立的断言,而不是一次写完的文档。这条视角直接决定了 §6 的漂移治理为什么必须做。


3. 需求分类:功能 / 非功能,再加三层

经典软件工程把需求分成功能需求(FR,系统「做什么」)与非功能需求(NFR,「做到什么程度」:性能、可靠性、安全、可维护)。智能软件在此之上要显式补三层,否则验收无门:

层说明典型条目可验证形态
FR 功能需求系统对用户/环境做什么能总结工单、能生成标题输入→可观测行为
NFR 非功能需求质量属性约束延迟 P95≤2s、成本≤0.01$/次阈值断言
MC 模型能力需求对模型本体能力的期望支持中英、上下文≥8k、可函数调用能力探针 / 基准
DR 数据需求输入数据/知识域的约束覆盖类目 A/B/C、时效≤30天分布/覆盖率检查
GD 交互与护栏需求行为边界、禁止项、降级策略不得编造来源、越权须拒答负面清单 + 拒答率

把一段口语需求按这五层拆一遍,通常第一次就会暴露「说不清」的地方——而这正是需求工程的价值:越早暴露歧义,返工越便宜。

与 pr02 的衔接:pr02 §4 把视频提示词映射成了「功能 / 非功能 / 时间片验收 / 负面清单」。本篇是把它一般化成一套可复用模板,任何自然语言需求都能套。


4. Prompt as Specification:把提示词还原成 SRS

本案例的原始需求,是一段产品经理写在群里、含糊又自信的口语(贴近真实):

原始需求(自然语言)
我们要做一个客服工单助手:把用户提交的问题自动归类到「退款 / 物流 / 账号 / 其他」,
给出一个简短的回复初稿,语气要友好专业。尽量快一点,别让人等。
遇到投诉或说要投诉到监管的,转人工。不要瞎编补偿金额。

需求工程的第一步,是把这段话拆成带编号、可追溯的规格条目,并当场标注每条的可验证性(能测 / 有歧义 / 缺失 / 无法客观验证):

编号层规格化表述可验证性
FR-1FR将工单归入 {退款, 物流, 账号, 其他} 之一,输出单标签可测(有对照集)
FR-2FR生成回复初稿文本,长度 ≤ 3 句可测(长度可数)
NFR-1NFR端到端延迟 P95 ≤ 2 s可测(阈值)
NFR-2NFR「尽量快一点,别让人等」歧义 → 需量化
GD-1GD命中「投诉 / 监管」等词 ⇒ 转人工,不得自动回复可测(触发+动作)
GD-2GD禁止输出未在知识库中的具体补偿金额需定义「瞎编」判据
MC-1MC语气「友好专业」无法客观验证 → 转量表
DR-1DR(未提及语言/时效覆盖)缺失 → 需补

核心手法:遇到 快一点 友好 别瞎编 这类形容词需求,不要照抄进 SRS,而是逼出一个可执行判据:要么量化成阈值(NFR-2 → P95≤2s),要么转成量表 + 双人一致性(MC-1 → 0–5 李克特 + Kappa,接 pr05 / pr09),要么定义成负面清单 + 拒答率(GD-2)。能被判据锚定的需求才是需求,否则只是愿望。

4.1 结构化 SRS 的机器可读形态

为了后续能自动化校验与 diff,规格条目落成一个 srs.json,每条带 id / layer / text / verifiable / acceptance 字段:

JSON
{
  "version": "v0.1",
  "requirements": [
    {"id":"FR-1","layer":"FR","text":"工单归入四类之一,单标签",
     "verifiable":true,
     "acceptance":{"type":"classification","metric":"accuracy","threshold":0.90,"dataset":"tickets-dev"}},
    {"id":"NFR-1","layer":"NFR","text":"端到端延迟 P95 ≤ 2s",
     "verifiable":true,
     "acceptance":{"type":"threshold","metric":"p95_latency_s","threshold":2.0}},
    {"id":"GD-2","layer":"GD","text":"禁止编造未授权补偿金额",
     "verifiable":false,
     "acceptance":{"type":"negative_checklist","judge":"llm","needs_defn":"补偿金额须能在知识库溯源"}}
  ]
}

verifiable=false 或带 needs_defn 的条目,会在 §5 的校验脚本里被自动揪出来当作「待消解需求」——把「说不清」变成一张可管理的清单,而不是埋进代码里的雷。


5. 验收标准(AC):无唯一真值时怎么写才能测

AC(Acceptance Criteria)是连接「需求」与「测试」的桥。传统写法 Given–When–Then 假定有确定期望值;概率输出场景要把它改造成四种可验证模板:

模板何时用写法示意下游
阈值型可量化指标准确率≥0.90;P95≤2s直接断言
属性型无唯一真值但有不变量「输出标签 ∈ 四类」「回复不含手机号」蜕变/属性测试(pr05)
抽样型语义质量随机 50 条人工/Judge 评分均分≥4LLM-as-a-Judge + Kappa(pr05/09)
负面型护栏、禁止项命中投诉词必转人工,违反率=0对抗样本回归(pr07)

可验证性判定规则(给读者的一把尺):一条 AC 只有满足「存在一个判据,能在有限成本内产出 通过 / 不通过」才算成立。否则它必须被改写,直到成立、或被显式标注为「本期不可验证(接受风险)」并写入决策记录。

Python · 给定 srs.json,自动给出可验证率与待办
import json, sys

srs = json.load(open(sys.argv[1], encoding="utf-8"))
reqs = srs["requirements"]

def ac_ok(r):
    a = r.get("acceptance") or {}
    if not r.get("verifiable"):        # 显式标了不可验证
        return False
    if a.get("type") == "negative_checklist" and "needs_defn" in a:
        return False                    # 负面清单但判据没定义完
    return bool(a)                      # 有 acceptance 且判据齐全才算锚定

pending = [r["id"] for r in reqs if not ac_ok(r)]
rate = (len(reqs) - len(pending)) / max(len(reqs), 1)
print(f"需求总数={len(reqs)}  可验证率={rate:.0%}")
print("待消解(需量化 / 补判据 / 记录风险):", pending)

把「可验证率」当成本篇实操的交付指标:不是「我写完了需求」,而是「我写出了 N 条需求,其中 X% 已经能直接转成测试」。


6. 规格漂移(drift):检测信号与治理闭环

即便 SRS 一开始完全可验证,它也会随时间失效。漂移有三个来源,对应三个可监测信号:

漂移源诱因可监测信号治理动作
需求侧产品改口 / 隐含假设被推翻srs.json diff(条目增删改)变更评审 + 版本号 +0.1
模型侧换模型 / 量化 / 微调(pr11)固定 AC 探针集上的分数变化回归门禁:跌破阈值即阻断
数据侧语料 / 检索库分布变化(pr20 RAG)输入画像漂移(类目占比、长度、时效)触发再评测 + 更新 AC 阈值
SRS 基线v0.1 + AC 探针 每次改动模型/数据/需求 跑 AC 探针diff + 回归评测 通过 → 放行 漂移 → 阻断重定基线/改AC 回写基线
图 6-1 · 漂移治理闭环:把 SRS 基线 + AC 探针变成 CI 里的一道回归门禁(与 pr15 兼容矩阵同构)
Python · 最小漂移检测:比对两版 srs + 探针分数
import json

def load_spec(p):
    s = json.load(open(p, encoding="utf-8"))
    return {r["id"]: r["text"] for r in s["requirements"]}, s["version"]

base, vb = load_spec("srs_v0.1.json")
cur,  vc = load_spec("srs_v0.2.json")

added   = cur.keys - base.keys
removed = base.keys - cur.keys
changed = {k for k in base.keys & cur.keys if base[k] != cur[k]}

print(f"版本 {vb} -> {vc}")
print(f"新增 {sorted(added)}  删除 {sorted(removed)}  修改 {sorted(changed)}")

# 需求侧漂移 ⇒ 提示重跑 AC 探针;探针分数跌破基线 ⇒ 模型/数据侧漂移
def gate(probe_scores, baseline, tol=0.03):
    regressed = [k for k in baseline
                 if probe_scores.get(k, 0) < baseline[k] - tol]
    return (not regressed), regressed

ok, bad = gate({"FR-1": 0.86, "NFR-1": 0.97},   # 本次探针
               {"FR-1": 0.90, "NFR-1": 0.97})    # 基线
print("门禁:", "放行" if ok else f"阻断,回归项={bad}")
# -> 阻断,回归项=['FR-1']:分类准确率跌 4%,触发变更评审而非直接上线

诚实提示:本篇所有阈值(0.90、P95≤2s、±0.03)都是占位示例,不是任何真实系统的实测值。落地时须按自己的数据分布、SLA 与模型卡重新标定;判据本身也应随版本一起评审。


7. 实操:自然语言需求 → 结构化规格 → 验收标准

下面把 §4–§5 串起来做一次实操:用一段真实的含糊需求,产出一份可验证 SRS。给一条把二者连起来的辅助抽取骨架(模型细节隔离在配置里,换模型不改逻辑):

Python · 需求 → 候选 SRS(抽取 + jsonschema 校验)
import json, requests
from jsonschema import validate   # pip install jsonschema

SERVER = "http://127.0.0.1:8001/v1"   # 任一 OpenAI 兼容端点
MODEL  = "your-model"                  # 以你本地/云端实际部署为准

# 强制模型按 schema 产出结构化需求(Prompt as extractor)
EXTRACT_PROMPT = """把下面的自然语言需求拆成条目。每条给:
id(FR/NFR/MC/DR/GD-序号), layer, text(可验证化后的表述),
verifiable(bool), acceptance(若可验证给判据,否则给 needs_defn 说明缺口)。
只输出 JSON。需求:{req}"""

SCHEMA = {
  "type":"object",
  "properties":{"requirements":{"type":"array","items":{
     "type":"object",
     "properties":{
        "id":{"type":"string"},"layer":{"type":"string"},
        "text":{"type":"string"},"verifiable":{"type":"boolean"},
        "acceptance":{"type":"object"}},
     "required":["id","layer","text","verifiable"]}}},
  "required":["requirements"]
}

def extract(req_text):
    r = requests.post(f"{SERVER}/chat/completions", json={
        "model": MODEL,
        "messages":[{"role":"user","content":EXTRACT_PROMPT.format(req=req_text)}],
        "temperature": 0,
        "response_format": {"type": "json_object"},   # 结构化输出,降低歧义
    }, timeout=60)
    obj = json.loads(r.json["choices"][0]["message"]["content"])
    validate(obj, SCHEMA)          # 产出即校验:不合规格直接失败
    obj["version"] = "v0.1"
    return obj

if __name__ == "__main__":
    raw = open("raw_requirement.txt", encoding="utf-8").read
    srs = extract(raw)
    json.dump(srs, open("srs_v0.1.json", "w", encoding="utf-8"),
              ensure_ascii=False, indent=2)
    print("已写出 srs_v0.1.json —— 下一步跑 §5 的可验证率脚本")
Python · 把每条 AC 落成可执行测试骨架(喂给 pr05)
import json

srs = json.load(open("srs_v0.1.json", encoding="utf-8"))
for r in srs["requirements"]:
    a = r.get("acceptance") or {}
    if a.get("type") == "threshold":
        print(f'# AC[{r["id"]}] 阈值断言:assert metric("{a["metric"]}") <= {a["threshold"]}')
    elif a.get("type") == "classification":
        print(f'# AC[{r["id"]}] 分类判据:assert accuracy(model, "{a["dataset"]}") >= {a["threshold"]}')
    elif a.get("type") == "negative_checklist":
        gap = a.get("needs_defn", "判据待定")
        print(f'# AC[{r["id"]}] 负面清单(待消解:{gap})→ 需对抗用例(pr07)')
    if not r.get("verifiable"):
        print(f'# TODO[{r["id"]}] 不可验证,须量化或记风险后再进测试')

实验交付物:① 原始需求文本 ② srs_v0.1.json(结构化、带可验证性标注)③ 由 AC 生成的测试骨架 ④ 一张「待消解需求」清单。四者齐了,这次实操才算闭环。


8. 结果度量:把「需求质量」变成过程指标

需求阶段也能出可统计指标,供 pr08 仪表盘 / pr14 实证分析使用:

  • 需求可验证率 = 锚定判据的条目 / 总条目(§5 脚本产出)——衡量「说不清」的残留量。
  • 需求覆盖率 = 被至少一条 AC 覆盖的 FR / 总 FR——防止「写了没人验收的功能需求」。
  • 漂移频率 = 单位周期内触发门禁阻断的次数——反映三侧稳定性,也是技术债信号。
  • 歧义消解成本 = 从「标为不可验证」到「补齐判据」的平均往返轮次与时长。

串链回顾:本篇产出的 srs_v0.1.json 与 AC 探针集,正是 pr15 版本清单要锁的对象、pr05 测试要执行的判据、pr14 实证要度量的自变量。需求工程不是文档仪式,而是给下游所有可复现实验提供一个稳定、可 diff 的契约源头。


9. 可复现性清单(写报告前打勾)

  • ☑ 原始需求、srs.json、AC 探针集全部纳入版本管理,带 version 字段
  • ☑ 抽取所用模型、端点、temperature=0、response_format 记录在报告(以官方模型卡为准)
  • ☑ 每条 AC 标注判据类型(阈值/属性/抽样/负面)与对应测试脚本
  • ☑ 「待消解需求」显式列出,含消解计划或风险接受声明
  • ☑ 漂移门禁阈值(±tol、SLA)标注为占位示例,实际值另标定
  • ☑ 双人评审 SRS,记录一致性(可复用 pr09 的 Kappa 流程)

一页纸总结:智能软件需求难在「无唯一真值 + 需求藏在 Prompt + 三侧同时漂移」→ 用 FR/NFR/MC/DR/GD 五层分类把它拆开 → 把形容词逼成可执行判据(阈值/属性/抽样/负面四模板)→ 用 srs.json + AC 探针 建基线 → 用 diff + 回归门禁治理漂移 → 交付「可验证率 / 覆盖率 / 漂移频率」三个过程指标,向下游 pr05/15/14 提供稳定的契约源头。一次完整实操 = 自然语言需求 → 结构化规格 → 验收标准 四件套。