openai[patch]: allow specification of output format for Responses API (#31686)

This commit is contained in:
ccurme
2025-06-26 13:41:43 -04:00
committed by GitHub
parent 59c2b81627
commit 88d5f3edcc
14 changed files with 328 additions and 35 deletions

View File

@@ -649,6 +649,25 @@ class BaseChatOpenAI(BaseChatModel):
.. versionadded:: 0.3.9
"""
output_version: Literal["v0", "responses/v1"] = "v0"
"""Version of AIMessage output format to use.
This field is used to roll-out new output formats for chat model AIMessages
in a backwards-compatible way.
Supported values:
- ``"v0"``: AIMessage format as of langchain-openai 0.3.x.
- ``"responses/v1"``: Formats Responses API output
items into AIMessage content blocks.
Currently only impacts the Responses API. ``output_version="responses/v1"`` is
recommended.
.. versionadded:: 0.3.25
"""
model_config = ConfigDict(populate_by_name=True)
@model_validator(mode="before")
@@ -903,6 +922,7 @@ class BaseChatOpenAI(BaseChatModel):
schema=original_schema_obj,
metadata=metadata,
has_reasoning=has_reasoning,
output_version=self.output_version,
)
if generation_chunk:
if run_manager:
@@ -957,6 +977,7 @@ class BaseChatOpenAI(BaseChatModel):
schema=original_schema_obj,
metadata=metadata,
has_reasoning=has_reasoning,
output_version=self.output_version,
)
if generation_chunk:
if run_manager:
@@ -1096,7 +1117,10 @@ class BaseChatOpenAI(BaseChatModel):
else:
response = self.root_client.responses.create(**payload)
return _construct_lc_result_from_responses_api(
response, schema=original_schema_obj, metadata=generation_info
response,
schema=original_schema_obj,
metadata=generation_info,
output_version=self.output_version,
)
elif self.include_response_headers:
raw_response = self.client.with_raw_response.create(**payload)
@@ -1109,6 +1133,8 @@ class BaseChatOpenAI(BaseChatModel):
def _use_responses_api(self, payload: dict) -> bool:
if isinstance(self.use_responses_api, bool):
return self.use_responses_api
elif self.output_version == "responses/v1":
return True
elif self.include is not None:
return True
elif self.reasoning is not None:
@@ -1327,7 +1353,10 @@ class BaseChatOpenAI(BaseChatModel):
else:
response = await self.root_async_client.responses.create(**payload)
return _construct_lc_result_from_responses_api(
response, schema=original_schema_obj, metadata=generation_info
response,
schema=original_schema_obj,
metadata=generation_info,
output_version=self.output_version,
)
elif self.include_response_headers:
raw_response = await self.async_client.with_raw_response.create(**payload)
@@ -3540,6 +3569,7 @@ def _construct_lc_result_from_responses_api(
response: Response,
schema: Optional[type[_BM]] = None,
metadata: Optional[dict] = None,
output_version: Literal["v0", "responses/v1"] = "v0",
) -> ChatResult:
"""Construct ChatResponse from OpenAI Response API response."""
if response.error:
@@ -3676,7 +3706,10 @@ def _construct_lc_result_from_responses_api(
tool_calls=tool_calls,
invalid_tool_calls=invalid_tool_calls,
)
message = _convert_to_v03_ai_message(message)
if output_version == "v0":
message = _convert_to_v03_ai_message(message)
else:
pass
return ChatResult(generations=[ChatGeneration(message=message)])
@@ -3688,6 +3721,7 @@ def _convert_responses_chunk_to_generation_chunk(
schema: Optional[type[_BM]] = None,
metadata: Optional[dict] = None,
has_reasoning: bool = False,
output_version: Literal["v0", "responses/v1"] = "v0",
) -> tuple[int, int, int, Optional[ChatGenerationChunk]]:
def _advance(output_idx: int, sub_idx: Optional[int] = None) -> None:
"""Advance indexes tracked during streaming.
@@ -3756,12 +3790,15 @@ def _convert_responses_chunk_to_generation_chunk(
elif chunk.type == "response.output_text.done":
content.append({"id": chunk.item_id, "index": current_index})
elif chunk.type == "response.created":
response_metadata["id"] = chunk.response.id
id = chunk.response.id
response_metadata["id"] = chunk.response.id # Backwards compatibility
elif chunk.type == "response.completed":
msg = cast(
AIMessage,
(
_construct_lc_result_from_responses_api(chunk.response, schema=schema)
_construct_lc_result_from_responses_api(
chunk.response, schema=schema, output_version=output_version
)
.generations[0]
.message
),
@@ -3773,7 +3810,10 @@ def _convert_responses_chunk_to_generation_chunk(
k: v for k, v in msg.response_metadata.items() if k != "id"
}
elif chunk.type == "response.output_item.added" and chunk.item.type == "message":
id = chunk.item.id
if output_version == "v0":
id = chunk.item.id
else:
pass
elif (
chunk.type == "response.output_item.added"
and chunk.item.type == "function_call"
@@ -3868,9 +3908,13 @@ def _convert_responses_chunk_to_generation_chunk(
additional_kwargs=additional_kwargs,
id=id,
)
message = cast(
AIMessageChunk, _convert_to_v03_ai_message(message, has_reasoning=has_reasoning)
)
if output_version == "v0":
message = cast(
AIMessageChunk,
_convert_to_v03_ai_message(message, has_reasoning=has_reasoning),
)
else:
pass
return (
current_index,
current_output_index,