首页
/ gpt4free(g4f)推理字段标准化:统一 reasoning 输出格式与双向输入兼容设计

gpt4free(g4f)推理字段标准化:统一 reasoning 输出格式与双向输入兼容设计

2026-09-03 17:02:27作者:钟日瑜

在 gpt4free(下称 g4f)的多供应商聚合架构中,DeepSeek 使用 reasoning_content 字段而 OpenAI 使用 reasoning 字段来携带思维链(reasoning)内容,这一命名分歧曾让下游消费方不知该读哪个字段。本文基于仓库中的决策文档 reasoning-standardization.md 展开,讲清 g4f 如何以 OpenAI 的 reasoning 字段作为统一输出标准、同时保留双格式输入兼容,并结合 g4f/providers/response.pyg4f/client/stubs.pyg4f/Provider/template/OpenaiTemplate.py 等源码,完整梳理该标准化从「供应商响应解析」到「客户端模型序列化」再到「内部 JSON 流」的全链路实现与测试验证方式。

问题背景:两种字段命名的冲突

g4f 的核心价值在于把大量不同后端(OpenAI 兼容 API、DeepSeek、本地模型、各类逆向/免费接口)的流式响应收敛成统一的内部响应类型。但在「推理型模型」(DeepSeek-R1、o 系列、Kimi 等)的场景下,各家供应商对思维链内容在 chat completion 流式响应中的字段命名并不一致:

  • OpenAI 格式choices[0].delta.reasoning
  • DeepSeek 格式choices[0].delta.reasoning_content

决策文档明确指出,这一不一致曾造成「g4f Interference API 到底该输出哪个字段名」的困惑。最终决策为:API 输出标准化为 OpenAI 的 reasoning 字段格式,同时保持输入侧对两种格式均兼容

标准化决策与理由

文档给出了四点标准化依据,理解它们有助于把握 g4f 的整体 API 设计取向:

  1. OpenAI 兼容性:OpenAI 是 chat completion API 的事实标准;
  2. 生态兼容性:绝大多数工具库与 SDK 默认按 OpenAI 格式解析响应;
  3. 一致性:无论底层 provider 是谁,对外输出格式保持统一;
  4. 向后兼容:输入解析继续同时接受 reasoning_contentreasoning 两种格式。

这个「输出收敛、输入宽进」的策略在 g4f 中是普遍适用的模式——它避免了让每个下游客户端去感知底层 provider 的差异。

输入侧兼容:OpenaiTemplate 的双字段解析

决策文档中引用的核心实现位于 g4f/Provider/template/OpenaiTemplate.py

reasoning_content = choice.get("delta", {}).get("reasoning_content", choice.get("delta", {}).get("reasoning"))

这行代码利用 dict.get 的默认值机制实现了「先查 DeepSeek 字段,再回退到 OpenAI 字段」的兼容读取。在源码中可以确认,这一模式出现在 read_response 函数(g4f/Provider/template/OpenaiTemplate.py)的两个分支中:

流式(SSE)分支L435-L440):

reasoning_content = choice.get("delta", {}).get(
    "reasoning_content", choice.get("delta", {}).get("reasoning")
)
if reasoning_content:
    reasoning = True
    yield Reasoning(reasoning_content)

这里除了提取字段外,还维护了一个 reasoning 布尔状态:当思维链 token 流之后正式回复的 content 开始时(见 L422-L431),会先 yield Reasoning(status="") 发送一个「推理结束」标记,把思维链段落与正文段落清晰切分。从源码结构看,这个状态机使得 g4f 的 SSE 消费方能可靠地区分「模型在想」和「模型在说」。

非流式(JSON)分支L385-L390)对 choice 做同样的双字段探测,命中后以空 status 的 Reasoning 对象发出。

值得注意的是,OpenaiTemplate.create_async_generatorextra_parameters 白名单(L203-L218)中同时包含 reasoning_effortinclude_reasoning 等参数,说明输入侧标准化不止于响应解析——请求侧对各家「开启/控制推理」的参数也做了透传归一。

内部表示:Reasoning 响应类型

供应商响应被解析后,统一封装为 g4f/providers/response.py 中的 Reasoning 类型:

class Reasoning(ResponseType):
    def __init__(
        self,
        token: Optional[str] = None,
        label: Optional[str] = None,
        status: Optional[str] = None,
        is_thinking: Optional[str] = None,
    ) -> None:
        ...

Reasoning 承担三种语义角色:

  • 携带思维链 tokentoken 字段保存增量文本,get_dict() 返回 {"token": ..., "status": ...}
  • 状态标记status 用于「开始思考/停止思考」的边界信号(例如上面流式分支中 Reasoning(status="") 表示推理段结束);
  • 字符串化规则__str__is_thinkingtokenstatus/label 的优先级回退,保证无论哪种形态都能被 str() 安全消费。

正因为内部类型与字段命名彻底解耦(g4f 内部只认 Reasoning 对象,不关心供应商用哪个 key 命名),输出侧才能在序列化时统一改写为 reasoning

输出侧标准化:client stubs 统一使用 reasoning 字段

文档「Files Changed」一节提到 g4f/client/stubs.py 已由 reasoning_content 改为 reasoning 字段。当前源码中可以直接验证这一决策的落地:

流式 Delta(ChatCompletionDeltag4f/client/stubs.py

class ChatCompletionDelta(BaseModel):
    role: str
    content: Optional[str]
    reasoning: Optional[str] = None
    tool_calls: list[ToolCallModel] = None

    @classmethod
    def model_construct(cls, content: Optional[str]):
        if isinstance(content, Reasoning):
            return super().model_construct(
                role="assistant", content=None, reasoning=str(content)
            )
        ...

关键点:当 g4f 内部流产出一个 Reasoning 对象时,ChatCompletionDelta.model_construct 会把它放入 reasoning 字段并置 content=Nonefield_serializer("content") 还会兜底处理历史数据中 content 里残留 Reasoning/ToolCalls 对象的情况,确保序列化时思维链内容只会出现在 reasoning 键上。由此产出的流式响应与文档给出的 OpenAI 兼容格式一致:

{
  "id": "chatcmpl-example",
  "object": "chat.completion.chunk",
  "choices": [{
    "index": 0,
    "delta": {
      "role": "assistant",
      "reasoning": "I need to think about this step by step..."
    },
    "finish_reason": null
  }]
}

非流式 Message(ChatCompletionMessageg4f/client/stubs.py

class ChatCompletionMessage(BaseModel):
    role: str
    content: str
    reasoning: Optional[str] = None
    ...
    @classmethod
    def model_construct(
        cls, content: str, reasoning: list[Reasoning] = None, tool_calls: list = None
    ):
        if reasoning is not None and isinstance(reasoning, list):
            reasoning = "".join([str(content) for content in reasoning])
        return super().model_construct(
            role="assistant",
            content=content,
            **filter_none(tool_calls=tool_calls, reasoning=reasoning),
        )

非流式路径接收的是一段流中收集到的 Reasoning 列表,通过 "".join([str(...)]) 拼接为完整思维链文本后写入 message.reasoningfilter_none 保证无推理内容时该键不会出现在 JSON 中(不产生空字段噪音)。ChatCompletion.model_constructL266-L293)则把 reasoning 参数一路透传到 message 层。这与文档给出的非流式格式对应:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "Here's my answer",
      "reasoning": "My reasoning process was..."
    }
  }]
}

此外,用量统计中同样沿用了 reasoning 前缀的 OpenAI 命名:CompletionTokenDetails.reasoning_tokensg4f/client/stubs.py),使计费/用量维度也与输出字段命名保持同源。

另一条输出路径:内部 JSON 流中的 reasoning 事件

g4f 除 OpenAI 兼容端点外,还有 GUI/前端消费的 JSON 事件流。在 g4f/gui/server/api.py 中:

elif isinstance(chunk, Reasoning):
    yield self._format_json("reasoning", **chunk.get_dict())

即内部流中 Reasoning 对象被格式化为 {"type": "reasoning", "token": ..., "status": ...} 事件。测试用例 test_reasoning_standardization.py 中的 test_current_api_format_consistency 正是模拟了这段 _format_json 逻辑,验证该事件的字段结构稳定;而 test_openai_compatible_streaming_formattest_deepseek_compatible_format 两个用例则分别构造了两种上游命名下的 delta 结构,作为「输入兼容、输出统一」这一决策的可执行证据:

  • test_streaming_delta_with_reasoning:验证 Reasoning 对象经 ChatCompletionDelta.model_construct 后落在 delta.reasoning 上,且 role="assistant"content=None
  • test_reasoning_object_structure:验证 Reasoning.get_dict(){"token": ..., "status": ...} 结构与 str() 行为;
  • test_proposed_standardization:显式断言标准化后的 g4f 流式输出应使用 reasoning 键而非 reasoning_content

边界说明:何时仍会出现 reasoning_content

需要区分两个层面,避免误读「标准化」的范围:

  1. g4f 对外输出(client stubs 的 OpenAI 兼容端点):只输出 reasoning,不含 reasoning_content

  2. g4f 对上游供应商的转发:内部仍可能使用 reasoning_content。例如 g4f/tools/run_tools.py 中的注释明确写道,思考模式下 reasoning_content 必须回传给 DeepSeek 类 API:

    # The `reasoning_content` in the thinking mode must be passed back to the API.
    for message in messages:
        if isinstance(message, dict) and message.get("role") == "assistant" and message.get("tool_calls"):
            message["reasoning_content"] = message.get("reasoning_content", "")
    

    也就是说,工具调用循环(tool loop)中携带历史 assistant 消息回传上游时,DeepSeek 侧的字段名要求被原样保留——这是「输入/上游兼容」原则在请求侧的体现,与「输出标准化」并不矛盾。

小结与实践指引

  • 下游集成 g4f 的 OpenAI 兼容端点时,统一读取 choices[0].delta.reasoning(流式)与 choices[0].message.reasoning(非流式),不要依赖 reasoning_content
  • 对接 g4f 作为上游时(例如把 g4f 输出写入其他供应商的上下文),若目标供应商要求 reasoning_content(如 DeepSeek 思考模式),需要在转发层自行映射字段名;
  • 排查字段落点时,按「供应商 SSE → OpenaiTemplate.read_response 双字段解析 → Reasoning 对象 → ChatCompletionDelta/ChatCompletionMessage 序列化」这条链路定位即可,各环节均可在 g4f/Provider/template/OpenaiTemplate.pyg4f/providers/response.pyg4f/client/stubs.py 中直接查证,回归验证可参考 etc/unittest/test_reasoning_standardization.py
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384