Appearance
怎么在不依赖真实模型的情况下测试 Runtime:Fake Model 与 Deterministic Tests
为什么真实模型反而不适合测 Runtime
要验证“达到 max_turns 必须终止”,真正被测试的是 Runtime 的计数和状态迁移。若测试依赖真实模型碰巧连续返回 search_again,失败时你无法判断是 Runtime bug、模型换了行为,还是网络抖动。
所以需要把 Model 看成一个 Port:生产环境接真实 Provider,单元测试接 Scripted/Fake Model。Fake 的任务不是模拟智能,而是精确制造 Runtime 需要面对的输入条件。
Fake Model 应返回和真实 Adapter 同一种 domain output
错误做法是给测试单独设计一种简化对象,导致 production adapter 和 test adapter 走两条代码路径。
更合理的是统一:
python
@dataclass(frozen=True)
class ModelResponse:
items: tuple[OutputItem, ...]
usage: ModelUsage | None = None
class ModelClient(Protocol):
async def generate(self, request: ModelRequest) -> ModelResponse: ...Fake 只是在 generate() 时从预先写好的脚本取下一项:
python
class ScriptedModel:
def __init__(self, responses):
self.responses = iter(responses)
self.requests = []
async def generate(self, request):
self.requests.append(request)
value = next(self.responses)
if isinstance(value, Exception):
raise value
return value这样 Runtime 不知道自己面对的是 Fake 还是真模型。
一条脚本可以稳定覆盖完整控制流
例如固定:
text
Turn 1 → tool_call(search_code)
Turn 2 → tool_call(read_file)
Turn 3 → final_output测试关注的不是模型文本好不好,而是:
text
turn == 3
search_code executed once
read_file executed once
tool results returned to next model request
run.status == SUCCEEDED如果第二次响应改成非法 Tool 名,就能稳定验证 Tool Registry 拒绝路径;如果第三次永远返回继续搜索,就能验证 max-turn termination。
Error、Streaming 和 Timing 也应该脚本化
Fake Model 不应该只支持“成功文本”。真正有价值的测试替身需要能制造:
- 第 N 次调用抛 rate-limit / timeout / malformed-response;
- 返回多个并行 Tool calls;
- 返回 schema validation failure;
- 按固定 event sequence 模拟 streaming;
- 等待一个 test-controlled barrier,验证 cancellation/deadline。
关键是测试控制事件顺序,而不是 sleep(0.5) 猜并发时序。并发测试可以用 asyncio.Event 或 barrier 明确卡住 Fake Model,等 Runtime 进入指定状态后再继续。
Fake Model 和 Mock Provider SDK 不是一回事
两者测试不同边界:
text
Scripted Model
→ 验证 Runtime / domain contract
Mock/Fake HTTP or Provider SDK
→ 验证 Provider Adapter mapping
Real provider integration
→ 验证网络、鉴权、真实 schema/API 兼容
Eval
→ 验证模型决策和语义质量如果所有测试都直接 mock OpenAI/Anthropic SDK response,Runtime 就被 Provider 数据结构污染;换 Provider 时测试也要大面积重写。
最值得固定下来的几类 Runtime Case
Fake Model 特别适合这些 deterministic contract:
text
unknown tool → rejected
invalid tool args → no side effect
max turns reached → terminal failure/stop
tool failed → failure observation enters next turn
approval required → run enters WAITING
resume → completed attempts are not repeated
cancellation → no new action starts after cancel fact这些测试失败时,应该首先怀疑自己的代码,而不是模型“今天发挥不好”。真实模型选择哪个 Tool、规划是否合理,则交给 Module 18 的 Eval。
自测
- 你要稳定复现“第二次模型调用产生 ToolCall,第三次返回 invalid output”。Fake/Scripted Model 应记录什么序列,而不是依赖真实模型碰运气?
- 一个 Runtime 单测偶尔因真实模型改写措辞而失败。这个测试究竟在验证 Runtime contract 还是模型质量?应该怎样拆开?