通过确定性上下文分批策略,将OpenAPI规范相关的Token用量从约50000降至约9200,在API测试Agent场景下验证有效,工程方法论扎实。
How deterministic context batching reduced approximate token usage by ~81.7% while building an agentic API testing prototype
我还在 YouTube 上录制了一个简短演示,展示了完整的工作流程:从加载 OpenAPI 规范到生成并执行测试,以及自动修复:
Watch the demo on YouTube
我最初构建这个是为了参与 GSoC 2026 提案,目标是 foss42。
虽然最终没有入选 GSoC,但在准备提案的过程中,我已经构建出了想要贡献的这个系统的工作原型:一个 agentic API 测试工作流,能够读取 OpenAPI 规范、生成测试、执行测试,并对失败结果进行推理。
在构建过程中,我遇到了一个与 HTTP 本身关系不大的问题。
OpenAPI 规范太大了,无法高效地发送给 LLM。
这促使我构建了 OpenAPI Context Batching——一种确定性的方法,每次只向 LLM 提供 OpenAPI 规范的相关部分,而不是反复发送整个 API。
在我的原型实验中,在相同的五轮交互工作流中,近似 token 用量从约 50,000 个减少到约 9,200 个,降幅达 81.7%。🤯
The Problem: OpenAPI Specs are Massive
我的项目最初目标是让 AI agent 读取 OpenAPI 规范、生成测试计划并分析 API 失败原因。但我很快遇到了一个巨大的问题。
真实的 OpenAPI 规范非常庞大。它们包含数百个端点、嵌套的 schema 和复杂的验证规则。如果将整个 JSON 文件直接塞进 LLM prompt,会遇到两个主要问题:
每次单一会话都会消耗数千个 token。
模型完全被淹没,开始产生虚假端点的幻觉。
如果我只想让 AI agent 测试 /categories 端点,它完全不需要看到 /payments、/cart 或其他无关端点的 schema。我意识到我需要一种更智能的上下文提供方式,而不是简单地提供更大的上下文。
The Golden Rule: AI Plans, Dart Executes
在优化 token 之前,我必须先锁定核心架构。我为整个系统确立了一条严格规则:AI 负责制定测试计划,Dart 负责实际执行测试。
让 LLM 直接执行 HTTP 调用是一个糟糕的想法,因为它很容易编造虚假的响应。为了防止这个问题,模型被限制为生成结构化的 JSON 测试计划。
我构建了一个独立的、无头(headless)的 Dart 执行类叫做 ApiTestRunner。这个执行器接收 JSON 测试计划并使用 API Dash 的网络层执行真实的 HTTP 请求。AI 永远不会直接接触网络。
The Solution: Deterministic Context Batching
那么,我们如何才能真正压缩一个庞大的 OpenAPI 规范呢?我们不依赖另一个 AI 来总结它。我们使用确定性代码,因为 OpenAPI 是高度结构化的。
我编写了一个名为 OpenAPI Context Batching 的自定义 Dart 算法。脚本不再发送完整规范,而是解析它并以确定性方式将其拆分。以下是它的工作原理:
Domain Splitting(按域名拆分):脚本读取原始 OpenAPI JSON,并按根域名(如 /auth 或 /categories)拆分端点。
Recursive Reference Resolution(递归引用解析):OpenAPI 大量使用 $ref 来复用共享的 schema。如果只提取端点路径,AI 就无法知道实际的数据载荷长什么样。我构建了一个递归解析器,深入挖掘端点并找到所有 $ref。
Deep Schema Extraction(深度 schema 提取):解析器只拉取该确切端点所需的特定深层嵌套 schema。这完全避免发送臃肿的 components/schemas 对象。
Focused Context Map(聚焦上下文映射):输出是一个简洁的 Map<String, String>,每个条目是仅针对一个域的完整解析后的批量 schema。
当 agent 需要对请求进行推理时,它只收到这个高度聚焦的批量 schema。由于算法是确定性的,相同的 OpenAPI 规范每次都会产生完全相同的端点分区。
The Guardrailed Retry Loop
为了让系统更加智能,我构建了一个带有自愈循环的对话 agentic 模式。
如果 Dart 执行引擎运行测试后收到失败的 non-2xx 状态码,它会拦截该错误。然后它将确切的状态码和响应体重新注入 LLM prompt,请求 agent 自动修复参数。
为了确保这不会导致一个由 AI 驱动的无限循环从而耗尽额度,我硬编码了严格的三次最大重试限制。
The Results: 81.7% Token Reduction
我在原型开发期间使用自定义的 shop.json OpenAPI 规范对这个方法进行了基准测试。结果非常显著:测试工作流的输入 token 减少了约 81.7%。
通过只发送依赖感知的上下文,我在一个五步对话工作流中实现了 81.7% 的 token 用量降幅。这不仅节省了大量 token,聚焦的上下文还意味着 AI 响应更快,并停止了幻觉。
The coolest thing I learned from this project is that we should not use LLMs for everything.
从这个项目中学到的最酷的事情是,我们不应该把 LLM 用于所有事情。
解析文件、查找依赖和执行 HTTP 请求是最适合由确定性软件处理的任务。生成测试计划和对边缘情况进行推理才是 LLM 真正擅长的领域。
Building this engine independently of the Flutter UI also allowed me to expose it as an MCP server, meaning external tools like Claude Desktop can trigger real test runs. It was an incredible learning experience, and I am super excited to keep exploring how we can build smarter, more efficient developer tools!
在独立于 Flutter UI 构建这个引擎之后,我还能够将其作为 MCP 服务器暴露出来,这意味着外部工具(如 Claude Desktop)可以触发真实的测试运行。这是一段令人难以置信的学习经历,我非常兴奋能够继续探索如何构建更智能、更高效的开发工具!
For further actions, you may consider blocking this person and/or reporting abuse