news 2026/9/15 10:44:16

TensorZero 模型提供商架构与 Prompt Caching 支持指南:从 AGENTS.md 到 cache.rs 的源码级解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TensorZero 模型提供商架构与 Prompt Caching 支持指南:从 AGENTS.md 到 cache.rs 的源码级解析

TensorZero 模型提供商架构与 Prompt Caching 支持指南:从 AGENTS.md 到 cache.rs 的源码级解析

【免费下载链接】tensorzeroTensorZero is an open-source LLMOps platform that unifies an LLM gateway, observability, evaluation, optimization, and experimentation.项目地址: https://gitcode.com/GitHub_Trending/te/tensorzero

TensorZero 作为一个统一 LLM 网关、可观测性、评测与优化能力的 LLMOps 平台,其核心引擎tensorzero-core需要对接 OpenAI、Anthropic、AWS Bedrock、Google Gemini、DeepSeek 等数十家模型提供商,而每家提供商的用量(Usage)与缓存(Prompt Caching)上报格式又各不相同。本文以 crates/tensorzero-core/src/providers/AGENTS.md 为骨架,深入解析 TensorZero 如何通过独立的tensorzero-types-providerscrate 管理提供商线格式类型,并以 crates/tensorzero-types-providers/src/cache.rs 作为"单一事实来源"统一各提供商的缓存 token 映射。读完本文,你将掌握 TensorZero 提供商模块的架构分层、全部提供商缓存字段的映射关系、从 ProviderUsage 到内部 Usage 的转换链路,以及为新增提供商接入缓存支持并运行 e2e 验证的完整流程。

一、提供商模块的组织方式与迁移策略

在 TensorZero 的源码树中,所有与外部模型提供商通信的实现代码位于 crates/tensorzero-core/src/providers,其中每个文件或子目录对应一个(或一组)提供商:

  • anthropic.rsaws_bedrock.rsaws_sagemaker.rsazure.rsdeepseek.rsdummy.rsgroq.rsmistral.rsopenrouter.rstgi.rstogether.rsvllm.rsxai.rshyperbolic.rssglang.rs
  • 目录形式的有fireworks/gcp_vertex_gemini/openai/,以及gcp_vertex_anthropic.rsgoogle_ai_studio_gemini.rs
  • 另有chat_completions.rsaws_common.rshelpers.rshelpers_thinking_block.rs等公共辅助模块,以及test_helpers.rs测试辅助

providers/AGENTS.md 首先给出的架构约定是:与模型提供商 API 通信相关的类型正在逐步迁移到一个独立的 crate——tensorzero-types-providers。其迁移原则非常明确:

如果你要创建新类型(例如MyProviderUsage),优先在tensorzero-types-providers下创建,再通过 import 引用,而不是直接放进tensorzero-core

这套策略的动机可以从 crate 的文档注释中读出:tensorzero-types-providers是一个纯 serde 类型库,只负责"外部提供商 API 的线上格式类型"(wire format types),见 crates/tensorzero-types-providers/src/lib.rs。把请求/响应的线格式类型与请求发送、流式解析、重试等运行时逻辑解耦,好处是:

  1. 编译边界清晰:改一个提供商的新字段,不会牵连tensorzero-core中大量业务逻辑重编译;
  2. 可独立测试:serde 反序列化、字段映射可以在无网关运行时环境下单独验证(例如 crates/tensorzero-types-providers/src/conversions.rs 内就内嵌了大量转换单测);
  3. 方便未来被其他 crate 复用(如tensorzero-node等绑定层)。

当前该 crate 的模块清单见 lib.rs:aws_bedrockcacheconversionsdeepseekfireworksgroqmistralopenaiopenrouterserde_utiltogetherxai,覆盖了 OpenAI 兼容系、Anthropic 系、Gemini 系、DeepSeek、Fireworks、Mistral 等主流提供商,与tensorzero-core/src/providers下的实现文件一一对应。

二、Prompt Caching 的"单一事实来源":cache.rs

AGENTS.md 给出的第二条、也是最重要的架构约定是:

关于prompt caching 支持,请参见tensorzero-types-providers/src/cache.rs。它是"哪些提供商支持缓存、它们的 API 字段名是什么、如何映射到 TensorZero 内部Usage结构体"的单一事实来源(single source of truth)。在为一个提供商新增或修改缓存 token 支持时,请遵循其中的清单(checklist)。

cache.rs自述为"文档专用模块"(documentation-only),实际缓存相关类型仍定义在各提供商模块中(如openai::OpenAIPromptTokensDetails),但它承载了全部映射决策的权威描述,见 crates/tensorzero-types-providers/src/cache.rs。

2.1 内部 Usage 的两个缓存字段

TensorZero 内部统一的用量结构体Usage定义在 crates/tensorzero-types/src/usage.rs,除input_tokensoutput_tokenscost外,与缓存相关的字段只有两个:

字段含义
provider_cache_read_input_tokens从缓存中读取(命中)的 token 数,成本更低
provider_cache_write_input_tokens写入缓存的 token 数,可能成本更高

这两个字段都声明为Option<u32>且带#[serde(default, skip_serializing_if = "Option::is_none")],序列化时省略。它们遵循一套严格的空值语义:

  • None= 该提供商不报告这一指标(可能完全不支持缓存,也可能支持但响应里没带);
  • Some(0)= 提供商支持缓存,但本次请求没有任何 token 命中缓存。

这一点在Usage::zero()中体现得很清楚:核心字段归零,但两个缓存字段初始化为None("未报告"),因为不是所有提供商都支持 prompt caching;聚合工具函数会保留遇到的任何Some值,而不是被None污染,见 crates/tensorzero-types/src/usage.rs。

三、各提供商的缓存字段映射全表

cache.rs按提供商响应的"格式族"分组,给出了完整的字段来源映射。下面按类别展开,并结合仓库源码逐一印证。

3.1 OpenAI 兼容系:prompt_tokens_details.cached_tokens

OpenAI、Groq、xAI、OpenRouter 都遵循 OpenAI chat completions 响应格式,统一复用OpenAIPromptTokensDetails结构体。该结构体定义于 crates/tensorzero-types/src/usage.rs,仅有cached_tokens: Option<u32>一个字段,并被 re-export 到 crates/tensorzero-types-providers/src/openai.rs 供各兼容提供商使用。

提供商cache_read来源cache_write来源机制
OpenAIprompt_tokens_details.cached_tokens自动(>= 1024 tokens)
Groqprompt_tokens_details.cached_tokens自动
xAIprompt_tokens_details.cached_tokens自动
OpenRouterprompt_tokens_details.cached_tokens取决于底层提供商

注意这一类提供商只报告读缓存(cache_read)cache_write一律为None。转换逻辑在 crates/tensorzero-types-providers/src/conversions.rs:provider_cache_read_input_tokens取自prompt_tokens_details.and_then(|d| d.cached_tokens)provider_cache_write_input_tokens恒为None。OpenAI 系转换的完整实现可对照 crates/tensorzero-types/src/usage.rs 中的From<OpenAIUsage> for Usage

3.2 Anthropic 格式系:显式缓存控制

Anthropic 与 GCP Vertex Anthropic 使用cache_control显式标记缓存点,响应中同时给出读写两个字段;AWS Bedrock 使用cachePoint显式标记,字段名改为驼峰式。

提供商cache_read来源cache_write来源机制
Anthropiccache_read_input_tokenscache_creation_input_tokens显式(cache_control
GCP Vertex Anthropiccache_read_input_tokenscache_creation_input_tokens显式(cache_control
AWS BedrockcacheReadInputTokenCountcacheWriteInputTokenCount显式(cachePoint

以 Anthropic 为例,其用法结构体中的cache_creation_input_tokenscache_read_input_tokens均为Option<u32>,在From<AnthropicUsage> for Usage中直接一对一映射:provider_cache_read_input_tokens = cache_read_input_tokensprovider_cache_write_input_tokens = cache_creation_input_tokens,见 crates/tensorzero-core/src/providers/anthropic.rs。同文件内还有大量针对该映射的单测(如cache_creation_input_tokens: Some(100)cache_read_input_tokens: Some(200)映射到内部两个字段的断言,见 anthropic.rs),并且严格区分了Some(0)None两种语义(见 anthropic.rs)。

AWS Bedrock 的线格式类型定义在 crates/tensorzero-types-providers/src/aws_bedrock.rs,字段为cache_read_input_tokens/cache_write_input_tokensOption<i32>),经 serde 驼峰化后即文档表格中的cacheReadInputTokenCount/cacheWriteInputTokenCount;在 crates/tensorzero-core/src/providers/aws_bedrock.rs 的转换中同样映射到内部Usage的读写字段(非流式与流式分支均有对应处理)。

3.3 Google Gemini 系:cachedContentTokenCount

提供商cache_read来源cache_write来源机制
GCP Vertex GeminiusageMetadata.cachedContentTokenCount隐式(2.5+)/ 显式(CachedContent API)
Google AI Studio GeminiusageMetadata.cachedContentTokenCount隐式(2.5+)/ 显式(CachedContent API)

Gemini 系只报告 cache_read,不报告 cache_write。源码注释印证了这一字段名:GCP Vertex Gemini 的实现注释写明"Gemini 将缓存内容 token 报告为cachedContentTokenCount"(见 crates/tensorzero-core/src/providers/gcp_vertex_gemini/mod.rs),Google AI Studio 的实现注释也完全一致(见 crates/tensorzero-core/src/providers/google_ai_studio_gemini.rs)。

cache.rs还特别记录了一个重要的现实差异(截至文档标注的 2026 年 3 月):

  • GCP Vertex Gemini(aiplatform.googleapis.com)在缓存命中时会返回cachedContentTokenCount,但只是机会性(opportunistically)返回——即使 prompt 完全相同也不保证一定出现;
  • Google AI Studio(generativelanguage.googleapis.com完全不会返回该字段;
  • 两个提供商都能在字段存在时正确解析它。

这一提示对依赖 Gemini 缓存指标做成本核算的用户至关重要:字段缺失不代表缓存未生效,只是提供商未上报。

3.4 DeepSeek:独特的顶层字段格式

DeepSeek 不使用 OpenAI 的prompt_tokens_details,而是在 usage 顶层直接暴露两个缓存字段:

提供商cache_read来源cache_write来源机制
DeepSeekprompt_cache_hit_tokensprompt_cache_miss_tokens自动

对应的线格式类型DeepSeekUsage定义在 crates/tensorzero-types-providers/src/deepseek.rs,其文档注释明确指出"DeepSeek 的 usage 使用顶层prompt_cache_hit_tokensprompt_cache_miss_tokens,而不是标准的 OpenAIprompt_tokens_details.cached_tokens"。字段语义上,prompt_cache_hit_tokens是从自动缓存中读取的 token,prompt_cache_miss_tokens是不在缓存中、将供后续请求写入的 token。转换时二者分别映射到provider_cache_read_input_tokensprovider_cache_write_input_tokens,见 crates/tensorzero-types-providers/src/conversions.rs。这是全表中唯一将"缓存未命中(miss)token"直接视为 write 来源的映射,与 DeepSeek 官方计费模型一致。

3.5 Fireworks:缓存信息在 HTTP 响应头而非 JSON 体

提供商cache_read来源cache_write来源机制
FireworksHTTP 头fireworks-cached-prompt-tokens自动

Fireworks 是最特殊的映射:缓存 token 数不写在 JSON body 里,而是放在 HTTP 响应头fireworks-cached-prompt-tokens中。tensorzero-core中负责提取该头的函数是extract_fireworks_cached_prompt_tokens(见 crates/tensorzero-core/src/providers/fireworks/mod.rs),它会读取响应头并解析为Option<u32>,随后赋值给usage.provider_cache_read_input_tokens(见 fireworks/mod.rs)。该函数自带单元测试,覆盖了"无该头返回 None"、"fireworks-cached-prompt-tokens: 42解析为 Some(42)"以及"非法值返回 None"三种场景(见 fireworks/mod.rs)。

3.6 Mistral

提供商cache_read来源cache_write来源机制
Mistralprompt_tokens_details.cached_tokens自动

Mistral 走 OpenAI 兼容格式,复用OpenAIPromptTokensDetails,只报告 cache_read。

3.7 尚未在 JSON 响应中暴露缓存的提供商

提供商说明
Together透明后端缓存,响应中无 token 计数
Hyperbolic不支持 prompt caching
vLLM按 OpenAI 兼容格式解析prompt_tokens_details.cached_tokens,但并非所有部署都会上报
SGLang按 OpenAI 兼容格式解析prompt_tokens_details.cached_tokens,但并非所有部署都会上报

对于这四类提供商,cache_read/cache_write通常会落到None——即便底层实际发生了缓存,也因响应未暴露指标而无法统计。这一"诚实上报"的设计避免了把猜测数据写进观测系统。

四、从 ProviderUsage 到内部 Usage 的转换链路

cache.rs的映射表要落地,依赖每个提供商实现的From<ProviderUsage> for Usage转换。这条链路在代码中分为两层:

  1. 线格式层tensorzero-types-providers):定义各提供商响应的 serde 结构体(如OpenAIUsageDeepSeekUsageOpenAIPromptTokensDetails),只负责把 JSON 正确反序列化;
  2. 转换层:实现From<ProviderUsage> for Usage,把线格式字段映射进统一的内部Usage

对于 OpenAI 兼容系与 DeepSeek,转换集中在 crates/tensorzero-types-providers/src/conversions.rs,且每个转换都配有单测。例如其中针对 xAI 的测试覆盖了"省略prompt_tokens_details时 cache_read 应为 None"与"带cached_tokens时正确取值"两种分支(见 conversions.rs)。

对于 Anthropic 这类非 OpenAI 格式的提供商,转换实现仍保留在tensorzero-core的提供商模块中(如 anthropic.rs)。从代码结构看,这正是 AGENTS.md 所述"逐步迁移"过程中的中间状态:新类型优先放入独立 crate,历史实现按需迁移。

五、新增提供商缓存支持的 5 步清单

cache.rs为开发者提供了一份明确的操作清单(见 crates/tensorzero-types-providers/src/cache.rs),当需要为某个提供商接入或修改缓存 token 支持时,按此顺序执行:

  1. 更新映射表:在上文对应的分组表格中为提供商添加一行;
  2. 复用公共类型:如果提供商使用prompt_tokens_details.cached_tokens格式,直接在它的 usage 结构体中复用OpenAIPromptTokensDetails(从tensorzero-types-providers导入),不要重复定义;
  3. 实现字段映射:在该提供商的From<ProviderUsage> for Usage实现中,把线格式字段映射到provider_cache_read_input_tokens/provider_cache_write_input_tokens,并遵守None(不报告)与Some(0)(支持但零命中)的语义;
  4. 接入 e2e 测试:在 e2e 测试配置中把该提供商加入cache_input_tokens_inference列表;
  5. 运行缓存 token 的 e2e 测试验证:跑通非流式与流式两条用例。

六、e2e 如何验证缓存 token 映射

缓存映射不是只靠单元测试,TensorZero 还为每个提供商跑了真实的端到端验证。测试配置层面,每个提供商的 e2e 测试都会定义cache_input_tokens_inference: Vec<E2ETestProvider>字段(见 crates/tensorzero-core/tests/e2e/providers/common.rs),例如 OpenAI、Anthropic、Groq、xAI、Mistral、DeepSeek、Fireworks、Together、Hyperbolic、vLLM、SGLang、Azure、GCP Vertex Gemini、Google AI Studio 等都在各自测试文件中填充了该列表(如 mistral.rs、deepseek.rs、fireworks.rs 等)。而像 SageMaker + TGI 这种"输入不够大且不支持缓存"的组合,则显式留空并注释原因(见 aws_sagemaker_tgi.rs)。

测试宏会把该列表注入两个用例:test_cache_input_tokens_non_streamingtest_cache_input_tokens_streaming(见 common.rs),分别调用 commonv2/cache_input_tokens.rs 中的test_cache_input_tokens_non_streaming_with_provider与流式版本。

以非流式用例为例,其验证策略(见 cache_input_tokens.rs)非常直观:

  1. 两次推理,共享同一大段 system promptLARGE_SYSTEM_PROMPT被内嵌进测试,见 cache_input_tokens.rs),但 user 消息不同,以确保代理服务器记录两次独立响应;
  2. 第一次请求触发缓存写入(cache write),第二次请求命中缓存(cache read);
  3. 断言两次请求的input_tokens都大于 4000,证明缓存 token 被计入 input_tokens(若未计入,首次写入请求的 token 数不会这么大,见 cache_input_tokens.rs 的断言注释);
  4. 首次请求的缓存字段只记录不硬断言——因为自动缓存类提供商(OpenAI、Groq、xAI)在首次写入时可能不返回prompt_tokens_details,而显式缓存类提供商(Anthropic、Bedrock)会返回cache_write第二次请求则必须报告cache_read

测试还通过cache_control_extra_body()(见 cache_input_tokens.rs)按模型名注入所需的显式缓存标记:Anthropic / GCP Vertex Anthropic 使用/system/0/cache_control指针注入{"type": "ephemeral"},AWS Bedrock(claude-haiku-4-5、deepseek-r1、nova-lite-v1)使用/system/-指针注入{"cachePoint": {"type": "default"}};而自动缓存的 OpenAI、Groq、xAI 不需要任何标记也能跑通测试(见 cache_input_tokens.rs 的注释)。

七、实践要点与小结

结合 AGENTS.md 与 cache.rs,可以总结出 TensorZero 提供商缓存支持的三条核心实践准则:

  1. 以 cache.rs 为唯一权威:任何关于"哪个提供商支持缓存、字段叫什么、映射到哪"的改动,都必须先更新 crates/tensorzero-types-providers/src/cache.rs 中的映射表,保证文档与实现不漂移;
  2. 新类型进独立 crate:新建提供商相关类型一律放入tensorzero-types-providers(参考 lib.rs 的模块组织),并尽量复用OpenAIPromptTokensDetails这类公共线格式类型;
  3. 用 e2e 兜底:字段映射是否正确,最终以 commonv2/cache_input_tokens.rs 的非流式/流式用例为准,新增提供商必须加入cache_input_tokens_inference列表并跑通验证。

这套"独立类型 crate + 单一事实来源映射表 + 转换实现 + e2e 验证"的四层结构,既保证了 TensorZero 对多提供商差异的兼容性,也让新增提供商成为一项有明确清单、可验证、可追踪的工程任务。对于希望为自家私有化模型或新厂商接入 TensorZero 的开发者,这份清单就是最直接的入手指南。

【免费下载链接】tensorzeroTensorZero is an open-source LLMOps platform that unifies an LLM gateway, observability, evaluation, optimization, and experimentation.项目地址: https://gitcode.com/GitHub_Trending/te/tensorzero

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 10:43:41

MCP for Unity 提示 uv Not Found:uvx 启动不了服务器怎么修?

MCP for Unity 提示 uv Not Found&#xff1a;uvx 启动不了服务器怎么修&#xff1f; 【免费下载链接】unity-mcp Unity MCP acts as a bridge between AI assistants and your Unity Editor. Give your LLM tools to manage assets, control scenes, edit scripts, and automa…

作者头像 李华
网站建设 2026/9/15 10:42:01

JavaScript实现2048军旗版:二维数组与移动合并算法全解析

简介&#xff1a;一份基于JavaScript实现的2048军旗版游戏完整源码包&#xff0c;面向Web前端初学者和游戏开发入门者&#xff0c;可帮助理解数字拼图游戏从棋盘建模到交互响应的完整实现&#xff0c;也适用于课程设计、期末作业或想要在经典2048基础上增加自定义玩法的改版参考…

作者头像 李华
网站建设 2026/9/15 10:41:47

从React到Astro:用岛架构消除静态页面的JavaScript负担

我最近在一个维护了很久的 React 博客项目里做了一件很多人看来很“激进”的事&#xff1a;把页面里绝大多数 React 组件删掉&#xff0c;换成了 Astro 来做静态站点生成。不是 React 不行&#xff0c;而是我意识到&#xff0c;在“几乎没有用户交互”的页面里&#xff0c;让浏…

作者头像 李华
网站建设 2026/9/15 10:39:11

SAP Gateway Service Tagging 深入解析,给 OData 服务目录建立一套可搜索的语义索引

在一个运行时间足够长的 SAP S/4HANA 系统里,OData 服务数量通常会越来越多。最早可能只有几十个服务,后来随着 SAP Fiori 应用、自定义 UI5 应用、移动端、外围系统集成以及各种扩展需求不断增加,服务目录很容易膨胀到几百甚至更多。 到了这个阶段,一个很现实的问题会冒出…

作者头像 李华
网站建设 2026/9/15 10:32:41

从模拟到真实:小熊派硬件接入 IoT 平台的全过程记录

从模拟到真实&#xff1a;小熊派硬件接入 IoT 平台的全过程记录&#x1f4cc; 原创声明&#xff1a;本文基于本人课程实训期间独立开发的智慧路灯 IoT 管理平台&#xff08;Spring Boot EMQX TDengine&#xff09;实战经验整理&#xff0c;为第一手踩坑记录&#xff0c;内容已…

作者头像 李华