https://jiajia.fun/qa/7413851f-%E6%88%91%E5%88%9A%E5%88%9A%E7%9C%8B%E5%88%B0-context-%E7%9A%84%E5%AE%8C%E6%95%B4%E6%9E%84%E5%BB%BA%E5%8E%9F%E6%9D%A5%E5%9C%A8-llm-provider-%E7%9A%84%E6%9C%8D%E5%8A%A1%E7%AB%AF-%E8%AF%A6%E7%BB%86%E8%AF%B4%E4%B8%80%E8%AF%B4%E8%BF%99%E4%B8%AA-thinking-%E7%BE%A4%E5%8F%8B-3889-tools-stream-completion

# 我刚刚看到 context 的完整构建原来在 llm provider 的服务端？详细说一说这个。thinking 群友 3889，tools，stream/completion 这些参数是怎么影响服务端对上下文的构建的。

回复 群友 3892： 先说结论：调用方看到的“上下文”，和 provider 服务端实际拿去算的“上下文”，是两套东西。你拼的 messages、system、tools 只是输入原料，服务端还要按模型和参数重新渲染成一条真正的序列，这条序列才决定缓存命中和最终计费。 一、服务端到底做了什么 请求进来后，服务端按固定顺序渲染：tools 定义 → system 提示 → messages 序列 → 最后追加生成模板。这个过程不是“把 JSON 拼起来”，而是每层都有模板化处理：工具定义会被规范化成模型训练时的格式，system 会被包进特定的控制 token 里，messages 里的 role 会被转成模型认识的角色标记，assistant 的 thinking 块和 tool\_use 块各有自己的包裹方式。 所以缓存说的字节级前缀匹配，匹配的是渲染后的序列。你在客户端看到两个请求一模一样，服务端渲染出来可能因为模型版本、模板变更、参数差异而不同。 二、thinking 群友 3889 的影响 这个参数主要动两处。 一是生成模板不一样。开启 extended thinking 后，服务端要在 assistant 回复前插入 thinking 块的起始标记，并在预算参数上做约束（budget\_tokens 这种）。模板变了，序列化出来的字节就变了。这也是为什么大多数模型上开关 thinking 会让 messages 缓存失效，反过来也一样。 二是它会改变实际能塞多少东西。thinking 预算算在 max\_tokens 里，而 max\_tokens 又和上下文窗口共享额度。所以高 群友 3889 时，服务端不是“构建了完整上下文”，而是要先预留思考空间，再看剩下多少能给输入。这一段如果 provider 做了截断策略，被截掉的部分根本不会进入渲染序列，自然也不进缓存。 三、tools 的影响 tools 在最前面，影响最大。增删一个工具、改一个描述、调一下顺序、JSON schema 里 key 顺序变了，整条前缀全部作废，system 和 messages 一起失效。 另外还有一层容易忽略的：工具定义在服务端会被规范化。比如可选字段补默认值、schema 里字段重排、描述文本按模板包裹。这意味着两件事：客户端传的东西即使“语义相同”，渲染后也可能不一致；反过来，你做一些无意义的调整（比如 schema 里换一下属性声明顺序），服务端规范化后可能反而稳定。 四、stream 对上下文构建的影响 这个基本没有影响。stream 只是响应侧传输方式，输入渲染完全一致。要说有关系只有两点：一是 TTL 从请求开始算，流式慢慢吐的时候缓存时钟在走，一个长响应回来，5 分钟 TTL 可能已经所剩无几；二是请求排队时，第一个请求还没返回，并发的后续请求读不到缓存，只能当预热。 五、completion 侧的影响 max\_tokens 变小不会让输入缓存失效，但会影响服务端是否做上下文截断。真正会动前缀的是这些参数：切模型（缓存是模型作用域，全清）、改 thinking 配置、改 tools 集合、改 system 文本、改 stop sequences（部分实现会写进渲染模板）。 六、如何实际验证 不要靠猜。看 usage 里的 cache\_read\_input\_tokens 和 cache\_creation\_input\_tokens，连续几轮 read 一直是 0 就是静默失效了。然后逐字节 diff 两个本该一致的请求响应，找第一个分歧点。工程上更稳的做法是把渲染结果当作不可控的黑盒，只控制输入侧的不变量：会话开始一次性冻结 tools 集合和顺序，system 文本不带时间戳和动态字段，中途需要变更就走追加消息而不是改前缀。
