为什么单独开这一篇:前面 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」的断言失效,需求必须改写成属性 / 约束 / 阈值形式才可验证。
自然语言提示词事实上承担了需求规格,但它天生有歧义、有遗漏、有不可测的形容词。需求工程要做的第一件事,是把它显式结构化。
需求文本、模型权重、训练/检索数据各自独立演化。任一处改动都可能让「当初能验收的规格」今天悄悄失效——这就是规格漂移。
关键视角:把需求当成跨三个版本轴成立的断言,而不是一次写完的文档。这条视角直接决定了 §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-1 | FR | 将工单归入 {退款, 物流, 账号, 其他} 之一,输出单标签 | 可测(有对照集) |
| FR-2 | FR | 生成回复初稿文本,长度 ≤ 3 句 | 可测(长度可数) |
| NFR-1 | NFR | 端到端延迟 P95 ≤ 2 s | 可测(阈值) |
| NFR-2 | NFR | 「尽量快一点,别让人等」 | 歧义 → 需量化 |
| GD-1 | GD | 命中「投诉 / 监管」等词 ⇒ 转人工,不得自动回复 | 可测(触发+动作) |
| GD-2 | GD | 禁止输出未在知识库中的具体补偿金额 | 需定义「瞎编」判据 |
| MC-1 | MC | 语气「友好专业」 | 无法客观验证 → 转量表 |
| DR-1 | DR | (未提及语言/时效覆盖) | 缺失 → 需补 |
核心手法:遇到 快一点 友好 别瞎编 这类形容词需求,不要照抄进 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 字段:
{
"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 评分均分≥4 | LLM-as-a-Judge + Kappa(pr05/09) |
| 负面型 | 护栏、禁止项 | 命中投诉词必转人工,违反率=0 | 对抗样本回归(pr07) |
可验证性判定规则(给读者的一把尺):一条 AC 只有满足「存在一个判据,能在有限成本内产出 通过 / 不通过」才算成立。否则它必须被改写,直到成立、或被显式标注为「本期不可验证(接受风险)」并写入决策记录。
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 阈值 |
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。给一条把二者连起来的辅助抽取骨架(模型细节隔离在配置里,换模型不改逻辑):
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 的可验证率脚本")
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 提供稳定的契约源头。一次完整实操 = 自然语言需求 → 结构化规格 → 验收标准 四件套。