[LangChain语言模型组件的设计与实现]OpenAI Chat模型的设计与实现
LangChain中的语言模型是一个Runnable对象。所有的语言模型类型(包括基于文本补齐的Completion模型和基于多角色参与的Chat模型)都继承自抽象类BaseLanguageModel,BaseChatModel是所有Chat模型的基类。ChatOpenAI的基类BaseChatOpenAI就继承自BaseChatModel。上篇文章着重介绍BaseLanguageModel和BaseChatModel这两个基类的设计,这文章将会介绍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,后者派生于BaseChatModel。BaseChatOpenAI实现了_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 模型标识
BaseChatOpenAI的model_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 官方文档建议在temperature和top_p之间二选一进行调整,不要同时大幅改动两者。
presence_penalty和frequency_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。
logprobs和top_logprobs字段是用于获取模型生成每个词时的确定性或概率分布的专业工具。在 OpenAI 的API中,它们通常用于模型诊断、分类任务或幻觉检测。前者是一个布尔值,决定模型是否返回生成结果中每个Token的对数概率。如果一个回答的平均logprob非常低(比如很多 -5 以上的数值),说明模型在“胡编乱造”或非常不确定。当开启了logprobs=True后,top_logprobs字段决定了除了模型选中的那个词之外,还要额外返回排名靠前的多少个候选词及其概率。比如设置top_logprobs=3,对于生成的每一个位置,OpenAI 不仅告诉你它选了哪个词,还会告诉你当时概率排名前三的分别是哪些词,以及它们的概率。
1.4 运行行为与策略
如下几个字段控制模型如何执行、重试以及返回数据。streaming字段决定决定是否以流的形式返回结果,stream_usage字段则决定了流式输出时是否包含Token使用统计。max_retries和request_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_client和root_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_tool和with_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. 请求和响应的解析
我们总结一下ChatOpenAI的invoke方法总体的处理流程。它会将作为原始输入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_output和generations字段的内容。程序执行后,我们会从输出中得到调用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"
}
LLMResult的generations字段包含如下两个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": {}
}
更多推荐



所有评论(0)