4949from .events .event import Event
5050from .events .event import EventActions
5151from .flows .llm_flows import contents
52+ from .flows .llm_flows .functions import find_event_by_function_call_id
5253from .flows .llm_flows .functions import find_matching_function_call
5354from .memory .base_memory_service import BaseMemoryService
5455from .memory .in_memory_memory_service import InMemoryMemoryService
@@ -70,6 +71,16 @@ def _is_tool_call_or_response(event: Event) -> bool:
7071 return bool (event .get_function_calls () or event .get_function_responses ())
7172
7273
74+ def _get_function_responses_from_content (
75+ content : types .Content ,
76+ ) -> list [types .FunctionResponse ]:
77+ if not content :
78+ return []
79+ return [
80+ part .function_response for part in content .parts if part .function_response
81+ ]
82+
83+
7384def _is_transcription (event : Event ) -> bool :
7485 return (
7586 event .input_transcription is not None
@@ -341,6 +352,35 @@ def _enforce_app_name_alignment(self) -> None:
341352 self ._app_name_alignment_hint = f'{ mismatch_details } { resolution } '
342353 logger .warning ('App name mismatch detected. %s' , mismatch_details )
343354
355+ def _resolve_invocation_id (
356+ self ,
357+ session : Session ,
358+ new_message : Optional [types .Content ],
359+ invocation_id : Optional [str ],
360+ ) -> Optional [str ]:
361+ """Infers invocation_id from new_message if it is a function response."""
362+ function_responses = _get_function_responses_from_content (new_message )
363+ if not function_responses :
364+ return invocation_id
365+
366+ fc_event = find_event_by_function_call_id (
367+ session .events , function_responses [0 ].id
368+ )
369+ if not fc_event :
370+ raise ValueError (
371+ 'Function call event not found for function response id:'
372+ f' { function_responses [0 ].id } '
373+ )
374+
375+ if invocation_id and invocation_id != fc_event .invocation_id :
376+ logger .warning (
377+ 'Provided invocation_id %s is ignored because new_message has a '
378+ 'function response with invocation_id %s.' ,
379+ invocation_id ,
380+ fc_event .invocation_id ,
381+ )
382+ return fc_event .invocation_id
383+
344384 def _format_session_not_found_message (self , session_id : str ) -> str :
345385 message = f'Session not found: { session_id } '
346386 if not self ._app_name_alignment_hint :
@@ -497,42 +537,57 @@ async def _run_with_trace(
497537 session = await self ._get_or_create_session (
498538 user_id = user_id , session_id = session_id
499539 )
540+
500541 if not invocation_id and not new_message :
501542 raise ValueError (
502543 'Running an agent requires either a new_message or an '
503544 'invocation_id to resume a previous invocation. '
504545 f'Session: { session_id } , User: { user_id } '
505546 )
506547
507- if invocation_id :
508- if (
509- not self .resumability_config
510- or not self .resumability_config .is_resumable
511- ):
512- raise ValueError (
513- f'invocation_id: { invocation_id } is provided but the app is not'
514- ' resumable.'
515- )
516- invocation_context = await self ._setup_context_for_resumed_invocation (
548+ is_resumable = (
549+ self .resumability_config and self .resumability_config .is_resumable
550+ )
551+ if not is_resumable and not new_message :
552+ raise ValueError (
553+ 'Running an agent requires a new_message or a resumable app. '
554+ f'Session: { session_id } , User: { user_id } '
555+ )
556+
557+ if not is_resumable :
558+ invocation_context = await self ._setup_context_for_new_invocation (
517559 session = session ,
518560 new_message = new_message ,
519- invocation_id = invocation_id ,
520561 run_config = run_config ,
521562 state_delta = state_delta ,
522563 )
523- if invocation_context .end_of_agents .get (
524- invocation_context .agent .name
525- ):
526- # Directly return if the current agent in invocation context is
527- # already final.
528- return
529564 else :
530- invocation_context = await self ._setup_context_for_new_invocation (
531- session = session ,
532- new_message = new_message , # new_message is not None.
533- run_config = run_config ,
534- state_delta = state_delta ,
565+ invocation_id = self ._resolve_invocation_id (
566+ session , new_message , invocation_id
535567 )
568+ if not invocation_id :
569+ invocation_context = await self ._setup_context_for_new_invocation (
570+ session = session ,
571+ new_message = new_message ,
572+ run_config = run_config ,
573+ state_delta = state_delta ,
574+ )
575+ else :
576+ invocation_context = (
577+ await self ._setup_context_for_resumed_invocation (
578+ session = session ,
579+ new_message = new_message ,
580+ invocation_id = invocation_id ,
581+ run_config = run_config ,
582+ state_delta = state_delta ,
583+ )
584+ )
585+ if invocation_context .end_of_agents .get (
586+ invocation_context .agent .name
587+ ):
588+ # Directly return if the current agent in invocation context is
589+ # already final.
590+ return
536591
537592 async def execute (ctx : InvocationContext ) -> AsyncGenerator [Event ]:
538593 async with Aclosing (ctx .agent .run_async (ctx )) as agen :
0 commit comments