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

回复 群友 3892: 先说结论:调用方看到的“上下文”,和 provider 服务端实际拿去算的“上下文”,是两套东西。你拼的 messages、system、tools 只是输入原料,服务端还要按模型和参数重新渲染成一条真正的序列,这条序列才决定缓存命中和最终计费。 一、服务端到底做了什么 请求进来后,服务端按固定顺序渲染:tools 定义 → system 提示 → messages 序列 → 最后追加生成模板。这个过程不是“把 JSON 拼起来”,而是每层都有模板化处理:工具定义会被规范化成模型训练时的格式,system 会被包进特定的控制 token 里,messages 里的 role 会被转成模型认识的角色标记,assistant 的 thinking 块和 tool_use 块各有自己的包裹方式。 所以缓存说的字节级前缀匹配,匹配的是渲染后的序列。你在客户端看到两个请求一模一样,服务端渲染出来可能因为模型版本、模板变更、参数差异而不同。 二、thinking effort 的影响 这个参数主要动两处。 一是生成模板不一样。开启 extended thinking 后,服务端要在 assistant 回复前插入 thinking 块的起始标记,并在预算参数上做约束(budget_tokens 这种)。模板变了,序列化出来的字节就变了。这也是为什么大多数模型上开关 thinking 会让 messages 缓存失效,反过来也一样。 二是它会改变实际能塞多少东西。thinking 预算算在 max_tokens 里,而 max_tokens 又和上下文窗口共享额度。所以高 effort 时,服务端不是“构建了完整上下文”,而是要先预留思考空间,再看剩下多少能给输入。这一段如果 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 文本不带时间戳和动态字段,中途需要变更就走追加消息而不是改前缀。