Skip to main content

提供商检测

FIM One 使用 LiteLLM 作为通用适配器。core/model/openai_compatible.py 中的 _resolve_litellm_model() 函数将用户的 LLM_BASE_URL + LLM_MODEL 映射到带有提供商前缀的 LiteLLM 模型标识符。该前缀决定了 LiteLLM 如何路由请求 — 原生 API 协议(Anthropic Messages API、Gemini 等)或通用 OpenAI 兼容的 /v1/chat/completions。 解析顺序:
  1. 显式提供商(来自数据库 ModelConfig.provider 字段)— 最高优先级。如果提供商与 URL 中的已知域匹配,则不返回 api_base(LiteLLM 原生路由)。否则,api_base 设置为中继 URL。
  2. 域名匹配 KNOWN_DOMAINS — 通过主机名识别官方 API 端点。
  3. URL 路径提示 PATH_PROVIDER_HINTS — 常见于 UniAPI 等中继平台,其中路径中的 /claude 或 /anthropic 表示上游协议。
  4. 回退 — openai/ 前缀(通用 OpenAI 兼容)。
当提供商前缀是原生协议(anthropic、gemini 等)且 URL 不是官方端点时,LiteLLM 使用原生协议但将请求发送到中继的 api_base。这意味着提供商特定的行为 — 包括下面描述的 Bedrock 预填充问题 — 无论请求是发送到官方 API 还是通过中继,都会适用。
如果你的中继 URL 路径中包含 /claude,FIM One 会自动通过 Anthropic 的原生协议路由。这通常是正确的(更好的流式传输、思考支持),但意味着提供商特定的行为会适用 — 包括下面描述的 Bedrock 预填充问题。

tool_choice — 四种模式

tool_choice 参数通过 OpenAI 格式标准化。LiteLLM 在发送请求前将其转换为每个提供商的原生协议。 "auto" 和强制模式({"type":"function",...})之间的区别是 FIM One 中每个兼容性问题的关键。这两种模式由具有不同要求的完全不同的子系统使用。

tool_choice 的使用位置

两个子系统使用 tool_choice,它们以根本不同的方式使用它。

ReAct 引擎 — tool_choice=“auto”

ReAct 循环需要模型在每次迭代中做出决定:调用工具或给出最终答案。只有 "auto" 才有意义——模型可以自由选择生成 tool_calls 或文本内容。这与所有提供商、所有模型和所有模式(包括扩展思考)兼容。 ReAct 引擎在 abilities["tool_call"] = True 时使用原生函数调用(_run_native),否则回退到 JSON-in-content 模式(_run_json)。两种模式都使用 "auto"——区别在于工具是通过 tools 参数传递还是在系统提示中描述。详见 ReAct 引擎——双模式执行。

structured_llm_call——tool_choice=forced

一次性结构化提取(架构注释、DAG规划、计划分析)。强制模型调用特定的虚拟函数,从而保证输出结构化JSON。这是触发提供商特定错误的调用位置。 structured_llm_call实现了三级降级链。这三个层级以代码命名,其编号与下文结构化输出的四个保证层级并不相同: 关键的设计差异在于:structured_llm_call的回退是运行时行为——它会动态尝试每个层级,并捕获异常以继续执行后续层级。ReAct引擎的模式选择则发生在构建时——它会在开始时检查一次_native_mode_active,并在整个循环中固定使用一种模式。这意味着structured_llm_call可以透明地从提供商特定的400错误中恢复,而ReAct依赖于在开始时正确选择模式。

结构化输出:四个保证层级

“结构化输出”是一个涵盖四种不同机制的术语。它们都会生成JSON,但提供的保证并不相同。区别在于约束存在于何处:是在解码器内部——它无法生成Schema禁止的token;还是在提示词中——它只能提出要求。 T1和T2背后的机制是受约束解码。提供商会将Schema编译成语法,并在每个解码步骤中屏蔽所有会导致输出无法根据Schema解析的token。必填字段不能被跳过,因为在该字段生成之前,用于闭合对象的token不会位于允许集合中。这与“模型被客气地要求后遵守了”是完全不同类型的保证,也正因如此,T1/T2在温度为1以及较小的模型上仍然成立,而提示词遵循并不能做到这一点。

T1和T2不可互换

它们通过不同的通道提供相同的保证,而通道在三个方面都很重要。 两者的轮次形态不同。 T1返回工具调用轮次;模型已经决定要采取行动。T2返回普通的助手消息;模型以某种结构给出了回答。当调用方需要一次提取而不是智能体循环时,T2会直接表达这一点,而不是为模型虚构一个供其“调用”的虚拟函数。 强制使用T1会与思考冲突。 要从T1获取经过架构约束的载荷,通常意味着强制指定选项,可以是required或命名函数。多家提供商会拒绝在扩展思考处于启用状态时强制指定工具选项。下方的表B记录了具体情况。T2不存在此类冲突:它是响应约束,而不是工具约束,因此它是唯一一条在启用思考时仍然有效的架构约束路径。对于采用推理模型作为标准的部署而言,这才是选择T2而非T1的实际理由,而不是风格偏好。 T1需要额外一轮T2不需要的架构连接工作。 虚拟函数需要名称、描述,以及决定模型是否可以拒绝调用它。

T2的成本

受约束解码并非免费,而且你已有的schema通常并不是它所接受的schema。
  • schema子集。 OpenAI的strict mode要求每个对象都设置additionalProperties: false,并将每个属性都列入required;可选性通过与null组成联合类型来表示,而不是从required中省略。根节点必须是对象。属性总数和嵌套深度也存在上限。大多数手写schema在符合要求前都需要进行编辑。
  • 语法编译。 携带新schema的首次请求需要承担提供商侧一次性的编译延迟。复用稳定的schema可以分摊这项开销;而每个请求都生成全新的schema则无法做到这一点。
  • 新的失败面。 不愿回答的模型会返回拒答,而不是符合schema结构的对象,因此调用方需要为此增加一个分支。

没有任何层级能够保证的内容

每个层级都会约束形式,但没有任何层级能够约束真实性。T2响应可以完全符合schema且在事实上错误;即使没有一个值是正确的,受枚举约束的字段也会返回允许值中的一个,因为解码器的职责是让输出保持在语法范围内,而不是知道答案。Schema门控可以消除解析失败和字段形状错误,但不能免除对值所表达内容进行检查的必要性。
两套编号方案并不对应。 本页使用T1–T4表示上述保证层级,使用Level 1/2/3表示structured_llm_call降级链的三个阶梯,这些名称取自代码(native_fc、json_mode、plain_text)。FIM One的Level 2是json_object,它属于T3,而不是T2。这条链只有三个阶梯,是因为它完全跳过了T2,而不是因为只有三个层级。

FIM One 所处的位置

由此产生五个后果,这些是当前设计的实际限制。
  1. 链中没有可回退到的 schema 约束层级。 当 Level 1 失败时,下一层是 json_object,它只能保证文本可解析。不存在仍然强制要求字段的中间步骤。
  2. Level 1 的保证弱于其名称所暗示的程度。 未启用 strict 时,原生函数调用属于 2024 年之前的非严格形式:模型通常会遵循 schema,但不会被阻止省略必填字段、编造键,或在声明为数字时返回字符串。
  3. 在 anthropic/ 路由上,Level 2 实际上并不是真正的 T3。 Anthropic Messages API 没有 response_format,因此 LiteLLM 会通过注入 assistant prefill 来模拟 JSON mode。这是一种提示词层面的机制,使实际保证介于 T4 和 T3 之间,而不是达到 T3。下文记录的 Bedrock prefill 陷阱,就是这种模拟机制明显失败的表现;更隐蔽的代价在于,这种保证从未达到参数名称所暗示的程度。
  4. 结果不会在之后根据 schema 进行验证。 jsonschema 不是依赖项。实际执行的任何检查都位于可选的 parse_fn 中,并且由每个调用位置自行决定,其严格程度各不相同。
  5. 失败会被吸收,而不是暴露出来。 几乎所有调用方都会传入 default_value,因此链耗尽后会返回一个看似合理的对象,而不是抛出异常。StructuredCallResult.level_used 会记录生成该值的层级,但没有任何调用位置读取它;在 default_value 路径上,它还会为实际上什么都没生成的链报告 plain_text。
如何查看当前实际处于哪个层级。 structured_llm_call 会在每次调用完成后于 INFO 级别记录一行日志;该日志仅在数据通过 parse_fn 后才会输出,因此其中标示的是确实成功的层级:
完全失败时,会以 WARNING 级别记录 level=none outcome=default_value。在一天的流量中检索这两行日志,是判断缺失的 T2 层级是否给特定部署带来影响的唯一方法,因为 default_value 设计会让症状表现为质量一般的答案,而不是错误。

每个层级的提供商支持情况

此表记录的是上游 API,而不是 FIM One 的代码路径。 本页中的其他表格都会列出实现相应行为的函数,而此表无法这样做,因为 FIM One 不使用任何提供商的 T2 功能,也不会对任何提供商设置 strict。此表的作用是确保实现 T2 的决策以实际可用的能力为起点。提供商的能力变化很快,同一系列中的部分模型可能会先于其他模型获得支持;依赖某一行之前,请重新查阅提供商自己的文档。
有三个模式值得单独说明,因为它们解释了表格的结构,而不只是重复表格内容。 T3 是通用的,T2 则不是。 FIM One 支持的每个提供商都提供 json_object,而大约一半的提供商提供 Schema 层级。因此,可移植的结构化输出路径必须建立在 T3 或更低层级之上,这就是 FIM One 采用当前链路的原因。添加 T2 意味着添加按模型划分的能力标志,而不是启用一个全局开关。 始终启用思考往往会关闭 T1 通道。 GLM、Kimi 的思考模型和 deepseek-reasoner 都会限制或拒绝强制工具选择,而 Anthropic 在思考处于启用状态时也会拒绝该选择。MiniMax 是一个反例。当通道关闭时,T2 是唯一剩下的 Schema 闸门选项,这也是缺失这一层级在中国模型部署中比在 OpenAI 部署中更重要的原因。 自托管会颠倒通常的排序。 约束解码是服务栈的属性,因此一个指令遵循能力远弱于前沿模型的本地 checkpoint 也可以使用它。最需要 Schema 闸门的模型,反而最有可能获得这一能力。

Bedrock预填陷阱

当为使用anthropic/前缀解析的模型传递response_format={"type":"json_object"}时,LiteLLM会在内部注入一条助手预填消息来模拟JSON模式。Anthropic Messages API没有原生的response_format参数,所以LiteLLM通过在助手内容前加一个开括号来近似实现:
这在Anthropic的直接API上可以正常工作。但是,较新的AWS Bedrock模型版本会拒绝任何最后一条消息具有role: "assistant"的对话——他们称之为”助手消息预填”并抛出错误:
仅当同时满足以下三个条件时才会出现此错误:
  1. 模型使用anthropic/前缀解析(通过域名匹配或URL路径提示)。
  2. 传递了response_format={"type":"json_object"}(structured_llm_call中的json_mode代码路径)。
  3. 实际后端是AWS Bedrock(拒绝预填)。
通过OpenAI兼容端点的Bedrock? 如果你的Bedrock中继暴露了OpenAI兼容的/v1/chat/completions端点(AWS自己的OpenAI兼容网关或第三方代理),且URL路径不包含/claude或/anthropic,FIM One会使用openai/前缀解析。LiteLLM随后将后端视为标准OpenAI兼容服务器,直接传递response_format而不注入任何预填,服务器原生处理JSON约束。预填陷阱不适用——你无需设置json_mode_enabled=false。
这不影响原生工具调用(tool_choice="auto"配合tools=参数)。预填注入仅在response_format时发生。ReAct智能体执行完全不受影响。
如果第1级(native_fc)和第2级(json_mode)在Bedrock上都失败,系统会在第3级(plain_text)恢复。下面描述的json_mode_enabled标志消除了浪费的第2级调用。

修复方案:json_mode_enabled

每个模型的json_mode_enabled标志控制是否会尝试 Level 2(json_mode):
  • 通过 DB 配置的模型:在管理员 → 模型 → 高级设置中切换。该标志存储在ModelProviderModel.json_mode_enabled上(默认值为TRUE)。
  • 通过 ENV 配置的模型:在环境中设置LLM_JSON_MODE_ENABLED=false。
  • 效果:禁用后,abilities["json_mode"]返回False→不会传递response_format→不会进行预填充→Bedrock 可以正常工作。降级链变为native_fc → plain_text,完全跳过注定失败的json_mode调用。
  • 跳过的代价:实际上,模型仍会返回有效的 JSON,因为系统提示词要求模型这样做,并且在现代模型上,extract_json()能够可靠地解析自由格式的内容。失去的是保证,而不是输出:现在链路会在 T4 结束,此时除了提示词之外,没有任何机制约束结果。对于anthropic/路由来说,这种损失没有看起来那么大,因为模拟的 JSON 模式本来也不是解码器级别的保证。

思维模型 + 强制 tool_choice

多个提供商在启用扩展思维时拒绝强制 tool_choice,理由是固定特定函数调用与模型先进行推理的自由度相矛盾:
这是按提供商的规则,不是思维模型的通用法则。 Anthropic在协议级别强制执行,Moonshot(Kimi)的行为相同,但MiniMax在每次调用时都进行思维,仍然接受强制工具选择。下表B中的提供商能力矩阵逐个提供商记录了判决结果;不要从一行推广到另一行。 对于Anthropic模型,structured_llm_call通过在原生FC级别传递reasoning_effort=None来自动解决冲突,这会关闭该次调用的思维功能(structured.py::_call_llm)。结构化输出需要模式合规性,而不是深度推理,因此在此处禁用思维既正确又更便宜。 当思维无法通过API关闭时,native_fc在每次结构化调用时都会以400失败,耗时约十秒后链条才会降级到json_mode。Kimi是常见情况:启用思维时仅支持auto,强制工具选择需要关闭思维,而Moonshot仅通过模型id暴露此选项(kimi-k2已关闭,kimi-k2.5和kimi-k2-thinking已启用)。FIM One没有翻转此选项的参数,因此补救措施是下面的tool_choice_enabled标志。

修复:tool_choice_enabled

一个针对每个模型的 tool_choice_enabled 标志控制是否尝试 Level 1(native_fc):
  • 数据库配置的模型:在 Admin → Models → Advanced → “Native Function Calling” 中切换。该标志存储在 ModelProviderModel.tool_choice_enabled 上(默认为 TRUE)。
  • ENV 配置的模型:在环境中设置 LLM_TOOL_CHOICE_ENABLED=false。
  • 效果:禁用时,abilities["tool_choice"] 返回 False → 降级链从 Level 2(json_mode)或 Level 3(plain_text)开始,完全跳过 native_fc。这消除了不兼容模型每次结构化调用约 10 秒的性能损失。
  • ReAct 智能体不受影响:tool_choice_enabled 仅控制 structured_llm_call 中的强制工具选择。ReAct 引擎使用 tool_choice="auto"(模型自由决定),无论此设置如何都适用于所有模型。
tool_choice_enabled 和 tool_call 是独立的能力标志。tool_call(对于 OpenAICompatibleLLM 始终为 True)控制工具是否被传递给模型 — 禁用它会破坏 ReAct 智能体。tool_choice 仅控制是否尝试强制工具选择以进行结构化输出提取。
tool_choice="auto" 不受思考模式影响。ReAct 引擎专门使用 "auto",因此启用思考时智能体执行工作正常。
不要设置 abilities["tool_call"] = False 来避免此约束。这会禁用 ReAct 的 _run_native 模式(使用 tool_choice="auto" 且与思考配合良好),强制其进入不太可靠的 _run_json 模式。
提供商迁移说明: 某些第三方中继会静默丢弃不支持的参数,如 reasoning_effort(drop_params=True),因此即使配置了思考也永远不会激活。迁移到正确支持思考的提供商(Bedrock、直接 Anthropic API)时,native_fc 中的 reasoning_effort=None 确保一致的行为。无需用户操作 — 结构化输出在所有提供商中的工作方式相同。

提供商能力矩阵

本部分是每个提供商支持的功能以及FIM One如何处理这些功能的权威记录。每一行都命名了实现该行为的函数,因此此处的任何声明都可以根据代码进行验证。其他页面链接到此处而不是重复数据;当代码更改时,本部分也会随之更改。 一行描述的是提供商的协议,而不是单个模型。当同一系列中的模型存在差异时(DeepSeek聊天与推理器、Kimi启用思考与关闭思考),单元格会说明这一点。

表 A:协议路由

已配置的 base_url 和 model 如何转换为 LiteLLM 调用,以及首选接口不可用时会发生什么。 GPT-5.x 如何选择协议。 FIM_GPT5_RESPONSES_MODE 用于选择协议:native(默认值)通过 litellm.aresponses 直接调用 /v1/responses;bridge 使用 LiteLLM 的 chat-completions 转换;off 强制使用普通 chat completions。之所以提供原生路径,是因为桥接会在关键环节丢失信息:它会丢弃推理项,导致 GPT-5.x 智能体在每轮工具调用时都重新推导思维链。直接使用该协议,可以重放这些推理项。显式传入 reasoning_effort=None 的调用(structured_llm_call 和完成信号探测就是如此)会继续使用 chat completions,因为不需要思考的调用没有要保留的推理状态。例外情况是无法关闭推理的模型(gpt-6.1-*、gpt-6-astra):无论 reasoning_effort 为何,它们的 chat completions 都会拒绝函数工具,因此这些调用会继续使用 /v1/responses,并采用最低推理强度 low。出于同样原因,off 模式或不支持 /v1/responses 的端点会导致这些模型无法进行工具调用。 原生请求有两个容易出错、但至关重要的属性:
  • store=false 可确保上游对话保持无状态;include=["reasoning.encrypted_content"] 则会请求返回加密载荷。如果不包含该字段,返回的推理项将为空,重放会悄无声息地变成空操作。
  • 重放的推理项必须去除服务器端的 id。由于 store=false 时上游不会持久化任何内容,回传该 id 会导致错误:Item with id 'rs_...' not found. Items are not persisted when 'store' is set to false。encrypted_content 数据块本身就携带了状态,因此去掉 id 不会造成任何损失(sanitize_reasoning_item)。
Bedrock。 由 Bedrock 托管的 Claude 遵循其解析到的路由,而不是由 Bedrock 托管这一事实。通过路由到 anthropic/ 的中继访问时,它会沿用 Anthropic 协议行为,包括 LiteLLM 的 json-mode assistant 预填充;较新的 Bedrock 版本会拒绝这种预填充。通过 OpenAI 兼容网关访问时,它会解析为 openai/,不会注入预填充,并且可以保持 json_mode_enabled 开启。

表 B:冲突与解决方案

FIM One 发出四个 tool_choice 状态中的三个:来自 ReAct 循环的 auto(react.py::_run_native)、来自结构化输出的命名函数(structured.py::_call_llm)以及当完成信号答案重放工具有效载荷时的 none。没有调用站点发出 required;该列记录提供商对非 auto 工具选择的约束,该约束同样适用于 required 和命名函数。

表 C:思维协议

LLM_REASONING_EFFORT 接受 low、medium 和 high;任何其他值都被读作未设置(deps.py::_reasoning_effort)。FIM One 随后在线路上放置的内容是按提供商的,这就是本表记录的内容。replay 列是 reasoning_replay_policy 的返回值,它是一个小的闭合集合,包含四种状态,而不是按提供商的列表。 unsupported 和 informational_only 在线路上产生相同的字节:两者都从传出历史中剥离 reasoning_content 和 signature。它们的意图不同,因此明确进行推理但落在 unsupported 中的模型是片段表中的间隙,而不是实时错误。

中继/代理陷阱

第三方网关的故障方式与直接提供商不同,其中大多数故障是无声的。下表将症状与其机制配对,以及FIM One已经采取的措施。
支持边界。 FIM One保证本页面记录的行为适用于一方端点:OpenAI自己的API、Anthropic、Google以及任何直接提供其自有模型的供应商。第三方中继以尽力而为的方式支持,不受该保证覆盖,因为中继对请求所做的操作超出我们的控制范围,通常也超出其自身文档的范围。中继可以丢弃参数、重写历史、移除缓存断点或回答其仅部分实现的协议,在大多数情况下它返回200而不是错误。这是关于我们承诺的陈述,而不是对运行内容的限制。FIM One不维护批准主机的允许列表,这里没有任何内容受域名限制。能力由端点实际执行的操作决定:缺失的路由返回404并被记住,被忽略的include产生空推理项且重放变为无操作,被拒绝的请求为该调用回退。探测端点比从其主机名推断其能力更准确,这是保持与Azure OpenAI、企业网关和正确实现协议的自托管代理兼容的唯一方法。如果中继的行为方式回退无法捕获,使用FIM_GPT5_RESPONSES_MODE(bridge或off)或按模型的tool_choice_enabled和json_mode_enabled开关固定协议,并针对一方端点重现后再将其作为FIM One错误提交。

推荐的按模型配置

tool_choice_enabled和json_mode_enabled都可以在管理员→模型→高级设置中按模型切换。默认值都是TRUE,对大多数提供商都是正确的;仅在看到错误或浪费延迟时进行调整。哪些提供商需要调整已在上表B中记录,操作员填写的按模型视图位于模型管理。
何时更改: 如果在日志中看到structured_llm_call: native_fc call raised警告,随后是成功的json_mode提取,则该模型不受益于native_fc。为该模型禁用”原生函数调用”以消除浪费的API调用(每个结构化输出请求约10秒)。
环境变量级别覆盖适用于通过环境变量配置的所有模型(不是管理员UI):

推理工作量和思维配置

FIM One 公开两个环境变量来控制扩展思维/推理: 启用思维后,以下两种行为会自动执行,都不需要用户配置:
  1. 温度由系统处理。 在启用思维的 anthropic/ 路由上,_build_request_kwargs 将 temperature 固定为 1.0,这是 Bedrock 的要求。完全拒绝采样参数的模型(Opus 4.7 和 4.8、Fable 5、Mythos 5)会从请求中完全移除 temperature,无论是否启用思维。不要手动设置 LLM_TEMPERATURE=1。
  2. GPT-5.x 在可能的情况下将工具和推理结合在一起。 FIM One 首先探测 GPT-5.x 的 Responses 桥接,因为这是两者结合的唯一表面。没有可用 /v1/responses 路由的端点会回退到聊天完成,判断结果按端点缓存,在该路径上携带 tools 的请求会发送显式 reasoning_effort 为 none。省略该字段不等同,因为服务器默认值不是 none。

结构化输出的防御性解析

即使 native_fc 正常工作,结构化输出管道也包含一个防御性解析层,用于处理来自任何提供商或兼容性层的边界情况。 DAG 规划器的 _dict_to_steps 解析器处理三个常见的边界情况:
  1. 单个对象而非数组。 某些模型返回 {"steps": {"id": "1", "task": "..."}} (单个步骤对象)而不是 {"steps": [{"id": "1", "task": "..."}]} (数组)。解析器通过检查 id 或 task 键来检测这种情况,并将对象包装在列表中。
  2. 双重编码的 JSON 字符串。 当结构化输出降级到 json_mode(缺乏模式强制)时,某些提供商将 steps 值作为 JSON 字符串而非原生数组返回 — 例如 {"steps": "[{\"id\": \"1\", ...}]"}。这个字符串可能还包含字面换行符(来自模型的格式化),会破坏标准的 json.loads。解析器使用 extract_json_value() (包含 _repair_json_strings)来处理:
    • JSON 字符串值内的字面换行符
    • 无效的转义序列(常见于 LaTeX 或代码内容)
    • 来自兼容性层的其他序列化问题
  3. 缺少 steps 包装器。 模型可能返回单个步骤作为顶级对象,而没有 steps 包装键。解析器在根级别检测 id 和 task,并相应地进行包装。
在正常操作下,native_fc 返回正确结构化的工具调用参数,这些边界情况不会出现。防御性解析器作为安全网存在,用于自定义 BaseLLM 子类、异常的提供商行为,或结构化输出降级到 json_mode 或 plain_text 的回退场景。

提示词缓存(跨提供商)

FIM One 通过 cache_control 断点实现 Anthropic 的显式提示词缓存,同时通过提示词部分注册表从其他所有提供商的自动前缀缓存中受益。目标是实现一条单一的提示词组装路径,在所有提供商中工作,而无需按调用的提示词形状差异。

架构

fim_one.core.prompt 模块公开三个基础元素:
  • PromptSection — 一个命名片段,包含静态 content: str 或动态 content: Callable
  • PromptRegistry — 一个记忆化存储(静态片段渲染一次,动态片段每次调用重新渲染)
  • DYNAMIC_BOUNDARY — 一个哨兵标记,注册表在最后一个静态片段和第一个动态片段之间插入,以便调用者可以在缓存断点处分割渲染的提示词
ReAct 的系统提示词(JSON 模式、原生函数调用模式、综合)分为:
  • 静态前缀(~95% 的提示词)— 身份、核心指南、工具描述
  • 动态后缀 — 当前日期时间、每个请求的语言指令、交接上下文

能力检测

fim_one.core.prompt.caching.is_cache_capable(model_id) 当模型 id 包含以下任何内容时返回 True:claude、anthropic、bedrock/anthropic、vertex_ai/claude。这些提供商接收两个 role="system" 消息,第一个(静态)消息带有 cache_control: {"type": "ephemeral"}。 所有其他提供商接收一个单一的连接系统消息,没有 cache_control 字段——这是必要的,因为非 Anthropic 端点要么拒绝该字段,要么静默丢弃它,通过某些中继发送它会导致 400 unknown parameter 错误。

跨提供商覆盖情况

对于支持自动前缀缓存的所有提供商,PromptRegistry 都能「免费」带来收益——通过确保静态部分在每次调用之间保持字节内容一致(当前日期时间位于动态后缀中,而非前缀中),所有支持自动缓存的提供商都能匹配哈希并命中缓存。因此,即使不考虑 Anthropic 专属的 cache_control,Registry 也能带来基础性的无模型收益。

可观测性

每个 chat/* 响应的 done_payload 现在包括:
TurnProfiler 每轮发出一条结构化日志行:turn_cache summary | model=claude-sonnet-4-6 | read_tokens=1067 | create_tokens=0 | saved_input_tokens=961 (~90%)。这也充当中继诚实探针 — 如果你通过 API 中继路由,比较实际计费的输入 token 与 read_tokens 来检测中继是否剥离 cache_control 或保留 0.10× 折扣。 LLM 层不返回美元估计 — 定价和中继加价在上层应用,因此 LLM 层仅返回客观的 token 计数。

多轮缓存 ROI

在 Claude 4 ReAct 轮次上测量,使用默认智能体提示词: 一个包含 10 个工具的 10 次迭代 ReAct 运行,在第一次之后的每一轮可节省约 8,640 个输入 token(9 次缓存命中 × 1067 token × 90%)。Anthropic 对第一次调用的缓存写入收费 1.25 倍,因此损益平衡点在第二次调用——单次查询不会受益。

推理重放策略(无模型正确性)

扩展思考/推理块在不同提供商之间的行为不同。统一的序列化策略会破坏协议契约和自动前缀缓存。fim_one.core.prompt.reasoning.reasoning_replay_policy(model_id) 返回四个值之一,并在 OpenAICompatibleLLM._build_request_kwargs() 中控制 ChatMessage.to_openai_dict(replay_policy=...) 的行为。

四项策略

  • anthropic_thinking — Claude 系列(包括 anthropic/、bedrock/anthropic、vertex_ai/claude)。思维块必须附带 signature 重放;如果缺少或修改了签名,Anthropic 会拒绝后续轮次。
  • informational_only — 发出 CoT 但不期望重放的模型:DeepSeek 推理模式(V3.2 上的 deepseek-reasoner,以及片段表仍匹配的较早版本 deepseek-r1 和 R1-Distill ID)、Qwen QwQ、Gemini flash-thinking、OpenAI o1 / o3 / o4。它们的文档明确说明”不要在消息历史中发送 reasoning_content”。即使发送:
    • 违反提供商合约(未来版本可能开始拒绝)
    • 无声地使其自动前缀缓存失效 — 消息字节在每个轮次变化,破坏哈希值
  • openai_responses — GPT-5.x,在 gpt-5 片段上匹配。其推理状态不是文本,而是携带加密负载的不透明项序列,只有 /v1/responses 有插槽容纳它们。在该协议上,这些项逐字重放,这是保持模型在工具轮次间思维链活跃的方式。可读摘要仍从传出请求中删除,因此在 chat-completions 回退上,其行为完全像 informational_only。在信息片段之前检查,其通用 reasoning 条目否则会吞没代理标记的 GPT-5 ID。
  • unsupported — 包罗万象:无推理能力的模型(GPT-4o、Gemini 1.5、Mistral、Llama),以及 ID 不匹配任何片段的推理模型(GLM、MiniMax、Kimi、Doubao)。无论哪种方式都不应重放任何字段,因此此策略在线路上放置与 informational_only 相同的字节。这也是未知模型 ID 的安全默认值。
可读的 reasoning_content 和不透明的 reasoning_items 是 ChatMessage 上的独立字段。to_openai_dict() 根本不序列化这些项,因此它们在结构上无法泄漏到 chat-completions 请求,无论策略如何。

执行

所有策略评估都在一个地方进行(_build_request_kwargs)。ChatMessage.to_openai_dict(replay_policy=None) 保留了 A3 宽松的默认设置,以便不协调的调用者不会回退。跨提供商测试矩阵位于 tests/test_reasoning_replay_policy.py 中,反向断言证明非 Anthropic 请求不会泄露 reasoning_content。

对于用户

特性和错误行为都是自动的 — 您无需配置任何内容。工作流影响:
  • 如果您在同一对话中在 Claude 和 DeepSeek 智能体之间切换,历史记录会完整保存思考块;在下一轮中,传出消息形状会根据当前模型进行调整。
  • 如果您使用代理 / 自定义 BaseLLM 子类,请确保其模型 id 是可识别的(包含其中一个片段),否则默认的 unsupported 策略将适用 — 这是安全的,但意味着异常代理后的 Claude 可能会丢失思考重放。将模型 id 片段添加到 _CACHE_CAPABLE_MODEL_FRAGMENTS(在 core/prompt/caching.py 中)和/或推理策略查找。

故障排除

“This model does not support assistant message prefill” Bedrock + json_mode。两个解决方案:(1)设置 LLM_JSON_MODE_ENABLED=false 或在管理员模型设置中禁用 JSON Mode;或(2)如果你的 Bedrock 提供商提供 OpenAI 兼容的 /v1/chat/completions 端点,切换到该端点——FIM One 将其解析为 openai/,预填充注入永远不会发生。 “Thinking may not be enabled when tool_choice forces tool use” / “tool_choice ‘specified’ is incompatible with thinking enabled” 对于 Anthropic 模型,structured_llm_call 会自动为 native_fc 调用禁用推理。在无法通过 API 关闭推理的情况下,例如 kimi-k2.5 和 kimi-k2-thinking 或 deepseek-reasoner,在模型的高级设置中禁用”Native Function Calling”,或全局设置 LLM_TOOL_CHOICE_ENABLED=false。降级链将跳过 native_fc,改为通过 json_mode 或 plain_text 提取结构化输出。在假设推理模型存在此问题之前,请查看提供商能力矩阵的表 B;MiniMax 没有此问题。 “DAG pipeline failed: LLM ‘steps’ is not an array” LLM 返回的 steps 字段是字符串或单个对象,而不是数组。这通常意味着结构化输出降级到了 json_mode(缺乏模式强制)。检查日志中的 structured_llm_call: level=xxx——如果显示 json_mode 而不是 native_fc,则 native_fc 以静默方式失败。如果使用自定义 BaseLLM 子类,请验证它接受 reasoning_effort 关键字参数。 ReAct 意外降级到 JSON mode 检查模型的 abilities["tool_call"] 是否为 True。对于 OpenAICompatibleLLM,这总是 True,但自定义 BaseLLM 子类可能会覆盖它。通过管理员 API 中的模型详情端点进行验证。 structured_llm_call 耗尽所有级别并抛出 StructuredOutputError 模型在任何级别都无法生成可解析的 JSON。这在现代模型中很少见。检查:(1)模式是否为有效的 JSON Schema,(2)模型是否有足够的 max_tokens 来生成完整响应,(3)系统提示是否与模式指令相矛盾。DAG 规划器和分析器都提供 default_value 回退,因此此错误仅从显式省略默认值的调用站点传播。