-
Notifications
You must be signed in to change notification settings - Fork 4.1k
fix: add reasoning-stripping handoff helpers #2749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Sequence | ||
|
|
||
| from ..handoffs import ( | ||
| HandoffInputData, | ||
| default_handoff_history_mapper, | ||
|
|
@@ -24,6 +26,8 @@ | |
|
|
||
| __all__ = [ | ||
| "remove_all_tools", | ||
| "remove_reasoning_items", | ||
| "strip_reasoning_items", | ||
| "nest_handoff_history", | ||
| "default_handoff_history_mapper", | ||
| ] | ||
|
|
@@ -49,6 +53,45 @@ def remove_all_tools(handoff_input_data: HandoffInputData) -> HandoffInputData: | |
| ) | ||
|
|
||
|
|
||
| def strip_reasoning_items(items: Sequence[TResponseInputItem]) -> list[TResponseInputItem]: | ||
| """Remove reasoning items from plain input history. | ||
|
|
||
| When the last reasoning item is stripped, assistant message IDs become orphaned and can trigger | ||
| Responses API validation errors on the next turn. In that case, strip those assistant IDs too. | ||
| """ | ||
|
|
||
| filtered_items: list[TResponseInputItem] = [] | ||
| removed_reasoning = False | ||
|
|
||
| for item in items: | ||
| if item.get("type") == "reasoning": | ||
| removed_reasoning = True | ||
| continue | ||
| filtered_items.append(item) | ||
|
|
||
| if removed_reasoning: | ||
| return _strip_orphaned_assistant_ids(filtered_items) | ||
| return filtered_items | ||
|
|
||
|
|
||
| def remove_reasoning_items(handoff_input_data: HandoffInputData) -> HandoffInputData: | ||
| """Filters out reasoning items while preserving ordinary messages.""" | ||
|
|
||
| history = handoff_input_data.input_history | ||
| filtered_history = ( | ||
| tuple(strip_reasoning_items(history)) if isinstance(history, tuple) else history | ||
| ) | ||
| filtered_pre_handoff_items = _remove_reasoning_from_items(handoff_input_data.pre_handoff_items) | ||
| filtered_new_items = _remove_reasoning_from_items(handoff_input_data.new_items) | ||
|
|
||
| return HandoffInputData( | ||
| input_history=filtered_history, | ||
| pre_handoff_items=filtered_pre_handoff_items, | ||
| new_items=filtered_new_items, | ||
| run_context=handoff_input_data.run_context, | ||
|
Comment on lines
+87
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| ) | ||
|
|
||
|
|
||
| def _remove_tools_from_items(items: tuple[RunItem, ...]) -> tuple[RunItem, ...]: | ||
| filtered_items = [] | ||
| for item in items: | ||
|
|
@@ -69,6 +112,15 @@ def _remove_tools_from_items(items: tuple[RunItem, ...]) -> tuple[RunItem, ...]: | |
| return tuple(filtered_items) | ||
|
|
||
|
|
||
| def _remove_reasoning_from_items(items: tuple[RunItem, ...]) -> tuple[RunItem, ...]: | ||
| filtered_items = [] | ||
| for item in items: | ||
| if isinstance(item, ReasoningItem): | ||
| continue | ||
| filtered_items.append(item) | ||
| return tuple(filtered_items) | ||
|
|
||
|
|
||
| def _remove_tool_types_from_input( | ||
| items: tuple[TResponseInputItem, ...], | ||
| ) -> tuple[TResponseInputItem, ...]: | ||
|
|
@@ -95,3 +147,16 @@ def _remove_tool_types_from_input( | |
| continue | ||
| filtered_items.append(item) | ||
| return tuple(filtered_items) | ||
|
|
||
|
|
||
| def _strip_orphaned_assistant_ids(items: list[TResponseInputItem]) -> list[TResponseInputItem]: | ||
| cleaned: list[TResponseInputItem] = [] | ||
| for item in items: | ||
| if ( | ||
| item.get("role") == "assistant" | ||
| and item.get("type") == "message" | ||
| and "id" in item | ||
| ): | ||
| item = {k: v for k, v in item.items() if k != "id"} | ||
| cleaned.append(item) | ||
| return cleaned | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove_reasoning_itemsremovesReasoningItementries fromnew_itemsbut never sanitizes assistant message IDs in those same run items. In handoffs where a turn emits both reasoning and an assistant message,execute_handoffsusesfiltered.new_itemsfor the next model input, andMessageOutputItem.to_input_item()preserves the messageid; after reasoning is dropped, that ID can still be orphaned and trigger the Responses API validation error this helper is intended to avoid. The same orphaned-ID cleanup needs to be applied to message run items (or to derivedinput_items) when reasoning is removed fromnew_items.Useful? React with 👍 / 👎.