Files
George Weale 7ea01ca6bd fix: stop live tool execution from bypassing run_async (v1)
In a live session, a streaming tool (one whose wrapped function is an async
generator) was executed by `FunctionTool._call_live`, which built its own
argument dict and called the wrapped function directly. It never went through
`BaseTool.run_async`, so everything `run_async` does was skipped: the
confirmation check added in #6575, Pydantic argument coercion, mandatory-argument
validation, and dispatch to a subclass that overrides `run_async`. A tool
declared with `require_confirmation=True` ran unattended.

`_call_live` and its `__call_tool_live` wrapper are removed. The live streaming
branch now calls `__call_tool_async`, which calls `tool.run_async`, and inspects
what comes back: an async generator is drained into the live request queue as
before, while a plain value — the error dict `run_async` returns when
confirmation is required or rejected, or when a mandatory argument is missing —
is relayed as a single FunctionResponse. The `input_stream` injection that
`_call_live` performed moves into `FunctionTool._prepare_invocation_args`, and
`_get_mandatory_args` no longer counts the framework-supplied `tool_context` and
`input_stream` parameters as mandatory.

Behaviour changes a live-mode caller will see:

- A tool behind `require_confirmation` is refused instead of executed, and a
  confirmation request is recorded on the tool context. Confirmation is still
  not answerable in live mode: the flow does not emit an
  `adk_request_confirmation` function call and the live path does not accept a
  `ToolConfirmation`, so the tool cannot be approved and resumed. TODOs mark the
  three sites that have to change to close that loop.
- Arguments annotated with a Pydantic model now arrive as model instances rather
  than raw dicts.
- A `BaseTool` subclass that overrides `run_async` is now dispatched through that
  override instead of having its `func` called directly.
- A streaming tool that raises now sends an error FunctionResponse. Previously
  the exception escaped the background task and the session waited forever for a
  response that never came. The exception text is logged, not sent to the model.
- A streaming tool invoked outside live mode, where there is no active stream to
  inject, now raises `TypeError` for the missing `input_stream` argument instead
  of returning the "mandatory input parameters are not present" error dict. Such
  a tool was already broken there; only the shape of the failure changed.

`FunctionTool._call_live` is private and had one caller, but a third-party
subclass that overrides it will silently stop being called.

Adapted from google/adk-python main: `LiveRequestQueue.send_content` on this
branch takes only a content argument, so the upstream `partial=` keywords are
dropped. The two comment-only changes upstream made to `base_llm_flow.py` are
also omitted, because one of them anchors on a line that does not exist here.
2026-08-17 22:56:31 +00:00
..
2026-01-20 14:50:09 -08:00