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 实现了一个 3 级降级链: 关键设计差异:structured_llm_call 的回退是运行时——它动态尝试每个级别并捕获异常以进行回退。ReAct 引擎的模式选择是构建时——它在开始时检查一次 _native_mode_active 并为整个循环提交到一种模式。这意味着 structured_llm_call 可以透明地从提供商特定的 400 错误中恢复,而 ReAct 依赖于模式在前期被正确选择。

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):
  • 数据库配置的模型:在 Admin → Models → Advanced settings 中切换。该标志存储在 ModelProviderModel.json_mode_enabled 上(默认值 TRUE)。
  • 环境变量配置的模型:在环境中设置 LLM_JSON_MODE_ENABLED=false
  • 效果:禁用时,abilities["json_mode"] 返回 Falseresponse_format 永远不会被传递 → 无预填充 → Bedrock 正常工作。降级链变为 native_fc → plain_text,完全跳过注定失败的 json_mode 调用。
  • 无质量损失:模型仍然返回有效的 JSON,因为系统提示指示它这样做。plain_text 级别使用 extract_json() 从自由格式内容中解析 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.5kimi-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_enabledtool_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_effortdrop_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 的聊天补全翻译,off 强制使用普通聊天补全。原生路径存在是因为桥接在唯一重要的地方有损失:它丢弃推理项,所以 GPT-5.x 智能体在每个工具轮次重新推导其思维链。直接与协议通信可以重放这些项。显式传递 reasoning_effort=None 的调用(这是 structured_llm_call 和完成信号探针所做的)保留在聊天补全上,因为不需要思考的调用没有推理状态可保留。 该原生请求的两个属性是承重的,容易出错:
  • store=false 保持上游对话无状态,include=["reasoning.encrypted_content"] 请求返回加密负载。没有 include,推理项到达时为空,重放无声地变成无操作。
  • 重放的推理项必须剥离其服务器端 id。使用 store=false 时,上游不持久化任何内容,所以将 id 回显会导致 Item with id 'rs_...' not found. Items are not persisted when 'store' is set to falseencrypted_content blob 自己携带状态,所以删除 id 没有成本(sanitize_reasoning_item)。
Bedrock。 Bedrock 托管的 Claude 遵循它解析到的行,而不是 Bedrock 托管它的事实。通过 anthropic/ 路由的中继到达时,它继承 Anthropic 协议行为,包括 LiteLLM 的 json 模式助手预填充,较新的 Bedrock 版本会拒绝。通过 OpenAI 兼容网关到达时,它解析为 openai/,不注入预填充,json_mode_enabled 可以保持开启。

表 B:冲突与解决方案

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

表 C:思维协议

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

中继/代理陷阱

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

推荐的按模型配置

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

推理工作量和思维配置

FIM One 公开两个环境变量来控制扩展思维/推理: 启用思维后,以下两种行为会自动执行,都不需要用户配置:
  1. 温度由系统处理。 在启用思维的 anthropic/ 路由上,_build_request_kwargstemperature 固定为 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_effortnone。省略该字段不等同,因为服务器默认值不是 none

结构化输出的防御性解析

即使 native_fc 正常工作,结构化输出管道也包含一个防御性解析层,用于处理来自任何提供商或兼容性层的边界情况。 DAG 规划器的 _dict_to_steps 解析器处理三个常见的边界情况:
  1. 单个对象而非数组。 某些模型返回 {"steps": {"id": "1", "task": "..."}} (单个步骤对象)而不是 {"steps": [{"id": "1", "task": "..."}]} (数组)。解析器通过检查 idtask 键来检测这种情况,并将对象包装在列表中。
  2. 双重编码的 JSON 字符串。 当结构化输出降级到 json_mode(缺乏模式强制)时,某些提供商将 steps 值作为 JSON 字符串而非原生数组返回 — 例如 {"steps": "[{\"id\": \"1\", ...}]"}。这个字符串可能还包含字面换行符(来自模型的格式化),会破坏标准的 json.loads。解析器使用 extract_json_value() (包含 _repair_json_strings)来处理:
    • JSON 字符串值内的字面换行符
    • 无效的转义序列(常见于 LaTeX 或代码内容)
    • 来自兼容性层的其他序列化问题
  3. 缺少 steps 包装器。 模型可能返回单个步骤作为顶级对象,而没有 steps 包装键。解析器在根级别检测 idtask,并相应地进行包装。
在正常操作下,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 包含以下任何内容时返回 Trueclaudeanthropicbedrock/anthropicvertex_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/anthropicvertex_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_itemsChatMessage 上的独立字段。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.5kimi-k2-thinkingdeepseek-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 回退,因此此错误仅从显式省略默认值的调用站点传播。