Skip to content

Commit 57c83d4

Browse files
authored
fix(openrouter): strip Responses reasoning IDs (#38383)
Closes #37777 --- OpenRouter can return OpenAI Responses reasoning item IDs such as `rs_*` in assistant reasoning details. Those IDs are not reliably resolvable on a later OpenRouter turn, so replaying them can make otherwise-valid multi-turn conversations fail with a provider 404. This keeps the useful reasoning payload while removing only the ephemeral Responses item IDs before serializing `reasoning_details` back into request history. Non-Responses IDs and reasoning text are left intact.
1 parent 95fe150 commit 57c83d4

2 files changed

Lines changed: 53 additions & 2 deletions

File tree

libs/partners/openrouter/langchain_openrouter/chat_models.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,19 @@ def _merge_reasoning_run(run: list[dict[str, Any]]) -> dict[str, Any]:
11251125
return merged_entry
11261126

11271127

1128+
def _strip_ephemeral_reasoning_ids(value: Any) -> Any:
1129+
"""Remove OpenAI Responses reasoning item IDs from outbound payloads."""
1130+
if isinstance(value, list):
1131+
return [_strip_ephemeral_reasoning_ids(item) for item in value]
1132+
if isinstance(value, dict):
1133+
return {
1134+
key: _strip_ephemeral_reasoning_ids(item)
1135+
for key, item in value.items()
1136+
if not (key == "id" and isinstance(item, str) and item.startswith("rs_"))
1137+
}
1138+
return value
1139+
1140+
11281141
def _merge_reasoning_details(
11291142
details: list[dict[str, Any]],
11301143
) -> list[dict[str, Any]]:
@@ -1244,8 +1257,8 @@ def _convert_message_to_dict(message: BaseMessage) -> dict[str, Any]: # noqa: C
12441257
if "reasoning_content" in message.additional_kwargs:
12451258
message_dict["reasoning"] = message.additional_kwargs["reasoning_content"]
12461259
if "reasoning_details" in message.additional_kwargs:
1247-
message_dict["reasoning_details"] = _merge_reasoning_details(
1248-
message.additional_kwargs["reasoning_details"]
1260+
message_dict["reasoning_details"] = _strip_ephemeral_reasoning_ids(
1261+
_merge_reasoning_details(message.additional_kwargs["reasoning_details"])
12491262
)
12501263
elif isinstance(message, SystemMessage):
12511264
message_dict = {"role": "system", "content": message.content}

libs/partners/openrouter/tests/unit_tests/test_chat_models.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,44 @@ def test_ai_message_distinct_reasoning_details_preserved(self) -> None:
12951295
result = _convert_message_to_dict(msg)
12961296
assert result["reasoning_details"] == details
12971297

1298+
def test_ai_message_reasoning_details_strips_responses_ids(self) -> None:
1299+
"""OpenAI Responses `rs_*` item IDs are stripped before replay."""
1300+
response_id = "rs_053a05e24b0da75e0169fa358ea9fc81908b18aff8157798c1"
1301+
details = [
1302+
{
1303+
"type": "reasoning.text",
1304+
"id": response_id,
1305+
"text": "step-by-step",
1306+
"index": 0,
1307+
}
1308+
]
1309+
msg = AIMessage(
1310+
content="Answer",
1311+
additional_kwargs={"reasoning_details": details},
1312+
)
1313+
result = _convert_message_to_dict(msg)
1314+
assert result["reasoning_details"] == [
1315+
{"type": "reasoning.text", "text": "step-by-step", "index": 0}
1316+
]
1317+
assert response_id.startswith("rs_")
1318+
assert details[0]["id"] == response_id
1319+
1320+
def test_ai_message_reasoning_details_preserves_non_responses_ids(self) -> None:
1321+
"""Non-Responses IDs are preserved in reasoning details."""
1322+
details = [
1323+
{
1324+
"type": "reasoning.text",
1325+
"id": "reasoning_abc123",
1326+
"text": "step-by-step",
1327+
}
1328+
]
1329+
msg = AIMessage(
1330+
content="Answer",
1331+
additional_kwargs={"reasoning_details": details},
1332+
)
1333+
result = _convert_message_to_dict(msg)
1334+
assert result["reasoning_details"] == details
1335+
12981336
def test_ai_message_unindexed_reasoning_details_not_merged(self) -> None:
12991337
"""Entries without an `index` are passed through unchanged."""
13001338
details = [

0 commit comments

Comments
 (0)