5454)
5555from .lifecycle import RunHooks
5656from .logger import logger
57- from .memory import Session
57+ from .memory import Session , SessionInputCallback
5858from .model_settings import ModelSettings
5959from .models .interface import Model , ModelProvider
6060from .models .multi_provider import MultiProvider
@@ -179,6 +179,13 @@ class RunConfig:
179179 An optional dictionary of additional metadata to include with the trace.
180180 """
181181
182+ session_input_callback : SessionInputCallback | None = None
183+ """Defines how to handle session history when new input is provided.
184+ - `None` (default): The new input is appended to the session history.
185+ - `SessionInputCallback`: A custom function that receives the history and new input, and
186+ returns the desired combined list of items.
187+ """
188+
182189 call_model_input_filter : CallModelInputFilter | None = None
183190 """
184191 Optional callback that is invoked immediately before calling the model. It receives the current
@@ -413,7 +420,9 @@ async def run(
413420
414421 # Keep original user input separate from session-prepared input
415422 original_user_input = input
416- prepared_input = await self ._prepare_input_with_session (input , session )
423+ prepared_input = await self ._prepare_input_with_session (
424+ input , session , run_config .session_input_callback
425+ )
417426
418427 tool_use_tracker = AgentToolUseTracker ()
419428
@@ -781,7 +790,9 @@ async def _start_streaming(
781790
782791 try :
783792 # Prepare input with session if enabled
784- prepared_input = await AgentRunner ._prepare_input_with_session (starting_input , session )
793+ prepared_input = await AgentRunner ._prepare_input_with_session (
794+ starting_input , session , run_config .session_input_callback
795+ )
785796
786797 # Update the streamed result with the prepared input
787798 streamed_result .input = prepared_input
@@ -1474,19 +1485,20 @@ async def _prepare_input_with_session(
14741485 cls ,
14751486 input : str | list [TResponseInputItem ],
14761487 session : Session | None ,
1488+ session_input_callback : SessionInputCallback | None ,
14771489 ) -> str | list [TResponseInputItem ]:
14781490 """Prepare input by combining it with session history if enabled."""
14791491 if session is None :
14801492 return input
14811493
1482- # Validate that we don't have both a session and a list input, as this creates
1483- # ambiguity about whether the list should append to or replace existing session history
1484- if isinstance (input , list ):
1494+ # If the user doesn't specify an input callback and pass a list as input
1495+ if isinstance (input , list ) and not session_input_callback :
14851496 raise UserError (
1486- "Cannot provide both a session and a list of input items. "
1487- "When using session memory, provide only a string input to append to the "
1488- "conversation, or use session=None and provide a list to manually manage "
1489- "conversation history."
1497+ "When using session memory, list inputs require a "
1498+ "`RunConfig.session_input_callback` to define how they should be merged "
1499+ "with the conversation history. If you don't want to use a callback, "
1500+ "provide your input as a string instead, or disable session memory "
1501+ "(session=None) and pass a list to manage the history manually."
14901502 )
14911503
14921504 # Get previous conversation history
@@ -1495,10 +1507,18 @@ async def _prepare_input_with_session(
14951507 # Convert input to list format
14961508 new_input_list = ItemHelpers .input_to_new_input_list (input )
14971509
1498- # Combine history with new input
1499- combined_input = history + new_input_list
1500-
1501- return combined_input
1510+ if session_input_callback is None :
1511+ return history + new_input_list
1512+ elif callable (session_input_callback ):
1513+ res = session_input_callback (history , new_input_list )
1514+ if inspect .isawaitable (res ):
1515+ return await res
1516+ return res
1517+ else :
1518+ raise UserError (
1519+ f"Invalid `session_input_callback` value: { session_input_callback } . "
1520+ "Choose between `None` or a custom callable function."
1521+ )
15021522
15031523 @classmethod
15041524 async def _save_result_to_session (
@@ -1507,7 +1527,11 @@ async def _save_result_to_session(
15071527 original_input : str | list [TResponseInputItem ],
15081528 new_items : list [RunItem ],
15091529 ) -> None :
1510- """Save the conversation turn to session."""
1530+ """
1531+ Save the conversation turn to session.
1532+ It does not account for any filtering or modification performed by
1533+ `RunConfig.session_input_callback`.
1534+ """
15111535 if session is None :
15121536 return
15131537
0 commit comments