ec2b666284
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
654 lines
19 KiB
Python
654 lines
19 KiB
Python
# Copyright 2026 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
from typing import AsyncGenerator
|
|
|
|
from google.adk.agents.context import Context
|
|
from google.adk.agents.live_request_queue import LiveRequestQueue
|
|
from google.adk.agents.llm_agent import LlmAgent
|
|
from google.adk.events.event import Event
|
|
from google.adk.models.llm_response import LlmResponse
|
|
from google.adk.runners import Runner
|
|
from google.adk.sessions.in_memory_session_service import InMemorySessionService
|
|
from google.adk.workflow._base_node import BaseNode
|
|
from google.adk.workflow._base_node import START
|
|
from google.adk.workflow._workflow import Workflow
|
|
from google.genai import types
|
|
from pydantic import Field
|
|
import pytest
|
|
|
|
from . import testing_utils
|
|
|
|
# --- Mock Nodes and Agents for Testing Live Mode Design ---
|
|
|
|
|
|
class _MockNonLiveNode(BaseNode):
|
|
"""A standard non-live node whose signature does NOT accept live_request_queue."""
|
|
|
|
called: bool = False
|
|
actual_input: Any = None
|
|
shared_state: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
def __init__(self, *, name: str):
|
|
super().__init__(name=name)
|
|
|
|
async def _run_impl(
|
|
self,
|
|
*,
|
|
ctx: Context,
|
|
node_input: Any,
|
|
) -> AsyncGenerator[Any, None]:
|
|
self.called = True
|
|
self.actual_input = node_input
|
|
self.shared_state["called"] = True
|
|
self.shared_state["actual_input"] = node_input
|
|
yield Event(output=f"{self.name}_output")
|
|
|
|
|
|
class _ConstantNode(BaseNode):
|
|
"""A node that outputs a constant value."""
|
|
|
|
output_value: Any = None
|
|
|
|
def __init__(self, *, name: str, output_value: Any):
|
|
super().__init__(name=name)
|
|
self.output_value = output_value
|
|
|
|
async def _run_impl(
|
|
self,
|
|
*,
|
|
ctx: Context,
|
|
node_input: Any,
|
|
) -> AsyncGenerator[Any, None]:
|
|
yield Event(output=self.output_value)
|
|
|
|
|
|
# --- Live Workflow Unit Tests (TDD) ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_hybrid_live_non_live_nodes():
|
|
"""CUJ 1: A workflow has hybrid live & non-live nodes."""
|
|
mock_model1 = testing_utils.MockModel.create(
|
|
responses=[
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: node1_start")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: node1_end")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent([
|
|
types.Part.from_function_call(
|
|
name="finish_task",
|
|
args={"result": "LiveNode1_output"},
|
|
)
|
|
])
|
|
),
|
|
]
|
|
)
|
|
live_node1 = LlmAgent(
|
|
name="LiveNode1",
|
|
model=mock_model1,
|
|
mode="task",
|
|
instruction="Handle live interaction 1.",
|
|
)
|
|
non_live_node = _MockNonLiveNode(name="NonLiveNode")
|
|
mock_model2 = testing_utils.MockModel.create(
|
|
responses=[
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: node2_start")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: node2_end")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent([
|
|
types.Part.from_function_call(
|
|
name="finish_task",
|
|
args={"result": "LiveNode2_output"},
|
|
)
|
|
])
|
|
),
|
|
]
|
|
)
|
|
live_node2 = LlmAgent(
|
|
name="LiveNode2",
|
|
model=mock_model2,
|
|
mode="task",
|
|
instruction="Handle live interaction 2.",
|
|
)
|
|
|
|
wf = Workflow(
|
|
name="hybrid_workflow",
|
|
edges=[
|
|
(START, live_node1),
|
|
(live_node1, non_live_node),
|
|
(non_live_node, live_node2),
|
|
],
|
|
)
|
|
|
|
live_queue = LiveRequestQueue()
|
|
|
|
# Pre-seed first live node's requests
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"node1_start", mime_type="audio/pcm")
|
|
)
|
|
live_queue.send_realtime(types.Blob(data=b"node1_end", mime_type="audio/pcm"))
|
|
|
|
ss = InMemorySessionService()
|
|
runner = Runner(app_name=wf.name, node=wf, session_service=ss)
|
|
session = await ss.create_session(app_name=wf.name, user_id="u")
|
|
|
|
events = []
|
|
async for event in runner.run_live(
|
|
user_id="u",
|
|
session_id=session.id,
|
|
live_request_queue=live_queue,
|
|
run_config=testing_utils.RunConfig(
|
|
get_session_config={"state_delta": {"__START__": "start_input"}}
|
|
),
|
|
):
|
|
events.append(event)
|
|
if event.output == "NonLiveNode_output":
|
|
# First live node and non-live node completed! Now feed the second live node's requests:
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"node2_start", mime_type="audio/pcm")
|
|
)
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"node2_end", mime_type="audio/pcm")
|
|
)
|
|
|
|
# 1. Assert exact outputs sequence
|
|
outputs = [e.output for e in events if e.output is not None]
|
|
assert outputs == [
|
|
{"result": "LiveNode1_output"},
|
|
"NonLiveNode_output",
|
|
{"result": "LiveNode2_output"},
|
|
]
|
|
assert non_live_node.shared_state.get("actual_input") == {
|
|
"result": "LiveNode1_output"
|
|
}
|
|
|
|
# 2. Assert intermediate content events (conversational turns)
|
|
content_texts = [
|
|
p.text
|
|
for e in events
|
|
if e.content and e.content.parts and e.output is None
|
|
for p in e.content.parts
|
|
if p.text
|
|
]
|
|
assert content_texts == [
|
|
"Acknowledged: node1_start",
|
|
"Acknowledged: node1_end",
|
|
"Acknowledged: node2_start",
|
|
"Acknowledged: node2_end",
|
|
]
|
|
|
|
# 3. Assert live requests fed to the models
|
|
assert [b.data for b in mock_model1.live_blobs] == [
|
|
b"node1_start",
|
|
b"node1_end",
|
|
]
|
|
assert [b.data for b in mock_model2.live_blobs] == [
|
|
b"node2_start",
|
|
b"node2_end",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_nested_workflow_has_live_node():
|
|
"""CUJ 2: A nested workflow has a live node."""
|
|
mock_model = testing_utils.MockModel.create(
|
|
responses=[
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: inner_start")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: inner_end")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent([
|
|
types.Part.from_function_call(
|
|
name="finish_task",
|
|
args={"result": "InnerLiveNode_output"},
|
|
)
|
|
])
|
|
),
|
|
]
|
|
)
|
|
live_node = LlmAgent(
|
|
name="InnerLiveNode",
|
|
model=mock_model,
|
|
mode="task",
|
|
instruction="Handle inner live interaction.",
|
|
)
|
|
inner_wf = Workflow(name="inner_wf", edges=[(START, live_node)])
|
|
outer_wf = Workflow(name="outer_wf", edges=[(START, inner_wf)])
|
|
|
|
live_queue = LiveRequestQueue()
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"inner_start", mime_type="audio/pcm")
|
|
)
|
|
live_queue.send_realtime(types.Blob(data=b"inner_end", mime_type="audio/pcm"))
|
|
|
|
ss = InMemorySessionService()
|
|
runner = Runner(app_name=outer_wf.name, node=outer_wf, session_service=ss)
|
|
session = await ss.create_session(app_name=outer_wf.name, user_id="u")
|
|
|
|
events = []
|
|
async for event in runner.run_live(
|
|
user_id="u",
|
|
session_id=session.id,
|
|
live_request_queue=live_queue,
|
|
run_config=testing_utils.RunConfig(
|
|
get_session_config={"state_delta": {"__START__": "start_input"}}
|
|
),
|
|
):
|
|
events.append(event)
|
|
|
|
# Assert exact outputs sequence
|
|
outputs = [e.output for e in events if e.output is not None]
|
|
assert outputs == [{"result": "InnerLiveNode_output"}]
|
|
|
|
# Assert content events
|
|
content_texts = [
|
|
p.text
|
|
for e in events
|
|
if e.content and e.content.parts and e.output is None
|
|
for p in e.content.parts
|
|
if p.text
|
|
]
|
|
assert content_texts == [
|
|
"Acknowledged: inner_start",
|
|
"Acknowledged: inner_end",
|
|
]
|
|
assert [b.data for b in mock_model.live_blobs] == [
|
|
b"inner_start",
|
|
b"inner_end",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_nested_live_node_and_outer_live_node():
|
|
"""CUJ 3: A nested workflow has live node & outer workflow then has a live node."""
|
|
mock_model_inner = testing_utils.MockModel.create(
|
|
responses=[
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: inner_start")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: inner_end")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent([
|
|
types.Part.from_function_call(
|
|
name="finish_task",
|
|
args={"result": "InnerLiveNode_output"},
|
|
)
|
|
])
|
|
),
|
|
]
|
|
)
|
|
inner_live = LlmAgent(
|
|
name="InnerLiveNode",
|
|
model=mock_model_inner,
|
|
mode="task",
|
|
instruction="Handle inner live interaction.",
|
|
)
|
|
inner_wf = Workflow(name="inner_wf", edges=[(START, inner_live)])
|
|
|
|
mock_model_outer = testing_utils.MockModel.create(
|
|
responses=[
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: outer_start")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: outer_end")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent([
|
|
types.Part.from_function_call(
|
|
name="finish_task",
|
|
args={"result": "OuterLiveNode_output"},
|
|
)
|
|
])
|
|
),
|
|
]
|
|
)
|
|
outer_live = LlmAgent(
|
|
name="OuterLiveNode",
|
|
model=mock_model_outer,
|
|
mode="task",
|
|
instruction="Handle outer live interaction.",
|
|
)
|
|
prep_node = _ConstantNode(name="PrepNode", output_value="prep_output")
|
|
|
|
wf = Workflow(
|
|
name="nested_sequential_live",
|
|
edges=[
|
|
(START, inner_wf),
|
|
(inner_wf, prep_node),
|
|
(prep_node, outer_live),
|
|
],
|
|
)
|
|
|
|
live_queue = LiveRequestQueue()
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"inner_start", mime_type="audio/pcm")
|
|
)
|
|
live_queue.send_realtime(types.Blob(data=b"inner_end", mime_type="audio/pcm"))
|
|
|
|
ss = InMemorySessionService()
|
|
runner = Runner(app_name=wf.name, node=wf, session_service=ss)
|
|
session = await ss.create_session(app_name=wf.name, user_id="u")
|
|
|
|
events = []
|
|
async for event in runner.run_live(
|
|
user_id="u",
|
|
session_id=session.id,
|
|
live_request_queue=live_queue,
|
|
run_config=testing_utils.RunConfig(
|
|
get_session_config={"state_delta": {"__START__": "start_input"}}
|
|
),
|
|
):
|
|
events.append(event)
|
|
if event.output == "prep_output":
|
|
# Inner live node and prep node completed! Feed outer node's requests:
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"outer_start", mime_type="audio/pcm")
|
|
)
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"outer_end", mime_type="audio/pcm")
|
|
)
|
|
|
|
# Assert exact outputs sequence
|
|
outputs = [e.output for e in events if e.output is not None]
|
|
assert outputs == [
|
|
{"result": "InnerLiveNode_output"},
|
|
"prep_output",
|
|
{"result": "OuterLiveNode_output"},
|
|
]
|
|
|
|
# Assert content events
|
|
content_texts = [
|
|
p.text
|
|
for e in events
|
|
if e.content and e.content.parts and e.output is None
|
|
for p in e.content.parts
|
|
if p.text
|
|
]
|
|
assert content_texts == [
|
|
"Acknowledged: inner_start",
|
|
"Acknowledged: inner_end",
|
|
"Acknowledged: outer_start",
|
|
"Acknowledged: outer_end",
|
|
]
|
|
assert [b.data for b in mock_model_inner.live_blobs] == [
|
|
b"inner_start",
|
|
b"inner_end",
|
|
]
|
|
assert [b.data for b in mock_model_outer.live_blobs] == [
|
|
b"outer_start",
|
|
b"outer_end",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dynamic_node_scheduling_of_live_node():
|
|
"""CUJ 4: A node in workflow dynamically schedules a live node using ctx.run_node()."""
|
|
|
|
class _DynamicLiveSchedulerNode(BaseNode):
|
|
"""A node that dynamically schedules a child live node using ctx.run_node()."""
|
|
|
|
child_node: BaseNode | None = None
|
|
child_output: Any = None
|
|
shared_state: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
def __init__(self, *, name: str, child_node: BaseNode):
|
|
super().__init__(name=name, rerun_on_resume=True)
|
|
self.child_node = child_node
|
|
|
|
async def _run_impl(
|
|
self,
|
|
*,
|
|
ctx: Context,
|
|
node_input: Any,
|
|
) -> AsyncGenerator[Any, None]:
|
|
if self.child_node:
|
|
output = await ctx.run_node(self.child_node, node_input=node_input)
|
|
self.child_output = output
|
|
self.shared_state["child_output"] = output
|
|
yield Event(output=f"{self.name}_output")
|
|
|
|
mock_model = testing_utils.MockModel.create(
|
|
responses=[
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: dynamic_start")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent(
|
|
[types.Part.from_text(text="Acknowledged: dynamic_end")]
|
|
)
|
|
),
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent([
|
|
types.Part.from_function_call(
|
|
name="finish_task",
|
|
args={"result": "DynamicLiveNode_output"},
|
|
)
|
|
])
|
|
),
|
|
]
|
|
)
|
|
live_node = LlmAgent(
|
|
name="DynamicLiveNode",
|
|
model=mock_model,
|
|
mode="task",
|
|
instruction="Handle dynamic live interaction.",
|
|
)
|
|
scheduler_node = _DynamicLiveSchedulerNode(
|
|
name="SchedulerNode", child_node=live_node
|
|
)
|
|
|
|
wf = Workflow(name="dynamic_wf", edges=[(START, scheduler_node)])
|
|
|
|
live_queue = LiveRequestQueue()
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"dynamic_start", mime_type="audio/pcm")
|
|
)
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"dynamic_end", mime_type="audio/pcm")
|
|
)
|
|
|
|
ss = InMemorySessionService()
|
|
runner = Runner(app_name=wf.name, node=wf, session_service=ss)
|
|
session = await ss.create_session(app_name=wf.name, user_id="u")
|
|
|
|
events = []
|
|
async for event in runner.run_live(
|
|
user_id="u",
|
|
session_id=session.id,
|
|
live_request_queue=live_queue,
|
|
run_config=testing_utils.RunConfig(
|
|
get_session_config={"state_delta": {"__START__": "start_input"}}
|
|
),
|
|
):
|
|
events.append(event)
|
|
|
|
# Assert exact outputs sequence
|
|
outputs = [e.output for e in events if e.output is not None]
|
|
assert outputs == [
|
|
{"result": "DynamicLiveNode_output"},
|
|
"SchedulerNode_output",
|
|
]
|
|
assert scheduler_node.shared_state.get("child_output") == {
|
|
"result": "DynamicLiveNode_output"
|
|
}
|
|
|
|
# Assert content events
|
|
content_texts = [
|
|
p.text
|
|
for e in events
|
|
if e.content and e.content.parts and e.output is None
|
|
for p in e.content.parts
|
|
if p.text
|
|
]
|
|
assert content_texts == [
|
|
"Acknowledged: dynamic_start",
|
|
"Acknowledged: dynamic_end",
|
|
]
|
|
assert [b.data for b in mock_model.live_blobs] == [
|
|
b"dynamic_start",
|
|
b"dynamic_end",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_live_node_output_passed_to_downstream():
|
|
"""CUJ 5: Dedicated test verifying output of a live node is passed to the next node."""
|
|
mock_model = testing_utils.MockModel.create(
|
|
responses=[
|
|
LlmResponse(
|
|
content=testing_utils.ModelContent([
|
|
types.Part.from_function_call(
|
|
name="finish_task",
|
|
args={"result": "LiveNode_output"},
|
|
)
|
|
])
|
|
),
|
|
]
|
|
)
|
|
live_node = LlmAgent(
|
|
name="LiveNode",
|
|
model=mock_model,
|
|
mode="task",
|
|
instruction="Handle live interaction.",
|
|
)
|
|
non_live_node = _MockNonLiveNode(name="NonLiveNode")
|
|
|
|
wf = Workflow(
|
|
name="dataflow_workflow",
|
|
edges=[
|
|
(START, live_node),
|
|
(live_node, non_live_node),
|
|
],
|
|
)
|
|
|
|
live_queue = LiveRequestQueue()
|
|
live_queue.send_realtime(types.Blob(data=b"start_msg", mime_type="audio/pcm"))
|
|
live_queue.send_realtime(types.Blob(data=b"end_msg", mime_type="audio/pcm"))
|
|
|
|
ss = InMemorySessionService()
|
|
runner = Runner(app_name=wf.name, node=wf, session_service=ss)
|
|
session = await ss.create_session(app_name=wf.name, user_id="u")
|
|
|
|
events = []
|
|
async for event in runner.run_live(
|
|
user_id="u",
|
|
session_id=session.id,
|
|
live_request_queue=live_queue,
|
|
run_config=testing_utils.RunConfig(
|
|
get_session_config={"state_delta": {"__START__": "start_input"}}
|
|
),
|
|
):
|
|
events.append(event)
|
|
|
|
outputs = [e.output for e in events if e.output is not None]
|
|
assert outputs == [{"result": "LiveNode_output"}, "NonLiveNode_output"]
|
|
assert non_live_node.shared_state.get("actual_input") == {
|
|
"result": "LiveNode_output"
|
|
}, "The downstream node must receive the live node's exact output"
|
|
assert [b.data for b in mock_model.live_blobs] == [b"start_msg", b"end_msg"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_single_turn_agent_runs_as_non_live_in_live_session():
|
|
"""CUJ 6: A single_turn LlmAgent in a live session runs as non-live and consumes node_input."""
|
|
mock_model = testing_utils.MockModel.create(
|
|
responses=[
|
|
"SingleTurn_output",
|
|
]
|
|
)
|
|
prep_node = _ConstantNode(
|
|
name="ConstantNode", output_value="initial_text_input"
|
|
)
|
|
single_turn_node = LlmAgent(
|
|
name="SingleTurnNode",
|
|
model=mock_model,
|
|
mode="single_turn",
|
|
instruction="Summarize the input.",
|
|
)
|
|
capture = _MockNonLiveNode(name="capture")
|
|
|
|
wf = Workflow(
|
|
name="single_turn_live_wf",
|
|
edges=[
|
|
(START, prep_node),
|
|
(prep_node, single_turn_node),
|
|
(single_turn_node, capture),
|
|
],
|
|
)
|
|
|
|
live_queue = LiveRequestQueue()
|
|
live_queue.send_realtime(
|
|
types.Blob(data=b"ignored_audio", mime_type="audio/pcm")
|
|
)
|
|
|
|
ss = InMemorySessionService()
|
|
runner = Runner(app_name=wf.name, node=wf, session_service=ss)
|
|
session = await ss.create_session(app_name=wf.name, user_id="u")
|
|
|
|
events = []
|
|
async for event in runner.run_live(
|
|
user_id="u",
|
|
session_id=session.id,
|
|
live_request_queue=live_queue,
|
|
):
|
|
events.append(event)
|
|
|
|
outputs = [e.output for e in events if e.output is not None]
|
|
assert outputs == ["initial_text_input", "capture_output"]
|
|
assert capture.shared_state.get("actual_input") == "SingleTurn_output"
|
|
# Verify that the model received the initial_text_input (node_input) and NOT the live queue audio
|
|
assert len(mock_model.requests) == 1
|
|
assert (
|
|
mock_model.requests[0].contents[0].parts[0].text == "initial_text_input"
|
|
)
|
|
assert mock_model.live_blobs == []
|