第二章:AI 系统的特殊性
理解 AI 系统与传统软件的本质差异,是设计有效 Harness 的前提。
概率性输出:不确定性的根源
传统软件的确定性
传统程序的执行是确定性的:
def add(a, b):
return a + b
# 同样输入,永远同样输出
assert add(1, 2) == 3 # 100% 确定
assert add(1, 2) == 3 # 再次执行,还是 3
给定输入 ,输出 是确定的函数关系:
AI 系统的概率性
AI 模型的输出是概率分布上的采样:
response = llm.generate("什么是 AI?")
# 同样输入,可能不同输出
# 第1次: "AI 是人工智能..."
# 第2次: "人工智能是指..."
# 第3次: "AI(Artificial Intelligence)..."
数学表达:
AI 模型不是"计算答案",而是"生成可能的答案"。 温度参数 temperature 控制随机性程度:
- temperature=0:最确定(但仍非100%)
- temperature=1:更多样性(不确定性更高)
测试方法论的断裂
graph LR
subgraph "传统测试逻辑"
A1[输入 x] --> B1[函数 f]
B1 --> C1[输出 y]
C1 --> D1[断言 y==expected]
D1 --> E1[Pass/Fail]
end
subgraph "AI 测试困境"
A2[输入 x] --> B2[模型 P]
B2 --> C2[采样 y]
C2 --> D2[无法断言]
D2 --> E2[???]
end
style D2 fill:#f66,stroke:#333
style E2 fill:#f66,stroke:#333
质量的多维性:没有唯一正确答案
传统软件:单一正确性
传统程序的质量标准是明确的:
| 场景 | 正确标准 | 测试方法 |
|---|---|---|
| 计算器 | 数值正确 | assert result == expected |
| 排序 | 顺序正确 | assert sorted(arr) == expected |
| 登录 | 成功/失败 | assert login(user, pwd) == True |
AI 系统:多维质量空间
一个 LLM 输出的质量有多个维度:
graph TB
A[LLM 输出质量] --> B[正确性]
A --> C[相关性]
A --> D[连贯性]
A --> E[安全性]
A --> F[有用性]
B --> B1[事实是否准确]
C --> C1[是否回答了问题]
D --> D1[逻辑是否通顺]
E --> E1[是否有害内容]
F --> F1[是否解决问题]
实例分析:
用户问:推荐一个北京好吃的餐厅
模型回答1:北京有很多好吃的餐厅,比如全聚德烤鸭店,历史悠久...(正确但冗长)
模型回答2:王府井附近的小吊梨汤不错。(简洁但信息少)
模型回答3:作为AI我没有个人体验,建议查看大众点评。(安全但无用)
模型回答4:去吃烤鸭吧,全聚德最出名!(有用但不够具体)
哪个"正确"?—— 需要多维度评分
质量评分矩阵
其中权重向量 ,评分向量 分别对应正确性、相关性、连贯性、安全性和有用性五个维度。
| 维度 | 评估方法 | 权重建议 |
|---|---|---|
| 正确性 | Fact check / Reference match | 30% |
| 相关性 | Semantic similarity | 20% |
| 连贯性 | Language model scoring | 15% |
| 安全性 | 规则检测 + 模型评估 | 25% |
| 有用性 | 用户反馈 / Task completion | 10% |
边界的模糊性:测试覆盖是 NP 问题
传统软件:有限状态空间
传统程序的状态空间是有限的(虽然可能很大):
程序状态 = {输入组合} × {内部状态}
测试覆盖目标:覆盖关键路径
理论上限:可以穷举(实际用覆盖率指标)
AI 系统:无限语义空间
AI 系统的输入是自然语言,语义空间是无限的:
graph LR
A["用户意图: 订一张机票"] --> B["表达方式1: 我想买机票"]
A --> C["表达方式2: 帮我订机票"]
A --> D["表达方式3: 需要订票"]
A --> E["表达方式N: ... 无限种表达"]
B --> F[模型理解]
C --> F
D --> F
E --> F
"相同"意图的不同表达:
| 用户意图 | 可能表达 | 数量 |
|---|---|---|
| 订机票 | "买票"、"订票"、"航班预订"... | ∞ |
| 查天气 | "天气"、"今天天气"、"外面天气"... | ∞ |
| 翻译 | "翻译一下"、"帮我翻译"、"这个怎么说"... | ∞ |
测试覆盖策略
无法穷举,需要策略性覆盖:
graph TB
A[所有可能输入<br/>无限空间] --> B[Golden Set<br/>典型场景]
A --> C[Boundary Set<br/>边界场景]
B --> B1[100个精选案例]
C --> C1[50个极端案例]
| 覆盖策略 | 方法 | 目标 |
|---|---|---|
| Golden Set | 精选高质量代表案例 | 覆盖典型场景 |
| Boundary Set | 极端、边缘、异常案例 | 发现边界问题 |
| Diversity Set | 语义多样性采样 | 覆盖表达变体 |
| Adversarial Set | 故意设计的攻击案例 | 安全性测试 |
模型的演进性:回归测试复杂化
传统软件回归
传统软件回归测试的核心假设:
代码修改 → 功能变化 → 测试验证 → Pass/Fail
回归目标:修改后原有功能仍正常
AI 模型回归的复杂性
模型更新后的"回归"复杂得多:
graph TB
A[模型更新] --> B[权重变化 Δθ]
B --> C[行为变化]
C --> D1[某些场景变好]
C --> D2[某些场景变差]
C --> D3[新场景能力]
C --> D4[原有能力退化]
D1 --> E[需要对比评估]
D2 --> E
D3 --> E
D4 --> E
回归评估维度:
| 维度 | 评估方法 | 目标 |
|---|---|---|
| 基线对比 | 新旧版本得分差 | 确保整体提升 |
| 分类评估 | 按场景类别分组评估 | 发现局部退化 |
| A/B测试 | 真实用户对比 | 验证实际体验 |
| 异常检测 | 新增错误模式识别 | 发现新问题 |
小结:AI 系统特殊性一览
graph TB
A[AI 系统特殊性] --> B[概率性输出]
A --> C[多维质量]
A --> D[边界模糊]
A --> E[持续演进]
B --> B1[断言失效 → 评估替代]
C --> C1[单一标准 → 多维评分]
D --> D1[穷举覆盖 → 策略采样]
E --> E1[回归测试 → 对比评估]
| 特殊性 | 传统假设失效 | AI Harness 方案 |
|---|---|---|
| 概率性输出 | 断言确定性失效 | 评估 + 阈值判断 |
| 多维质量 | Pass/Fail 二分失效 | 多维评分矩阵 |
| 边界模糊 | 覆盖率指标失效 | 策略性覆盖设计 |
| 模型演进 | 回归测试失效 | 版本对比 + A/B |
补充:长时运行 Agent 的挑战
跨上下文窗口的连续工作
传统软件开发中,一个开发者可以在一个任务上连续工作数小时甚至数天。但 AI Agent 面临一个独特的限制:上下文窗口有限。
Anthropic 的工程团队在实践长时运行 Agent 时发现了这个核心挑战 [10]:
人类开发者:
├── 上午理解需求 → 下午写代码 → 明天修 bug → 后天重构
├── 所有上下文在大脑中持续累积
└── 连续性天然保证
AI Agent:
├── 第一次调用:理解需求,开始编码
├── 上下文用完 → 必须重新启动
├── 第二次调用:不知道第一次做了什么 → 重复工作或遗漏
└── 连续性断裂!
失败模式
长时运行 Agent 如果没有 Harness 支撑,会出现几种典型的失败模式:
| 失败模式 | 表现 | 根因 |
|---|---|---|
| 重复劳动 | 第二次调用重做第一次已完成的工作 | 缺少进度记录 |
| 遗漏功能 | 忘记需求清单中的部分功能 | 缺少任务跟踪 |
| 不一致实现 | 前后两次调用的风格/模式不统一 | 缺少上下文传递 |
| 无限循环 | 反复尝试同一个失败的方案 | 缺少失败记忆 |
| 破坏已完成 | 修改了之前已经验证通过的代码 | 缺少保护机制 |
Harness 的解决方案
Anthropic 团队总结了一套行之有效的模式 [10]:
长时运行 Agent Harness 模式:
1. 初始化 Agent(Initializer Agent)
├── 读取需求 → 生成结构化的功能清单
├── 每个功能标记为 TODO
└── 输出:feature_list.json
2. 编码 Agent(Coding Agent)— 可多次调用
├── 读取 feature_list.json
├── 找到第一个 TODO 项
├── 实现 → 标记为 DONE → 提交
├── 更新 feature_list.json
└── 上下文用完时,下一个 Coding Agent 接力
3. 进度持久化
├── feature_list.json(任务清单)
├── claude-progress.txt(自然语言进度描述)
└── git commit(代码状态快照)
# feature_list.json 示例
{
"project": "user-auth-system",
"features": [
{
"id": "F001",
"name": "用户注册 API",
"status": "DONE",
"passes": [
{"pass": 1, "status": "DONE", "commit": "a1b2c3d"},
{"pass": 2, "status": "DONE", "commit": "e4f5g6h", "note": "添加输入验证"}
]
},
{
"id": "F002",
"name": "JWT Token 认证",
"status": "IN_PROGRESS",
"passes": [
{"pass": 1, "status": "DONE", "commit": "i7j8k9l"},
{"pass": 2, "status": "IN_PROGRESS", "note": "需要添加刷新逻辑"}
]
},
{
"id": "F003",
"name": "密码重置流程",
"status": "TODO",
"passes": []
}
]
}
Harness 的核心价值之一:让 Agent 的工作可接力。
通过将进度持久化到文件系统中(而非仅存在于上下文中), Agent 可以跨越上下文窗口的限制,实现真正的长时运行。 这就像接力赛跑——每一棒都从上一棒停下的地方继续。
参考文献:
- [10] Anthropic Engineering, "Effective harnesses for long-running agents", anthropic.com, 2026
下一章,我们将探讨上下文工程——如何让 Agent 看得见它需要的一切。