4848
4949
5050async def pre_run_process (self , process_func , new_message , user_id , session_id ):
51+ """Pre-run hook invoked before agent execution.
52+
53+ Iterates over all ``parts`` of ``new_message`` and, when a ``part`` contains
54+ ``inline_data`` and uploading is enabled, calls ``process_func`` to process
55+ the data (for example, upload to TOS and rewrite with an accessible URL).
56+ Typically used together with the ``intercept_new_message`` decorator.
57+
58+ Args:
59+ self: Runner instance.
60+ process_func: An async processing function with a signature like
61+ ``(part, app_name, user_id, session_id)`` used to handle
62+ ``inline_data`` in the message (e.g., upload to TOS).
63+ new_message (google.genai.types.Content): Incoming user message.
64+ user_id (str): User identifier.
65+ session_id (str): Session identifier.
66+
67+ Returns:
68+ None
69+
70+ Raises:
71+ Exception: Propagated if ``process_func`` raises and does not handle it.
72+ """
5173 if new_message .parts :
5274 for part in new_message .parts :
5375 if part .inline_data and self .upload_inline_data_to_tos :
@@ -60,10 +82,42 @@ async def pre_run_process(self, process_func, new_message, user_id, session_id):
6082
6183
6284def post_run_process (self ):
85+ """Post-run hook executed after agent run.
86+
87+ This is currently a no-op placeholder and can be extended to perform
88+ cleanup or finalize logic after a run.
89+
90+ Args:
91+ self: Runner instance.
92+
93+ Returns:
94+ None
95+
96+ Raises:
97+ None
98+ """
6399 return
64100
65101
66102def intercept_new_message (process_func ):
103+ """Create a decorator to insert pre/post hooks around ``run_async`` calls.
104+
105+ Internally it invokes :func:`pre_run_process` to preprocess the incoming
106+ message (e.g., upload image/video inline data to TOS), then iterates the
107+ underlying event stream and finally calls :func:`post_run_process`.
108+
109+ Args:
110+ process_func: Async function used to process ``inline_data`` (typically
111+ ``_upload_image_to_tos``).
112+
113+ Returns:
114+ Callable: A decorator that can wrap ``run_async``.
115+
116+ Raises:
117+ Exception: May propagate exceptions raised by the wrapped function or
118+ the pre-processing step.
119+ """
120+
67121 def decorator (func ):
68122 @functools .wraps (func )
69123 async def wrapper (
@@ -97,7 +151,34 @@ def _convert_messages(
97151 user_id : str ,
98152 session_id : str ,
99153) -> list :
100- """Convert VeADK formatted messages to Google ADK formatted messages."""
154+ """Convert a VeADK ``RunnerMessage`` into a list of Google ADK messages.
155+
156+ Supported inputs:
157+ - ``str``: Single-turn text prompt.
158+ - :class:`veadk.types.MediaMessage`: Single-turn multimodal prompt (text + image/video).
159+ - ``list``: A list of the above types (multi-turn with mixed text and multimodal).
160+
161+ For multimodal inputs, this reads the local media file bytes and detects
162+ the MIME type via ``filetype``; only ``image/*`` and ``video/*`` are supported.
163+
164+ Args:
165+ messages (RunnerMessage): Input message or list of messages to convert.
166+ app_name (str): App name (not directly used; kept for consistency with upload path).
167+ user_id (str): User ID (not directly used; kept for consistency with upload path).
168+ session_id (str): Session ID (not directly used; kept for consistency with upload path).
169+
170+ Returns:
171+ list[google.genai.types.Content]: Converted ADK messages.
172+
173+ Raises:
174+ ValueError: If the message type is unknown or media type is unrecognized.
175+ AssertionError: If the media MIME type is not supported (only image/* and video/*).
176+
177+ Note:
178+ This function only performs structural conversion. To upload inline media
179+ to an object store and rewrite URLs, use it together with
180+ ``intercept_new_message`` and ``_upload_image_to_tos``.
181+ """
101182 if isinstance (messages , str ):
102183 _messages = [types .Content (role = "user" , parts = [types .Part (text = messages )])]
103184 elif isinstance (messages , MediaMessage ):
@@ -146,6 +227,25 @@ def _convert_messages(
146227async def _upload_image_to_tos (
147228 part : genai .types .Part , app_name : str , user_id : str , session_id : str
148229) -> None :
230+ """Upload inline media data in a message part to TOS and rewrite its URL.
231+
232+ When ``part.inline_data`` has both ``display_name`` (original filename) and
233+ ``data`` (bytes), it generates an object storage path based on
234+ ``app_name``, ``user_id`` and ``session_id``. After upload, it replaces
235+ ``display_name`` with a signed URL.
236+
237+ Args:
238+ part (google.genai.types.Part): Message part containing ``inline_data``.
239+ app_name (str): App name.
240+ user_id (str): User ID.
241+ session_id (str): Session ID.
242+
243+ Returns:
244+ None
245+
246+ Raises:
247+ None: All exceptions are caught and logged; nothing is propagated.
248+ """
149249 try :
150250 if part .inline_data and part .inline_data .display_name and part .inline_data .data :
151251 from veadk .integrations .ve_tos .ve_tos import VeTOS
@@ -164,6 +264,47 @@ async def _upload_image_to_tos(
164264
165265
166266class Runner (ADKRunner ):
267+ """VeADK Runner that augments ADK with session, memory, tracing, and media upload.
268+
269+ This class builds on Google ADK's ``Runner`` and adds:
270+ - Integration with short-term memory (ShortTermMemory) for auto session management.
271+ - Optional long-term memory integration and session persistence.
272+ - New message interception and media upload to TOS.
273+ - Tracing dump and Trace ID logging.
274+ - A simplified ``run`` entry that supports multi-turn text/multimodal inputs.
275+
276+ Attributes:
277+ user_id (str): Default user ID.
278+ long_term_memory: Long-term memory service instance, or ``None`` if not set.
279+ short_term_memory (veadk.memory.short_term_memory.ShortTermMemory | None):
280+ Short-term memory instance used to auto-create/manage sessions.
281+ upload_inline_data_to_tos (bool): Whether to upload inline media to TOS while running.
282+ session_service: Session service instance (may come from short-term memory).
283+ memory_service: Memory service instance (may come from agent's long-term memory).
284+ app_name (str): Application name used in session management and object pathing.
285+
286+ Examples:
287+ Create a runner and perform a text-only interaction:
288+
289+ >>> from veadk.runner import Runner
290+ >>> from veadk.agent import Agent # assume it's properly constructed
291+ >>> runner = Runner(agent=my_agent, app_name="demo_app", user_id="u1")
292+ >>> output = await runner.run("Hello")
293+ >>> print(output)
294+
295+ Send multimodal (text + image):
296+
297+ >>> from veadk.types import MediaMessage
298+ >>> msg = MediaMessage(text="Describe the image", media="/path/to/image.png")
299+ >>> output = await runner.run(msg, upload_inline_data_to_tos=True)
300+ >>> print(output)
301+
302+ Note:
303+ This class wraps the parent ``run_async`` at initialization to insert media
304+ upload and post-run handling. If you override the underlying ``run_async``,
305+ ensure it remains compatible with this interception logic.
306+ """
307+
167308 def __init__ (
168309 self ,
169310 agent : BaseAgent | Agent ,
@@ -174,6 +315,33 @@ def __init__(
174315 * args ,
175316 ** kwargs ,
176317 ) -> None :
318+ """Initialize a Runner instance.
319+
320+ Selects the session service based on provided short-term memory or an
321+ external ``session_service``. If long-term memory or an external
322+ ``memory_service`` is provided, the passed service is preferred. After
323+ construction, it injects a message interception layer into the parent's
324+ ``run_async`` to support inline media upload and post-run handling.
325+
326+ Args:
327+ agent (google.adk.agents.base_agent.BaseAgent | veadk.agent.Agent):
328+ The agent instance used to run interactions.
329+ short_term_memory (ShortTermMemory | None): Optional short-term memory; if
330+ not provided and no external ``session_service`` is supplied, an in-memory
331+ session service will be created.
332+ app_name (str): Application name. Defaults to ``"veadk_default_app"``.
333+ user_id (str): Default user ID. Defaults to ``"veadk_default_user"``.
334+ upload_inline_data_to_tos (bool): Whether to enable inline media upload. Defaults to ``False``.
335+ *args: Positional args passed through to ``ADKRunner``.
336+ **kwargs: Keyword args passed through to ``ADKRunner``; may include
337+ ``session_service`` and ``memory_service`` to override defaults.
338+
339+ Returns:
340+ None
341+
342+ Raises:
343+ None
344+ """
177345 self .user_id = user_id
178346 self .long_term_memory = None
179347 self .short_term_memory = short_term_memory
@@ -238,6 +406,30 @@ async def run(
238406 save_tracing_data : bool = False ,
239407 upload_inline_data_to_tos : bool = False ,
240408 ):
409+ """Run a conversation with multi-turn text and multimodal inputs.
410+
411+ When short-term memory is configured, a session is auto-created as needed.
412+ Inputs are converted into ADK message format. If ``upload_inline_data_to_tos``
413+ is ``True``, media upload is enabled temporarily for this run (does not change
414+ the Runner's global setting).
415+
416+ Args:
417+ messages (RunnerMessage): Input messages (``str``, ``MediaMessage`` or a list of them).
418+ user_id (str): Override default user ID; if empty, uses the constructed ``user_id``.
419+ session_id (str): Session ID. Defaults to a timestamp-based temporary ID.
420+ run_config (google.adk.agents.RunConfig | None): Run config; if ``None``, a default
421+ config is created using the environment var ``MODEL_AGENT_MAX_LLM_CALLS``.
422+ save_tracing_data (bool): Whether to dump tracing data to disk after the run. Defaults to ``False``.
423+ upload_inline_data_to_tos (bool): Whether to enable media upload only for this run. Defaults to ``False``.
424+
425+ Returns:
426+ str: The textual output from the last event, if present; otherwise an empty string.
427+
428+ Raises:
429+ ValueError: If an input contains an unsupported or unrecognized media type.
430+ AssertionError: If a media MIME type is not among ``image/*`` or ``video/*``.
431+ Exception: Exceptions from the underlying ADK/Agent execution may propagate.
432+ """
241433 if upload_inline_data_to_tos :
242434 _upload_inline_data_to_tos = self .upload_inline_data_to_tos
243435 self .upload_inline_data_to_tos = upload_inline_data_to_tos
@@ -302,6 +494,17 @@ async def run(
302494 return final_output
303495
304496 def get_trace_id (self ) -> str :
497+ """Get the Trace ID from the current agent's tracer.
498+
499+ If the agent is not a :class:`veadk.agent.Agent` or no tracer is configured,
500+ returns ``"<unknown_trace_id>"``.
501+
502+ Returns:
503+ str: The Trace ID or ``"<unknown_trace_id>"``.
504+
505+ Raises:
506+ None
507+ """
305508 if not isinstance (self .agent , Agent ):
306509 logger .warning (
307510 ("The agent is not an instance of VeADK Agent, no trace id provided." )
@@ -322,6 +525,17 @@ def get_trace_id(self) -> str:
322525 return "<unknown_trace_id>"
323526
324527 def _print_trace_id (self ) -> None :
528+ """Log the current tracer's Trace ID.
529+
530+ If the agent is not a :class:`veadk.agent.Agent` or no tracer is configured,
531+ nothing is printed.
532+
533+ Returns:
534+ None
535+
536+ Raises:
537+ None
538+ """
325539 if not isinstance (self .agent , Agent ):
326540 logger .warning (
327541 ("The agent is not an instance of VeADK Agent, no trace id provided." )
@@ -342,6 +556,21 @@ def _print_trace_id(self) -> None:
342556 return
343557
344558 def save_tracing_file (self , session_id : str ) -> str :
559+ """Dump tracing data to disk and return the last written path.
560+
561+ Only effective when the agent is one of
562+ Agent/SequentialAgent/ParallelAgent/LoopAgent and a tracer is configured;
563+ otherwise returns an empty string.
564+
565+ Args:
566+ session_id (str): Session ID used to associate the tracing with a session.
567+
568+ Returns:
569+ str: The tracing file path; returns an empty string on failure or when no tracer.
570+
571+ Raises:
572+ None: All errors are logged and an empty string is returned.
573+ """
345574 if not isinstance (
346575 self .agent , (Agent , SequentialAgent , ParallelAgent , LoopAgent )
347576 ):
@@ -367,6 +596,18 @@ def save_tracing_file(self, session_id: str) -> str:
367596 return ""
368597
369598 async def save_eval_set (self , session_id : str , eval_set_id : str = "default" ) -> str :
599+ """Save the current session as part of an evaluation set and return its path.
600+
601+ Args:
602+ session_id (str): Session ID.
603+ eval_set_id (str): Evaluation set identifier. Defaults to ``"default"``.
604+
605+ Returns:
606+ str: The exported evaluation set file path.
607+
608+ Raises:
609+ Exception: Propagated if the underlying export logic raises.
610+ """
370611 eval_set_recorder = EvalSetRecorder (self .session_service , eval_set_id )
371612 eval_set_path = await eval_set_recorder .dump (
372613 self .app_name , self .user_id , session_id
@@ -376,6 +617,23 @@ async def save_eval_set(self, session_id: str, eval_set_id: str = "default") ->
376617 async def save_session_to_long_term_memory (
377618 self , session_id : str , user_id : str = "" , app_name : str = ""
378619 ) -> None :
620+ """Save the specified session to long-term memory.
621+
622+ If ``long_term_memory`` is not configured, the function logs a warning and returns.
623+ It fetches the session from the session service and then calls the long-term memory's
624+ ``add_session_to_memory`` for persistence.
625+
626+ Args:
627+ session_id (str): Session ID.
628+ user_id (str): Optional; override default user ID. If empty, uses ``self.user_id``.
629+ app_name (str): Optional; override default app name. If empty, uses ``self.app_name``.
630+
631+ Returns:
632+ None
633+
634+ Raises:
635+ Exception: May propagate if the underlying memory service raises during write.
636+ """
379637 if not self .long_term_memory :
380638 logger .warning ("Long-term memory is not enabled. Failed to save session." )
381639 return
0 commit comments