LangChain中的语言模型是一个Runnable对象。所有的语言模型类型(包括基于文本补齐的Completion模型和基于多角色参与的Chat模型)都继承自抽象类BaseLanguageModelBaseChatModel是所有Chat模型的基类。ChatOpenAI的基类BaseChatOpenAI就继承自BaseChatModel。上篇文章着重介绍BaseLanguageModelBaseChatModel这两个基类的设计,这文章将会介绍ChatOpenAI。

作为Chat模型的基类,BaseChatModel最终利用定义其中的_generate抽象方法将基于“非流式输出”的实现下放给它的继承类型。作为一个具体的Chat模型类,原则上只要提供该抽象方法的实现就可以了。如果需要提供针对“流式输出”的支持,还需要重写_stream方法。接下我们介绍ChatOpenAI(是langchain_openai.ChatOpenAI而不是langchain_community.chat_models.ChatOpenAI)这个典型的客户端,看看LangChain中一个具体的模型类是如何实现的。

1. OpenAI客户端

基于OpenAI的LLM以Web API的方式对外提供服务,OpenAI开发平台提供了针对这些API的开发文档。OpenAI 在2025 年 3 月发布并全面推行的Responses API(/v1/responses)是其 API 架构的一次重大进化。它不再仅仅是简单的文本补全,而是作为Assistants API 的正式继任者,将 Chat Completion 的灵活性与强大的智能体能力结合在一起。

与传统的 Chat Completion 不同,Responses API 本质上是有状态的。它采用Conversation(对话对象):代替之前的 Thread,在服务端自动维护对话历史。在多轮对话中,我们只需传入上一次响应的 ID(previous_response_id),系统会自动回溯上下文。这不仅减少了每次请求发送的 Token 数量,还提升了推理的连贯性。它还具备服务端压缩功能,能自动优化长对话,防止因历史记录过长导致超出上下文窗口或费用激增。

最新的OpenAI采用标准的面向资源的RESTful API,每种资源类型关联一个独立的URL路径,具体的资源会赋予一个唯一的标识,此标识在资源创建的API调用中返回。针对资源的操作会严格采用语义对应HTTP方法,资源标识会包含在请求URL中用于标识操作的目标资源。

ChatOpenAI采用的OpenAI客户端类型指的是openai这个包中的OpenAI和AsyncOpenAI,它们本质上是对一个httpx.Client和httpx.AsyncClient对象的封装。OpenAI面向资源的Response API以及针对它们的客户端的设计和使用并非本系列文章的重点,所以就不在此对它们进行赘述了。

2. BaseChatOpenAI

ChatOpenAI继承自抽象类BaseChatOpenAI,后者派生于BaseChatModelBaseChatOpenAI实现了_generate方法,并在该方法中利用客户端以HTTP的方式远程调用OpenAI API,然后将返回转换成ChatResult。它也重写了_stream方法,并按照预知的结构对响应输出流进行精准的“截断”,并将截取的内容转换成ChatGenerationChunk及时反馈给调用方。_stream方法针对的是传统的Chat Completion模式,所以它还针对最新的Resonse API定义了_stream_responses方法。

class BaseChatOpenAI(BaseChatModel):
    def _generate(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> ChatResult

    def _stream(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> Iterator[ChatGenerationChunk]	

    def _stream_responses(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> Iterator[ChatGenerationChunk]

BaseChatOpenAI也同时重写了_agenerate_astream方法,并且定义了针对Response API的_astream_responses方法针对异步提供了更优的实现。 BaseChatOpenAI定义了众多配置字段和属性,我们接下来分类介绍它们。

1.1 模型标识

BaseChatOpenAImodel_name字段用于指定目标模型的名称,model_kwargs字段存放没有被独立字段定义的模型参数,它们会作为参数透透传给 OpenAI API。tiktoken_model_name字段表示计算 Token 数量采用的分词器名称。如果未设置,默认使用model_name

class BaseChatOpenAI(BaseChatModel):
    model_name: str = Field(default="gpt-3.5-turbo", alias="model")
    model_kwargs: dict[str, Any] = Field(default_factory=dict)
    tiktoken_model_name: str | None = None

1.2 安全认证于网络设置

如下的字段用于设置网络通信、代理及 API 访问密钥。openai_api_key字段用于指定访问 OpenAI API 的密钥,默认从环境变量OPENAI_API_KEY读取 。从该字段的定义可以看出,除了直接以字符串形式指定密钥之外,我们还可以指定一个用于获取密钥的可执行对象。

openai_organization对应的 OpenAI 组织 ID。在 OpenAI 的生态系统中,“组织”是一个管理层级的核心概念,我们可以将其理解为 API 资源的“顶层容器”或“计费单位”。一个用户可以同时属于多个组织,每个组织都有独立的账单和配额。通过设置openai_organization字段,我们可以明确指出当前的 API 请求产生的费用应该由哪一个组织支付。

class BaseChatOpenAI(BaseChatModel):
    openai_api_key: (
        SecretStr | None | Callable[[], str] | Callable[[], Awaitable[str]]
    ) = Field(
        alias="api_key", default_factory=secret_from_env("OPENAI_API_KEY", default=None)
    )
    openai_organization: str | None = Field(default=None, alias="organization")
    openai_api_base: str | None = Field(default=None, alias="base_url")
    openai_proxy: str | None = Field(default_factory=from_env("OPENAI_PROXY", default=None)) 

openai_api_base字段表示API 的基础 URL,openai_proxy用于设置网络代理地址,默认从环境变量OPENAI_PROXY中读取。default_headers/default_query字段用于设置在每次 API 请求中默认携带的 HTTP 请求头或查询参数。

1.3 多样性输出

如下这些字段直接控制模型输出多样性、长度和质量。max_tokens字段限制模型生成的最大Token数量。stop字段提供一个“停止符序列”,当生成内容包含这些字符时,模型将停止输出,且停止符本身不会包含在返回结果中。我们可以利用它来防止越界,比如在 Few-shot提示词中,如果你定义了“Question: {question}/ Answer:{anwser}”这样的格式,那么就可以设置stop=[“Question:”]防止模型在回答完后自己又编造一个新的问题。我们也可以利用它来控制输出格式,比如在生成 JSON 或特定代码块时,设置stop=[“\n”]可以让模型只输出单行内容。

class BaseChatOpenAI(BaseChatModel):	
    max_tokens: int | None = Field(default=None)	 
    stop: list[str] | str | None = Field(default=None, alias="stop_sequences")
    temperature: float | None = None
    presence_penalty: float | None = None
    frequency_penalty: float | None = None
    logit_bias: dict[int, int] | None = None
    top_p: float | None = None
    reasoning_effort: str | None = None

    logprobs: bool | None = None
    top_logprobs: int | None = None

temperature字段表示采样温度。值越高输出越随机,值越低输出越确定。top-p字段决定了模型在选择下一个词时,候选词范围的大小。模型会将所有可能的词按概率从高到低排序,然后从上往下累加,直到总概率达到top-p的值位置。OpenAI 官方文档建议在temperaturetop_p之间二选一进行调整,不要同时大幅改动两者。

presence_penaltyfrequency_penalty对应于两种“惩罚策略”,前者根据一个词在文中已经出现的频率(次数)来施加惩罚。一个词出现的次数越多,它下次被选中的概率就降得越低。这种惩罚能减少字词重复,有效解决模型“卡带”(反复输出同一句话或词组)的问题。当我们发现模型输出非常啰嗦,或者总是重复某些特定短语时,可以调高此参数(建议范围 0.1 到 1.0)。后者只关注一个词是否出现过,而不关心它出现了多少次。只要这个词在文中出现过(哪怕只有一次),它就会受到一个固定的惩罚。它鼓励引入新话题,迫使模型去寻找没用过的词汇和概念,而不是在现有的论述中打转。这适合头脑风暴、创意写作或对话。它会让模型显得更有活力,更倾向于转换视角。

logit_bias字段可用于调整特定Token出现的概率,用于强制生成或禁止某些词汇。这是对模型分词层的直接干预,是控制词汇出现概率的“暴力手段”。模型在生成每个词之前,会给词库里所有的词(数万个)打分。logit_bias允许你给特定的 Token ID 加分或减分。该字段的取值范围为-100 到 100。-100 (极致打压)相当于完全封杀该词,模型绝不会输出它。+100 (极致诱导)相当于钦定该词,模型几乎必然输出它。我们可以利用它实现敏感词/竞品名禁用,如果你不希望模型提到某个特定品牌,可以将其 Token ID 设为 -100。也可以利用它引导特定单字回复,比如让模型只能回答“是”或“否”。

reasoning_effort字段控制模型推理的思考程度。这是 OpenAI针对o1等具备“思维链”推理能力的新一代模型推出的专用参数。o1 类模型在回答前会进行“思考”,reasoning_effort决定了模型在给最终答案前愿意花多少“脑力”去思考。该字段具有如下三个选项,它允许开发者在准确性和响应成本(时间/费用)之间进行权衡。对于简单的文本总结,设为low即可;对于复杂的架构设计,则建议设为high。

  • low:快速思考。适合逻辑较简单、对响应速度要求高的任务,且消耗的推理 Token较少。
  • medium:默认平衡状态。
  • high:深度思考,适合解决复杂的数学、代码重构或高难度逻辑证明,但响应会变慢,且可能消耗更多 Token。

logprobstop_logprobs字段是用于获取模型生成每个词时的确定性或概率分布的专业工具。在 OpenAI 的API中,它们通常用于模型诊断、分类任务或幻觉检测。前者是一个布尔值,决定模型是否返回生成结果中每个Token的对数概率。如果一个回答的平均logprob非常低(比如很多 -5 以上的数值),说明模型在“胡编乱造”或非常不确定。当开启了logprobs=True后,top_logprobs字段决定了除了模型选中的那个词之外,还要额外返回排名靠前的多少个候选词及其概率。比如设置top_logprobs=3,对于生成的每一个位置,OpenAI 不仅告诉你它选了哪个词,还会告诉你当时概率排名前三的分别是哪些词,以及它们的概率。

1.4 运行行为与策略

如下几个字段控制模型如何执行、重试以及返回数据。streaming字段决定决定是否以流的形式返回结果,stream_usage字段则决定了流式输出时是否包含Token使用统计。max_retriesrequest_timeout字段分别表示请求失败时的最大重试次数和请求的超时时间设置 。disabled_params字段提供了一个显式禁用的参数字典,防止某些不支持的参数被传入API。

class BaseChatOpenAI(BaseChatModel):
    streaming: bool = False
    stream_usage: bool | None = None
    max_retries: int | None = None
    request_timeout: float | tuple[float, float] | Any | None = Field(
        default=None, alias="timeout"
    )
    disabled_params: dict[str, Any] | None = Field(default=None)

1.5 客户端于底层传输

BaseChatOpenAI调用OpenAI API的客户端存储在client/async_client字段中。 root_clientroot_async_client的引入是为了解决“配置继承”与“动态参数覆盖”之间的矛盾。我们可以将它们理解为模型的“原始配置快照”。在复杂的链式调用中,LangChain 经常需要根据不同的任务动态修改模型参数(如调用bind方法),这时就会产生两层客户端。

root_client/root_async_client作为底层基石,存储了最基础、最全局的配置,包括API Key、Base URL、Organization以及你通过http_client传入的底层连接池等。它在整个BaseChatOpenAI对象的生命周期内保持稳定。client/async_client作为执行快照,通常是对root_client/root_async_client的引用,但在某些特定的执行上下文中,可能会携带临时的、仅针对单次调用的配置(如特定的default_headers或extra_query)。

class BaseChatOpenAI(BaseChatModel):
    client: Any = Field(default=None, exclude=True)
    async_client: Any = Field(default=None, exclude=True)
    root_client: Any = Field(default=None, exclude=True)
    root_async_client: Any = Field(default=None, exclude=True)

    http_client: Any | None = Field(default=None, exclude=True)    
    http_async_client: Any | None = Field(default=None, exclude=True)
    
    default_headers: Mapping[str, str] | None = None
    default_query: Mapping[str, object] | None = None
    include_response_headers: bool = False

我们可以通过设置http_client/http_async_client字段来指定客户端使用的httpx.Client/httpx.AsyncClient对象。对于每次API调用,default_headers/default_query提供的HTTP 头和查询字符串都会附加到请求中。include_response_headers字段决定是否在返回结果中包含完整的HTTP响应头。

1.6 新特性与高级实验字段

如下这些字段对应OpenAI近期推出的新功能。service_tier字段决定使用哪种服务层级,例如设置为scale以使用预留容量,而default才表示采用标准的按量计费。store字段为布尔值,如果为true,OpenAI 会在后台存储请求和响应,常用于在OpenAI Dashboard中进行模型微调或性能评估。truncation字段定义在达到模型上下文限制时如何截断历史消息,具体的设置取决于你所处的具体应用场景(如数据库处理、数据挖掘工具、UI设计或自然语言处理)。

class BaseChatOpenAI(BaseChatModel):
    service_tier: str | None = None
    store: bool | None = None
    truncation: str | None = None
    use_previous_response_id: bool = False   
    use_responses_api: bool | None = None

use_responses_api字段决定使用 OpenAI 新推出Response API,还是传统的 Chat Completion 接口。use_previous_response_id字段与特定的状态保持或连续生成机制有关,其核心作用是告诉 OpenAI:当前的请求并不是一个孤立的新对话开启,而是对上一个响应的延续、引用或修正。当模型在流式输出过程中因为max_tokens限制或其他中断原因停止,但我们希望它从中断的地方丝滑接龙时,通过关联previous_response_id可以使模型可以检索到上一次生成的内部状态,确保上下文的连贯性,而不仅仅是简单的文本拼接。

1.7 补遗

BaseOpenAI的include字段是一个输出过滤器。它的核心作用控制AIMessage对象的additional_kwargs中的元数据。当 OpenAI 返回响应时,API 会附带大量元数据(如 Token 统计、模型指纹、Logprobs、系统指纹等)。include允许你以“白名单”的形式定义:“我只关心这些特定字段,请把它们提取出来”。

extra_body字段允许我们向 API 请求的 JSON 体中注入任何未在标准参数中定义的字段,用于适配非官方代理或新的API特性 。n字段可以控制 生成的回答数量。seed字段用于指定随机种子,如果设置了相同的种子和参数,模型将尝试返回高度确定的结果,这对结果复现和单元测试至关重要 。verbosity字段控制日志输出的详细程度,辅助开发者在控制台追踪请求过程。

class BaseChatOpenAI(BaseChatModel):
    include_response_headers: bool = False
    n: int | None = None
    verbosity: str | None = None

BaseChatOpenAI通过重写了bind_toolwith_structured_output方法对工具绑定和结构化输出提供支持。经过with_structured_output方法封装后,原来以AIMessage形式的返回的内容直接转换成指定类型的数据对象。此方法自动处理了包含提示词生成、API 调用和结果解析的整个闭环。BaseChatOpenAI还定义了如下的get_num_tokens_from_messages方法精确计算Token消耗。

class BaseChatOpenAI(BaseChatModel)
    def bind_tools(
        self,
        tools: Sequence[dict[str, Any] | type | Callable | BaseTool],
        *,
        tool_choice: dict | str | bool | None = None,
        strict: bool | None = None,
        parallel_tool_calls: bool | None = None,
        response_format: _DictOrPydanticClass | None = None,
        **kwargs: Any,
    ) -> Runnable[LanguageModelInput, AIMessage]

    def with_structured_output(
        self,
        schema: _DictOrPydanticClass | None = None,
        *,
        method: Literal[
            "function_calling", "json_mode", "json_schema"
        ] = "function_calling",
        include_raw: bool = False,
        strict: bool | None = None,
        tools: list | None = None,
        **kwargs: Any,
    ) -> Runnable[LanguageModelInput, _DictOrPydantic]	

    def get_num_tokens_from_messages(
        self,
        messages: Sequence[BaseMessage],
        tools: Sequence[dict[str, Any] | type | Callable | BaseTool] | None = None,
    ) -> 

3. ChatOpenAI

BaseChatOpenAI针对传统的Chat Completion API和最新的Response API分别定义了_stream/_astream_astream_responses/_astream_responses方法。ChatOpenAI重写的_stream/_astream方法根据是否使用Response API的设置,实现了针对上述两组方法的路由。它重写的with_structured_output基本上就是直接调用基类的同名方法。

class ChatOpenAI(BaseChatOpenAI):
     def _stream(self, *args: Any, **kwargs: Any) -> Iterator[ChatGenerationChunk]:
        if self._use_responses_api({**kwargs, **self.model_kwargs}):
            return super()._stream_responses(*args, **kwargs)
        return super()._stream(*args, **kwargs)

    async def _astream(
        self, *args: Any, **kwargs: Any
    ) -> AsyncIterator[ChatGenerationChunk]:
        if self._use_responses_api({**kwargs, **self.model_kwargs}):
            async for chunk in super()._astream_responses(*args, **kwargs):
                yield chunk
        else:
            async for chunk in super()._astream(*args, **kwargs):
                yield chunk

    def with_structured_output(
        self,
        schema: _DictOrPydanticClass | None = None,
        *,
        method: Literal["function_calling", "json_mode", "json_schema"] = "json_schema",
        include_raw: bool = False,
        strict: bool | None = None,
        tools: list | None = None,
        **kwargs: Any,
    ) -> Runnable[LanguageModelInput, _DictOrPydantic]:
        return super().with_structured_output(
            schema,
            method=method,
            include_raw=include_raw,
            strict=strict,
            tools=tools,
            **kwargs,
        )

4. 请求和响应的解析

我们总结一下ChatOpenAIinvoke方法总体的处理流程。它会将作为原始输入LanguageModelInput对象(可以是一个PromptValue、字符串或者一个MessageLikeRepresentation序列)转换成一个消息列表,并作为参数调用generate方法。后者利用客户端调用OpenAI API,它会创建一个字典作为输入参数。对于这个作为API参数的字典,其核心成员“messages”就是由上述的消息列表生成的。ChatOpenAI对象很多的字段和调用时指定的关键字参数也会被添加到该字典中。如果没有采用流式输出,generate方法会通过解析完整的相应内容生成一个LLMResult对象。invoke方法返回的内容就来源于这个LLMResult对象。

我们可以利用如下这个例子加深对generate方法处理请求和解析响应的逻辑的理解。如代码片段所示,我们创建了一个基于模型“gpt-5.2-chat”的ChatOpenAI对象。在初始化该对象的时候,我们指定了OpenAI客户但使用的httpx.Client对象,该对象附加了两个“钩子”输出HTTP请求和响应的内容。我们还将参数n设置为2,这样会得到两个“答案”。

from typing import Any
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, BaseMessage
from langchain_core.outputs import ChatGeneration
from httpx import Client, Request, Response
import json

def print_json(content: Any, prefix: str = ""):
   if isinstance(content, str):
       print(f"{prefix}:\n{json.dumps(json.loads(content), indent=2, ensure_ascii=False)}")
   elif hasattr(content,"model_dump"):
       print_json(content.model_dump(), prefix=prefix)
   else:
       print(f"{prefix}:\n{json.dumps(content, indent=2, ensure_ascii=False)}")   

def log_raw_response(response:Response):
    print_json(response.read().decode('utf-8'), prefix="Response")

def log_raw_request(request: Request):
    print_json(request.content.decode('utf-8'), prefix="Request")

client = Client(
    verify=False, 
    event_hooks={
        'response': [log_raw_response],
        'request': [log_raw_request]
        })

model = ChatOpenAI(
    model="gpt-5.2-chat",
    base_url="…",
    api_key= lambda: "…",
    n= 2,
    http_client = client,
)

memssages:list[BaseMessage] = [HumanMessage(content="写一个关于猫的笑话,要求在50字以内")]
result = model.generate(messages=[memssages])
print("Result:")
print_json(result.llm_output, prefix="LLM Output")
print(f"Run Info: {result.run}")

chatGenerations: list[ChatGeneration] = []
for generations in result.generations:
    for generation in generations:
        print_json(generation, prefix="ChatGeneration")

我们创建了一个HumanMessage对象,并基于它构建了一个两层消息列表作为调用generate方法的输入。在得到作为结果的LLMResult对象后,我们输出它的llm_outputgenerations字段的内容。程序执行后,我们会从输出中得到调用OpenAI API对应HTTP请求的主体内容:

{
  "messages": [
    {
      "content": "写一个关于猫的笑话,要求在50字以内",
      "role": "user"
    }
  ],
  "model": "gpt-5.2-chat",
  "n": 2,
  "stream": false
}

返回的两个“答案(笑话)”对应着响应内容的“choices”节点,具体的内容以消息的形式封装在choice的messages字段中。每个choice节点还通过index、finish_reason和logprobs字段提供了当前choice所在的索引、结束原因和对数概率。它的content_filter_results字段提供了针对“仇恨(hate)”、“自残(self-harm)”、“色情(sexual)”和“暴力(voilence)”内容的审查/过滤结果。除了作为“核心内容”的choices节点,响应内容还提供了额外的元数据用于表示时间戳(created)、运行ID(id)、模型标识(model)和消费的Token(usage)。prompt_filter_results字段提供了针对提示词的内容审核/过滤,与content_filter_results具有相同的结构。

{
  "choices": [
    {
      "content_filter_results": {
        "hate": {
          "filtered": false,
          "severity": "safe"
        },
        "self_harm": {
          "filtered": false,
          "severity": "safe"
        },
        "sexual": {
          "filtered": false,
          "severity": "safe"
        },
        "violence": {
          "filtered": false,
          "severity": "safe"
        }
      },
      "finish_reason": "stop",
      "index": 0,
      "logprobs": null,
      "message": {
        "annotations": [],
        "content": "猫说要减肥,每天只吃鱼,结果越吃越胖,因为是“低脂”的鱼油版。",
        "refusal": null,
        "role": "assistant"
      }
    },
    {
      "content_filter_results": {
        "hate": {
          "filtered": false,
          "severity": "safe"
        },
        "self_harm": {
          "filtered": false,
          "severity": "safe"
        },
        "sexual": {
          "filtered": false,
          "severity": "safe"
        },
        "violence": {
          "filtered": false,
          "severity": "safe"
        }
      },
      "finish_reason": "stop",
      "index": 1,
      "logprobs": null,
      "message": {
        "annotations": [],
        "content": "我家猫减肥失败,因为它的运动项目只有翻身和偷吃。",
        "refusal": null,
        "role": "assistant"
      }
    }
  ],
  "created": 1771987537,
  "id": "chatcmpl-DCzC5YyyRov8DYRXIq0QalX1zq76R",
  "model": "gpt-5.2-chat-2025-12-11",
  "object": "chat.completion",
  "prompt_filter_results": [
    {
      "prompt_index": 0,
      "content_filter_results": {
        "hate": {
          "filtered": false,
          "severity": "safe"
        },
        "self_harm": {
          "filtered": false,
          "severity": "safe"
        },
        "sexual": {
          "filtered": false,
          "severity": "safe"
        },
        "violence": {
          "filtered": false,
          "severity": "safe"
        }
      }
    }
  ],
  "system_fingerprint": null,
  "usage": {
    "completion_tokens": 320,
    "completion_tokens_details": {
      "accepted_prediction_tokens": 0,
      "audio_tokens": 0,
      "reasoning_tokens": 256,
      "rejected_prediction_tokens": 0
    },
    "prompt_tokens": 19,
    "prompt_tokens_details": {
      "audio_tokens": 0,
      "cached_tokens": 0
    },
    "total_tokens": 339
  }
}

如下所示的是LLMResult对象llm_output字段携带的核心内容。它token_usage字段的内容来源于响应的usage节点,表示模型名称的model_name字段取自响应的model节点。

{
  "token_usage": {
    "completion_tokens": 320,
    "prompt_tokens": 19,
    "total_tokens": 339,
    "completion_tokens_details": {
      "accepted_prediction_tokens": 0,
      "audio_tokens": 0,
      "reasoning_tokens": 256,
      "rejected_prediction_tokens": 0
    },
    "prompt_tokens_details": {
      "audio_tokens": 0,
      "cached_tokens": 0
    }
  },
  "model_name": "gpt-5.2-chat"
}

LLMResultgenerations字段包含如下两个ChatGeneration对象,内容来源于响应的两个“choice”。

{
  "text": "猫说要减肥,每天只吃鱼,结果越吃越胖,因为是“低脂”的鱼油版。",
  "generation_info": {
    "finish_reason": "stop",
    "logprobs": null
  },
  "type": "ChatGeneration",
  "message": {
    "content": "猫说要减肥,每天只吃鱼,结果越吃越胖,因为是“低脂”的鱼油版。",
    "additional_kwargs": {
      "refusal": null
    },
    "response_metadata": {
      "finish_reason": "stop",
      "logprobs": null
    },
    "type": "ai",
    "name": null,
    "id": "lc_run--019c92af-fc0b-7ee3-a1a6-4e0ebc4e1aaa-0"
  }
}
{
  "text": "我家猫减肥失败,因为它的运动项目只有翻身和偷吃。",
  "generation_info": {
    "finish_reason": "stop",
    "logprobs": null
  },
  "type": "ChatGeneration",
  "message": {
    "content": "我家猫减肥失败,因为它的运动项目只有翻身和偷吃。",
    "additional_kwargs": {},
    "response_metadata": {
      "finish_reason": "stop",
      "logprobs": null
    },
    "type": "ai",
    "name": null,
    "id": "lc_run--019c92af-fc0b-7ee3-a1a6-4e0ebc4e1aaa-1"
  }
}

ChatOpenAI默认并没有使用最新的Response API,我们可以在创建ChatOpenAI时按照如下的方式利用use_responses_api参数打开此开关。值得一提的是,Responses API为了优化性能和简化逻辑,仅支持单次生成,所以不能同时指定参数n。如果需要获取多个不同结果,目前唯一的办法是发起多次独立的 API 请求。

model = ChatOpenAI(
    model="gpt-5.2-chat",
    base_url="…",
    api_key= lambda: "…",
    # n= 2,
    http_client = client,
    use_responses_api=True,
)

ChatOpenAI针对Response API会生成不同的请求,具体的结构如下所示(“messages”字段变成了“input”)。

{
  "input": [
    {
      "content": "写一个关于猫的笑话,要求在50字以内",
      "role": "user"
    }
  ],
  "model": "gpt-5.2-chat",
  "stream": false
}

如下所示的是响应的内容,与上面做一下对比会发现结构相差很大。

{
  "id": "resp_0244a43b175c230100699e7c2727948194b8b5fe6d75634f99",
  "object": "response",
  "created_at": 1771994151,
  "status": "completed",
  "background": false,
  "completed_at": 1771994156,
  "content_filters": [
    {
      "blocked": false,
      "source_type": "prompt",
      "content_filter_raw": [],
      "content_filter_results": {
        "hate": {
          "filtered": false,
          "severity": "safe"
        },
        "sexual": {
          "filtered": false,
          "severity": "safe"
        },
        "self_harm": {
          "filtered": false,
          "severity": "safe"
        },
        "violence": {
          "filtered": false,
          "severity": "safe"
        },
        "jailbreak": {
          "filtered": false,
          "detected": false
        }
      },
      "content_filter_offsets": {
        "start_offset": 30,
        "end_offset": 48,
        "check_offset": 0
      }
    },
    {
      "blocked": false,
      "source_type": "completion",
      "content_filter_raw": [],
      "content_filter_results": {
        "self_harm": {
          "filtered": false,
          "severity": "safe"
        },
        "sexual": {
          "filtered": false,
          "severity": "safe"
        },
        "violence": {
          "filtered": false,
          "severity": "safe"
        },
        "hate": {
          "filtered": false,
          "severity": "safe"
        },
        "protected_material_text": {
          "filtered": false,
          "detected": false
        },
        "protected_material_code": {
          "filtered": false,
          "detected": false
        }
      },
      "content_filter_offsets": {
        "start_offset": 0,
        "end_offset": 494,
        "check_offset": 0
      }
    }
  ],
  "error": null,
  "frequency_penalty": 0.0,
  "incomplete_details": null,
  "instructions": null,
  "max_output_tokens": null,
  "max_tool_calls": null,
  "model": "gpt-5.2-chat",
  "output": [
    {
      "id": "rs_0244a43b175c230100699e7c27b01c8194b3ca6ef956f4b94a",
      "type": "reasoning",
      "summary": []
    },
    {
      "id": "msg_0244a43b175c230100699e7c2babfc8194bb4eae86135c172f",
      "type": "message",
      "status": "completed",
      "content": [
        {
          "type": "output_text",
          "annotations": [],
          "logprobs": [],
          "text": "我问猫为什么总爱睡觉。  \n猫说:我在为下辈子当狮子攒精力。"
        }
      ],
      "role": "assistant"
    }
  ],
  "parallel_tool_calls": true,
  "presence_penalty": 0.0,
  "previous_response_id": null,
  "prompt_cache_key": null,
  "prompt_cache_retention": null,
  "reasoning": {
    "effort": "medium",
    "summary": null
  },
  "safety_identifier": null,
  "service_tier": "default",
  "store": true,
  "temperature": 1.0,
  "text": {
    "format": {
      "type": "text"
    },
    "verbosity": "medium"
  },
  "tool_choice": "auto",
  "tools": [],
  "top_logprobs": 0,
  "top_p": 0.85,
  "truncation": "disabled",
  "usage": {
    "input_tokens": 19,
    "input_tokens_details": {
      "cached_tokens": 0
    },
    "output_tokens": 201,
    "output_tokens_details": {
      "reasoning_tokens": 128
    },
    "total_tokens": 220
  },
  "user": null,
  "metadata": {}
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐