mirror of
https://github.com/hwchase17/langchain.git
synced 2025-07-04 12:18:24 +00:00
openai[patch]: add attribute to always use previous_response_id (#31734)
This commit is contained in:
parent
b02bd67788
commit
0bf223d6cf
@ -138,6 +138,7 @@ def test_configurable() -> None:
|
|||||||
"extra_body": None,
|
"extra_body": None,
|
||||||
"include_response_headers": False,
|
"include_response_headers": False,
|
||||||
"stream_usage": False,
|
"stream_usage": False,
|
||||||
|
"use_previous_response_id": False,
|
||||||
"use_responses_api": None,
|
"use_responses_api": None,
|
||||||
},
|
},
|
||||||
"kwargs": {
|
"kwargs": {
|
||||||
|
@ -607,6 +607,40 @@ class BaseChatOpenAI(BaseChatModel):
|
|||||||
.. versionadded:: 0.3.24
|
.. versionadded:: 0.3.24
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
use_previous_response_id: bool = False
|
||||||
|
"""If True, always pass ``previous_response_id`` using the ID of the most recent
|
||||||
|
response. Responses API only.
|
||||||
|
|
||||||
|
Input messages up to the most recent response will be dropped from request
|
||||||
|
payloads.
|
||||||
|
|
||||||
|
For example, the following two are equivalent:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
llm = ChatOpenAI(
|
||||||
|
model="o4-mini",
|
||||||
|
use_previous_response_id=True,
|
||||||
|
)
|
||||||
|
llm.invoke(
|
||||||
|
[
|
||||||
|
HumanMessage("Hello"),
|
||||||
|
AIMessage("Hi there!", response_metadata={"id": "resp_123"}),
|
||||||
|
HumanMessage("How are you?"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
llm = ChatOpenAI(
|
||||||
|
model="o4-mini",
|
||||||
|
use_responses_api=True,
|
||||||
|
)
|
||||||
|
llm.invoke([HumanMessage("How are you?")], previous_response_id="resp_123")
|
||||||
|
|
||||||
|
.. versionadded:: 0.3.26
|
||||||
|
"""
|
||||||
|
|
||||||
use_responses_api: Optional[bool] = None
|
use_responses_api: Optional[bool] = None
|
||||||
"""Whether to use the Responses API instead of the Chat API.
|
"""Whether to use the Responses API instead of the Chat API.
|
||||||
|
|
||||||
@ -1081,6 +1115,8 @@ class BaseChatOpenAI(BaseChatModel):
|
|||||||
return True
|
return True
|
||||||
elif self.truncation is not None:
|
elif self.truncation is not None:
|
||||||
return True
|
return True
|
||||||
|
elif self.use_previous_response_id:
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
return _use_responses_api(payload)
|
return _use_responses_api(payload)
|
||||||
|
|
||||||
@ -1097,6 +1133,13 @@ class BaseChatOpenAI(BaseChatModel):
|
|||||||
|
|
||||||
payload = {**self._default_params, **kwargs}
|
payload = {**self._default_params, **kwargs}
|
||||||
if self._use_responses_api(payload):
|
if self._use_responses_api(payload):
|
||||||
|
if self.use_previous_response_id:
|
||||||
|
last_messages, previous_response_id = _get_last_messages(messages)
|
||||||
|
payload_to_use = last_messages if previous_response_id else messages
|
||||||
|
if previous_response_id:
|
||||||
|
payload["previous_response_id"] = previous_response_id
|
||||||
|
payload = _construct_responses_api_payload(payload_to_use, payload)
|
||||||
|
else:
|
||||||
payload = _construct_responses_api_payload(messages, payload)
|
payload = _construct_responses_api_payload(messages, payload)
|
||||||
else:
|
else:
|
||||||
payload["messages"] = [_convert_message_to_dict(m) for m in messages]
|
payload["messages"] = [_convert_message_to_dict(m) for m in messages]
|
||||||
@ -3202,6 +3245,30 @@ def _use_responses_api(payload: dict) -> bool:
|
|||||||
return bool(uses_builtin_tools or responses_only_args.intersection(payload))
|
return bool(uses_builtin_tools or responses_only_args.intersection(payload))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_last_messages(
|
||||||
|
messages: Sequence[BaseMessage],
|
||||||
|
) -> tuple[Sequence[BaseMessage], Optional[str]]:
|
||||||
|
"""
|
||||||
|
Return
|
||||||
|
1. Every message after the most-recent AIMessage that has a non-empty
|
||||||
|
``response_metadata["id"]`` (may be an empty list),
|
||||||
|
2. That id.
|
||||||
|
|
||||||
|
If the most-recent AIMessage does not have an id (or there is no
|
||||||
|
AIMessage at all) the entire conversation is returned together with ``None``.
|
||||||
|
"""
|
||||||
|
for i in range(len(messages) - 1, -1, -1):
|
||||||
|
msg = messages[i]
|
||||||
|
if isinstance(msg, AIMessage):
|
||||||
|
response_id = msg.response_metadata.get("id")
|
||||||
|
if response_id:
|
||||||
|
return messages[i + 1 :], response_id
|
||||||
|
else:
|
||||||
|
return messages, None
|
||||||
|
|
||||||
|
return messages, None
|
||||||
|
|
||||||
|
|
||||||
def _construct_responses_api_payload(
|
def _construct_responses_api_payload(
|
||||||
messages: Sequence[BaseMessage], payload: dict
|
messages: Sequence[BaseMessage], payload: dict
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
@ -12,6 +12,7 @@ from langchain_core.load import dumps, loads
|
|||||||
from langchain_core.messages import (
|
from langchain_core.messages import (
|
||||||
AIMessage,
|
AIMessage,
|
||||||
AIMessageChunk,
|
AIMessageChunk,
|
||||||
|
BaseMessage,
|
||||||
FunctionMessage,
|
FunctionMessage,
|
||||||
HumanMessage,
|
HumanMessage,
|
||||||
InvalidToolCall,
|
InvalidToolCall,
|
||||||
@ -59,6 +60,7 @@ from langchain_openai.chat_models.base import (
|
|||||||
_convert_to_openai_response_format,
|
_convert_to_openai_response_format,
|
||||||
_create_usage_metadata,
|
_create_usage_metadata,
|
||||||
_format_message_content,
|
_format_message_content,
|
||||||
|
_get_last_messages,
|
||||||
_oai_structured_outputs_parser,
|
_oai_structured_outputs_parser,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2151,3 +2153,102 @@ def test_compat() -> None:
|
|||||||
message_v03_output = _convert_to_v03_ai_message(message)
|
message_v03_output = _convert_to_v03_ai_message(message)
|
||||||
assert message_v03_output == message_v03
|
assert message_v03_output == message_v03
|
||||||
assert message_v03_output is not message_v03
|
assert message_v03_output is not message_v03
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_last_messages() -> None:
|
||||||
|
messages: list[BaseMessage] = [HumanMessage("Hello")]
|
||||||
|
last_messages, previous_response_id = _get_last_messages(messages)
|
||||||
|
assert last_messages == [HumanMessage("Hello")]
|
||||||
|
assert previous_response_id is None
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
HumanMessage("Hello"),
|
||||||
|
AIMessage("Hi there!", response_metadata={"id": "resp_123"}),
|
||||||
|
HumanMessage("How are you?"),
|
||||||
|
]
|
||||||
|
|
||||||
|
last_messages, previous_response_id = _get_last_messages(messages)
|
||||||
|
assert last_messages == [HumanMessage("How are you?")]
|
||||||
|
assert previous_response_id == "resp_123"
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
HumanMessage("Hello"),
|
||||||
|
AIMessage("Hi there!", response_metadata={"id": "resp_123"}),
|
||||||
|
HumanMessage("How are you?"),
|
||||||
|
AIMessage("Well thanks.", response_metadata={"id": "resp_456"}),
|
||||||
|
HumanMessage("Great."),
|
||||||
|
]
|
||||||
|
last_messages, previous_response_id = _get_last_messages(messages)
|
||||||
|
assert last_messages == [HumanMessage("Great.")]
|
||||||
|
assert previous_response_id == "resp_456"
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
HumanMessage("Hello"),
|
||||||
|
AIMessage("Hi there!", response_metadata={"id": "resp_123"}),
|
||||||
|
HumanMessage("What's the weather?"),
|
||||||
|
AIMessage(
|
||||||
|
"",
|
||||||
|
response_metadata={"id": "resp_456"},
|
||||||
|
tool_calls=[
|
||||||
|
{
|
||||||
|
"type": "tool_call",
|
||||||
|
"name": "get_weather",
|
||||||
|
"id": "call_123",
|
||||||
|
"args": {"location": "San Francisco"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ToolMessage("It's sunny.", tool_call_id="call_123"),
|
||||||
|
]
|
||||||
|
last_messages, previous_response_id = _get_last_messages(messages)
|
||||||
|
assert last_messages == [ToolMessage("It's sunny.", tool_call_id="call_123")]
|
||||||
|
assert previous_response_id == "resp_456"
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
HumanMessage("Hello"),
|
||||||
|
AIMessage("Hi there!", response_metadata={"id": "resp_123"}),
|
||||||
|
HumanMessage("How are you?"),
|
||||||
|
AIMessage("Well thanks.", response_metadata={"id": "resp_456"}),
|
||||||
|
HumanMessage("Good."),
|
||||||
|
HumanMessage("Great."),
|
||||||
|
]
|
||||||
|
last_messages, previous_response_id = _get_last_messages(messages)
|
||||||
|
assert last_messages == [HumanMessage("Good."), HumanMessage("Great.")]
|
||||||
|
assert previous_response_id == "resp_456"
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
HumanMessage("Hello"),
|
||||||
|
AIMessage("Hi there!", response_metadata={"id": "resp_123"}),
|
||||||
|
]
|
||||||
|
last_messages, response_id = _get_last_messages(messages)
|
||||||
|
assert last_messages == []
|
||||||
|
assert response_id == "resp_123"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_request_payload_use_previous_response_id() -> None:
|
||||||
|
# Default - don't use previous_response ID
|
||||||
|
llm = ChatOpenAI(model="o4-mini", use_responses_api=True)
|
||||||
|
messages = [
|
||||||
|
HumanMessage("Hello"),
|
||||||
|
AIMessage("Hi there!", response_metadata={"id": "resp_123"}),
|
||||||
|
HumanMessage("How are you?"),
|
||||||
|
]
|
||||||
|
payload = llm._get_request_payload(messages)
|
||||||
|
assert "previous_response_id" not in payload
|
||||||
|
assert len(payload["input"]) == 3
|
||||||
|
|
||||||
|
# Use previous response ID
|
||||||
|
llm = ChatOpenAI(
|
||||||
|
model="o4-mini",
|
||||||
|
# Specifying use_previous_response_id automatically engages Responses API
|
||||||
|
use_previous_response_id=True,
|
||||||
|
)
|
||||||
|
payload = llm._get_request_payload(messages)
|
||||||
|
assert payload["previous_response_id"] == "resp_123"
|
||||||
|
assert len(payload["input"]) == 1
|
||||||
|
|
||||||
|
# Check single message
|
||||||
|
messages = [HumanMessage("Hello")]
|
||||||
|
payload = llm._get_request_payload(messages)
|
||||||
|
assert "previous_response_id" not in payload
|
||||||
|
assert len(payload["input"]) == 1
|
||||||
|
Loading…
Reference in New Issue
Block a user