跳转到主要内容

LLM 本地开发循环:在不耗尽 API 预算的情况下实现快速迭代

阅读需 2 分钟Tian PanTian Pan

大多数构建 LLM 应用的团队在第三周左右都会发现同样的问题:每次有人运行测试套件时,它都会发起实时 API 调用,消耗真金白银,耗时 30 多秒,且每次运行返回的结果都不尽相同。在原型阶段感觉良好的“直接调用 API”方法,现在变成了迭代速度的沉重负担——而且是账单上的一项重要支出。一个工程团队审计了他们每月的 API 支出,发现 2,847 美元中有 1,240 美元(43%)是由于开发和测试流量不必要地访问实时端点而产生的纯粹浪费。

解决方案不是停止测试,而是从一开始就构建正确的开发循环——让快速路径既便宜又具有确定性,而将慢速路径(真实的 API 调用)留给真正需要的时刻。

为什么内部循环会崩溃

当你开始一个 LLM 项目时,你会通过运行应用程序并观察输出来进行测试。这行得通。但随着代码库的增长,团队成员的增加,以及有人配置了 CI 流水线,突然之间,每个 Pull Request 都会触发数十个真实的 API 调用。成本不断累积。不稳定的测试失败(同样的提示词,不同的输出)开始阻塞合并。工程师们添加了 skip_in_ci 标志来规避这个问题,这意味着测试根本不再运行。

根本原因在于团队将 LLM 调用视为数据库查询——即在开发过程中应始终实时访问的外部 I/O。但 LLM 调用具有一些特性,使得这种做法异常昂贵:

  • 高延迟:单个 GPT-4 调用可能耗时 10–30 秒。一个包含 20 个步骤的 Agent 测试需要几分钟。
  • 每次运行的成本可变:输出 Token 的成本是输入 Token 的 3–6 倍。生成冗长输出的测试会按比例消耗预算。
  • 非确定性:Temperature > 0 意味着同一个测试可能会根据模型当天的“心情”而通过或失败,导致 CI 不可靠。
  • CI 没有免费层级:不像你可以在 Docker 中启动本地数据库,每一次测试运行都会在你的 OpenAI 或 Anthropic 账户中产生账单。

解决方法是对待 LLM API 调用时,采用对待任何昂贵外部依赖项时相同的纪律:捕获一次你关注的行为,之后以廉价的方式进行回放。

录制-回放:一次捕获,永久测试

录制-回放(Record-replay)模式是 LLM 测试基础设施最接近通用的解决方案。这个概念借鉴自 HTTP Mock 库:测试第一次在“录制”模式下运行时,它会发起真实的 API 调用并将响应持久化到磁盘。在后续以“回放”模式运行时,它会从磁盘读取而不是调用网络。

对于 Python 项目,pytest-recording(基于 VCR.py 构建)是最符合人体工程学的实现。你使用 @pytest.mark.vcr 装饰测试,第一次运行会捕获交互录制文件(cassettes)到签入存储库的 YAML 文件中。CI 始终以回放模式运行——不需要 API 密钥,执行只需几毫秒,输出在字节级完全一致。

@pytest.mark.vcr
def test_classify_support_ticket():
    result = classify_ticket("My order hasn't arrived in two weeks")
    assert result.category == "shipping"
    assert result.priority == "high"

对于基于 LangChain 的应用,vcr-langchain 将此扩展到了整个链的执行,而不仅仅是单个 HTTP 调用。对于使用 BAML 进行结构化输出的团队,BAML VCR 提供了具有类型感知存储的相同模式。

关键的实现细节:录制文件(cassettes)应与代码一起提交到存储库。当你特意更改提示词或模型时,删除受影响的录制文件,在录制模式下运行一次以重新生成,在 PR 差异中审查新的录制文件,然后提交。录制文件的差异变成了证明你的更改产生了预期行为转变的具体工件——而不仅仅是主观的“我运行的时候看起来是对的”。

用于单元测试的确定性 Fixtures

录制-回放适用于练习完整调用路径的集成级测试。但它仍然将你的单元测试与真实 API 响应的形式耦合在一起。为了测试封装 LLM 调用的逻辑——例如重试处理、输出解析、上下文窗口管理、错误升级——你需要更快速、更受控的方法。

确定性 Fixtures 是测试基础设施针对匹配输入返回的预设响应。与录制-回放不同,Fixtures 是手动编写且显式的。你精确定义“模型”针对给定提示词返回的内容,然后你的应用逻辑针对该内容运行。

llmock 这样的工具更进一步,它运行一个模拟 OpenAI 或 Anthropic API 的真实 HTTP 服务器。你的应用程序通过环境变量指向 http://localhost:8080 而不是 api.openai.com。应用程序代码无需任何改动——可以这么说,你只是重定向了 DNS。Fixture 响应在 JSON 中配置:

{
  "matchers": [{"contains": "classify this ticket"}],
  "response": {"category": "shipping", "priority": "high"}
}

这种方法比进程内补丁(in-process patching)更可靠,因为它在 HTTP 级别进行拦截。任何发起 API 调用的子进程或后台线程也会被拦截,而无需感知你的测试基础设施。

会员专享

余下内容仅对会员开放。

会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。

  • 完整文章,包含未公开存档的部分
  • 可落地的工作框架,附带权衡与决策依据
  • 新文章抢先看,先于公开发布

随时取消 · 一次订阅,畅读全部

参考资料

保持联系,关注我获取更多内容

阅读需 9 分钟

LLM-as-Judge 的对抗性失效:当你的评测框架被操控

一个输出固定回复的「空模型」在 AlpacaEval 上拿下了 86.5% 的胜率。本文系统梳理 LLM 评测框架被操控的方式、其内在的结构性偏差,以及让评测流水线保持诚实的审计方案。

insider
evaluation
阅读需 10 分钟

测试不可测之物:LLM 驱动 API 的集成契约

确定性测试套件无法应对非确定性的 LLM 输出。学习基于属性的测试、行为不变量断言和语义快照策略,在不引入脆弱性的情况下获得回归覆盖。

insider
llm
阅读需 10 分钟

AI 的测试金字塔倒置:为什么单元测试是 LLM 功能的错误投资

经典测试金字塔在 LLM 功能上失效的原因、提示词级单元测试为何带来虚假信心,以及与 AI 故障实际分布相匹配的测试分配策略。

insider
ai-engineering
阅读需 12 分钟

Eval 异味目录:让你的 LLM 评估套件比没有评估还糟糕的反模式

一份关于毒害 LLM 评估套件的反模式实战指南 —— 包括数据污染、脆弱的断言、评估腐化、评委合谋、虚荣聚合指标 —— 以及在无需重写整个测试框架的情况下恢复有效信号的重构模式。

insider
ai-engineering
阅读需 9 分钟

你的评测套件是一座博物馆:生产故障应当成为明天的测试用例

静态评测框架会随着产品的增长而过时——它们只能测试作者预设的场景。以生产为驱动的反馈闭环能够自动将真实故障转化为永久性回归测试,使评测套件始终与实际用户行为保持一致。

insider
llm