Skip to content
预计 1 分钟右侧目录可快速定位

怎么在不依赖真实模型的情况下测试 Runtime:Fake Model 与 Deterministic Tests ​

前置:模型调用失败后怎么处理:Error Taxonomy、Routing 与 Fallback

为什么真实模型反而不适合测 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。

自测 ​

  1. 你要稳定复现“第二次模型调用产生 ToolCall,第三次返回 invalid output”。Fake/Scripted Model 应记录什么序列,而不是依赖真实模型碰运气?
  2. 一个 Runtime 单测偶尔因真实模型改写措辞而失败。这个测试究竟在验证 Runtime contract 还是模型质量?应该怎样拆开?

Sources ​

AI Engineering · Agent · Harness · Backend Systems