Skip to content

Commit 5161d6f

Browse files
committed
fix(openrouter): strip Responses reasoning IDs
1 parent 95fe150 commit 5161d6f

2 files changed

Lines changed: 51 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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,42 @@ 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+
details = [
1301+
{
1302+
"type": "reasoning.text",
1303+
"id": "rs_053a05e24b0da75e0169fa358ea9fc81908b18aff8157798c1",
1304+
"text": "step-by-step",
1305+
"index": 0,
1306+
}
1307+
]
1308+
msg = AIMessage(
1309+
content="Answer",
1310+
additional_kwargs={"reasoning_details": details},
1311+
)
1312+
result = _convert_message_to_dict(msg)
1313+
assert result["reasoning_details"] == [
1314+
{"type": "reasoning.text", "text": "step-by-step", "index": 0}
1315+
]
1316+
assert details[0]["id"].startswith("rs_")
1317+
1318+
def test_ai_message_reasoning_details_preserves_non_responses_ids(self) -> None:
1319+
"""Non-Responses IDs are preserved in reasoning details."""
1320+
details = [
1321+
{
1322+
"type": "reasoning.text",
1323+
"id": "reasoning_abc123",
1324+
"text": "step-by-step",
1325+
}
1326+
]
1327+
msg = AIMessage(
1328+
content="Answer",
1329+
additional_kwargs={"reasoning_details": details},
1330+
)
1331+
result = _convert_message_to_dict(msg)
1332+
assert result["reasoning_details"] == details
1333+
12981334
def test_ai_message_unindexed_reasoning_details_not_merged(self) -> None:
12991335
"""Entries without an `index` are passed through unchanged."""
13001336
details = [

0 commit comments

Comments
 (0)