Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions src/agents/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -1976,23 +1976,15 @@ async def _prepare_input_with_session(
if session is None:
return input

# If the user doesn't specify an input callback and pass a list as input
if isinstance(input, list) and not session_input_callback:
raise UserError(
"When using session memory, list inputs require a "
"`RunConfig.session_input_callback` to define how they should be merged "
"with the conversation history. If you don't want to use a callback, "
"provide your input as a string instead, or disable session memory "
"(session=None) and pass a list to manage the history manually."
)

# Get previous conversation history
history = await session.get_items()

# Convert input to list format
new_input_list = ItemHelpers.input_to_new_input_list(input)

if session_input_callback is None:
# Historically we rejected list inputs without an explicit callback to avoid
# ambiguous merges; the default now appends new items to history for ergonomics.
return history + new_input_list
elif callable(session_input_callback):
res = session_input_callback(history, new_input_list)
Expand Down
26 changes: 11 additions & 15 deletions tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import pytest

from agents import Agent, RunConfig, Runner, SQLiteSession, TResponseInputItem
from agents.exceptions import UserError

from .fake_model import FakeModel
from .test_responses import get_text_message
Expand Down Expand Up @@ -371,10 +370,8 @@ async def test_sqlite_session_get_items_with_limit():

@pytest.mark.parametrize("runner_method", ["run", "run_sync", "run_streamed"])
@pytest.mark.asyncio
async def test_session_memory_rejects_both_session_and_list_input(runner_method):
"""Test that passing both a session and list input raises a UserError across all runner
methods.
"""
async def test_session_memory_appends_list_input_by_default(runner_method):
"""Test that list inputs are appended to session history when no callback is provided."""
with tempfile.TemporaryDirectory() as temp_dir:
db_path = Path(temp_dir) / "test_validation.db"
session_id = "test_validation_parametrized"
Expand All @@ -383,19 +380,18 @@ async def test_session_memory_rejects_both_session_and_list_input(runner_method)
model = FakeModel()
agent = Agent(name="test", model=model)

# Test that providing both a session and a list input raises a UserError
model.set_next_output([get_text_message("This shouldn't run")])

list_input = [
{"role": "user", "content": "Test message"},
initial_history: list[TResponseInputItem] = [
{"role": "user", "content": "Earlier message"},
{"role": "assistant", "content": "Saved reply"},
]
await session.add_items(initial_history)

list_input = [{"role": "user", "content": "Test message"}]

with pytest.raises(UserError) as exc_info:
await run_agent_async(runner_method, agent, list_input, session=session)
model.set_next_output([get_text_message("This should run")])
await run_agent_async(runner_method, agent, list_input, session=session)

# Verify the error message explains the issue
assert "list inputs require a `RunConfig.session_input_callback" in str(exc_info.value)
assert "to manage the history manually" in str(exc_info.value)
assert model.last_turn_args["input"] == initial_history + list_input

session.close()

Expand Down