Skip to content

Commit 52f0a1e

Browse files
committed
Remove metadata from nested handoff history
1 parent 98d154c commit 52f0a1e

File tree

4 files changed

+46
-110
lines changed

4 files changed

+46
-110
lines changed

docs/handoffs.md

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -100,35 +100,6 @@ handoff_obj = handoff(
100100

101101
1. This will automatically remove all tools from the history when `FAQ agent` is called.
102102

103-
### Inspecting handoff payloads
104-
105-
When you are debugging a workflow it is often useful to print the exact transcript that will be sent to the next agent. You can do this by inserting a lightweight filter that logs the `HandoffInputData` and then returns either the unmodified payload or the output from [`nest_handoff_history`](agents.extensions.handoff_filters.nest_handoff_history).
106-
107-
```python
108-
import json
109-
110-
from agents import Agent, HandoffInputData, handoff
111-
from agents.extensions.handoff_filters import nest_handoff_history
112-
113-
114-
def log_handoff_payload(data: HandoffInputData) -> HandoffInputData:
115-
nested = nest_handoff_history(data)
116-
history_items = nested.input_history if isinstance(nested.input_history, tuple) else ()
117-
for idx, item in enumerate(history_items, start=1):
118-
print(f"Turn {idx}: {json.dumps(item, indent=2, ensure_ascii=False)}")
119-
return nested
120-
121-
122-
math_agent = Agent(name="Math agent")
123-
124-
router = Agent(
125-
name="Router",
126-
handoffs=[handoff(math_agent, input_filter=log_handoff_payload)],
127-
)
128-
```
129-
130-
The new [examples/handoffs/log_handoff_history.py](https://github.com/openai/openai-agents-python/tree/main/examples/handoffs/log_handoff_history.py) script contains a complete runnable sample that prints the nested transcript every time a handoff occurs so you can see the `<CONVERSATION HISTORY>` block that will be passed to the next agent.
131-
132103
## Recommended prompts
133104

134105
To make sure that LLMs understand handoffs properly, we recommend including information about handoffs in your agents. We have a suggested prefix in [`agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX`][], or you can call [`agents.extensions.handoff_prompt.prompt_with_handoff_instructions`][] to automatically add recommended data to your prompts.

examples/handoffs/log_handoff_history.py

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/agents/extensions/handoff_filters.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ def remove_all_tools(handoff_input_data: HandoffInputData) -> HandoffInputData:
4141

4242
_CONVERSATION_HISTORY_START = "<CONVERSATION HISTORY>"
4343
_CONVERSATION_HISTORY_END = "</CONVERSATION HISTORY>"
44-
_NEST_HISTORY_METADATA_KEY = "nest_handoff_history"
45-
_NEST_HISTORY_TRANSCRIPT_KEY = "transcript"
4644

4745

4846
def nest_handoff_history(handoff_input_data: HandoffInputData) -> HandoffInputData:
@@ -100,9 +98,6 @@ def _build_developer_message(transcript: list[TResponseInputItem]) -> TResponseI
10098
return {
10199
"role": "developer",
102100
"content": content,
103-
"metadata": {
104-
_NEST_HISTORY_METADATA_KEY: {_NEST_HISTORY_TRANSCRIPT_KEY: transcript_copy}
105-
},
106101
}
107102

108103

@@ -163,20 +158,55 @@ def _extract_nested_history_transcript(
163158
) -> list[TResponseInputItem] | None:
164159
if item.get("role") != "developer":
165160
return None
166-
metadata = item.get("metadata")
167-
if not isinstance(metadata, dict):
161+
content = item.get("content")
162+
if not isinstance(content, str):
168163
return None
169-
payload = metadata.get(_NEST_HISTORY_METADATA_KEY)
170-
if not isinstance(payload, dict):
164+
start_idx = content.find(_CONVERSATION_HISTORY_START)
165+
end_idx = content.find(_CONVERSATION_HISTORY_END)
166+
if start_idx == -1 or end_idx == -1 or end_idx <= start_idx:
171167
return None
172-
transcript = payload.get(_NEST_HISTORY_TRANSCRIPT_KEY)
173-
if not isinstance(transcript, list):
168+
start_idx += len(_CONVERSATION_HISTORY_START)
169+
body = content[start_idx:end_idx]
170+
lines = [line.strip() for line in body.splitlines() if line.strip()]
171+
parsed: list[TResponseInputItem] = []
172+
for line in lines:
173+
parsed_item = _parse_summary_line(line)
174+
if parsed_item is not None:
175+
parsed.append(parsed_item)
176+
return parsed
177+
178+
179+
def _parse_summary_line(line: str) -> TResponseInputItem | None:
180+
stripped = line.strip()
181+
if not stripped:
174182
return None
175-
normalized: list[TResponseInputItem] = []
176-
for entry in transcript:
177-
if isinstance(entry, dict):
178-
normalized.append(deepcopy(entry))
179-
return normalized if normalized else []
183+
dot_index = stripped.find(".")
184+
if dot_index != -1 and stripped[:dot_index].isdigit():
185+
stripped = stripped[dot_index + 1 :].lstrip()
186+
role_part, sep, remainder = stripped.partition(":")
187+
if not sep:
188+
return None
189+
role_text = role_part.strip()
190+
if not role_text:
191+
return None
192+
role, name = _split_role_and_name(role_text)
193+
reconstructed: TResponseInputItem = {"role": role}
194+
if name:
195+
reconstructed["name"] = name
196+
content = remainder.strip()
197+
if content:
198+
reconstructed["content"] = content
199+
return reconstructed
200+
201+
202+
def _split_role_and_name(role_text: str) -> tuple[str, str | None]:
203+
if role_text.endswith(")") and "(" in role_text:
204+
open_idx = role_text.rfind("(")
205+
possible_name = role_text[open_idx + 1 : -1].strip()
206+
role_candidate = role_text[:open_idx].strip()
207+
if possible_name:
208+
return (role_candidate or "developer", possible_name)
209+
return (role_text or "developer", None)
180210

181211

182212
def _get_run_item_role(run_item: RunItem) -> str | None:

tests/test_extension_filters.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,6 @@ def test_nest_handoff_history_wraps_transcript() -> None:
247247
assert "<CONVERSATION HISTORY>" in developer_content
248248
assert "</CONVERSATION HISTORY>" in developer_content
249249
assert "Assist reply" in developer_content
250-
metadata = nested.input_history[0].get("metadata")
251-
assert isinstance(metadata, dict)
252-
history_payload = metadata.get("nest_handoff_history")
253-
assert isinstance(history_payload, dict)
254-
transcript = history_payload.get("transcript")
255-
assert isinstance(transcript, list)
256-
assert len(transcript) == 4
257-
assert transcript[0]["role"] == "user"
258-
assert transcript[1]["role"] == "assistant"
259250
assert nested.input_history[1]["role"] == "user"
260251
assert nested.input_history[1]["content"] == "Hello"
261252
assert len(nested.pre_handoff_items) == 0

0 commit comments

Comments
 (0)