Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions contributing/samples/live/live_workflow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Live Workflow Sample

## Overview

This sample composes three short, single-purpose **live (voice) agents** into a graph-based workflow:

1. `greeter_agent` — greets and confirms the caller's name.
1. `dob_verifier_agent` — captures and validates the caller's date of birth
(using the `validate_date_of_birth` tool).
1. `goals_agent` — once identity is verified, delivers the call goals and wraps
up the conversation.

Each stage runs in `mode='task'` and hands a typed result to the next
(`GreeterOutput`, `DobOutput`). The stages are wired directly into the
workflow's `edges`, so the framework runs them in order.

## Running the agent

```bash
uv run adk web contributing/samples/live/live_workflow
```

Open the ADK web interface and start a Live Session with the agent.

## Evaluating this agent

`test_config.json` and `live_workflow.evalset.json` evaluate the workflow in
**live mode** with an `llm_audio` user simulator (each user turn is synthesized
to audio and streamed to the live agent). The eval scores response quality,
tool-use quality, and overall trajectory quality against custom rubrics across
the staged conversation.

This sample uses the in-process, rubric-based LLM-as-judge metrics
(`rubric_based_final_response_quality_v1`, `rubric_based_tool_use_quality_v1`,
and `rubric_based_multi_turn_trajectory_quality_v1`), which support multi-agent
conversations. The first two are scored per turn; the trajectory metric judges
the whole conversation end-to-end.

The eval case uses a `conversation_scenario`, so an LLM-simulated user adapts to
each stage of the workflow instead of following a fixed script.

1. Install the eval extra: `uv pip install -e ".[eval]"`.
1. Add a `.env` in this directory with Vertex AI credentials (see
`live_bidi_streaming_single_agent/.env`). The project needs access to both
the Live API and Gemini TTS models.
1. Run the eval:
```bash
uv run adk eval \
contributing/samples/live/live_workflow \
contributing/samples/live/live_workflow/live_workflow.evalset.json \
--config_file_path contributing/samples/live/live_workflow/test_config.json
```
15 changes: 15 additions & 0 deletions contributing/samples/live/live_workflow/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 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.

from . import agent
120 changes: 120 additions & 0 deletions contributing/samples/live/live_workflow/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 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.

"""An example of how to build a graph-based live (voice) agent workflow."""

from google.adk.agents.llm_agent import Agent
from google.adk.tools.tool_context import ToolContext
from google.adk.workflow import START
from google.adk.workflow import Workflow
from pydantic import BaseModel
from pydantic import Field

LIVE_MODEL = 'gemini-live-2.5-flash-native-audio'


# --- Typed handoffs between stages -----------------------------------------
class GreeterOutput(BaseModel):
result: str = Field(
default='', description='The confirmed name of the person on the line.'
)


class DobOutput(BaseModel):
result: str = Field(
default='', description='Identity verification result, e.g. "verified".'
)


# --- Tool ------------------------------------------------------------------
def validate_date_of_birth(dob: str, tool_context: ToolContext) -> dict:
"""Validate a confirmed date of birth against records (mocked).

Args:
dob: The patient's date of birth in YYYY-MM-DD format.

Returns:
A dict with a ``match`` boolean.
"""
match = dob == '1985-07-12' # Mock record for the demo persona.
tool_context.state['dob_verified'] = match
return {'match': match}


# --- Stage 1: Greeting + identity ------------------------------------------
greeter_agent = Agent(
model=LIVE_MODEL,
name='greeter_agent',
description='Greets on a recorded line and confirms the right person.',
mode='task',
output_schema=GreeterOutput,
instruction="""
You are Sam, a friendly care-team assistant.
Greet the caller and confirm you are speaking with John Doe before
sharing anything else. Ask one question per turn. Once the name is
confirmed, briefly acknowledge it and complete your task, passing the
confirmed name as 'result'.
""",
)


# --- Stage 2: DOB verification ---------------------------------------------
dob_verifier_agent = Agent(
model=LIVE_MODEL,
name='dob_verifier_agent',
description='Captures and validates the date of birth before serving.',
mode='task',
output_schema=DobOutput,
tools=[validate_date_of_birth],
instruction="""
Verify the caller's identity by date of birth. Ask for their date of
birth, read it back to confirm, then validate it with
`validate_date_of_birth` using YYYY-MM-DD format. Once it matches, let
the caller know their identity is verified and complete your task with
"verified". If it still does not match after two tries, complete your
task with "unverified". Ask one question per turn.
""",
)


# --- Stage 3: Conversation goals + ending ----------------------------------
goals_agent = Agent(
model=LIVE_MODEL,
name='goals_agent',
description='Delivers the call goals once identity is verified.',
mode='task',
instruction="""
Identity is already verified. As soon as it is your turn, proactively
tell the caller about their upcoming appointment on Tuesday, June 16th at
3 PM with Dr. Example, and ask if they have any questions for the visit.
Do not wait to be asked. Answer any questions briefly, ask if there is
anything else, then wrap up warmly, end with "Goodbye.", and complete
your task.
""",
)


# --- The workflow: agents sequenced directly by edges ----------------------
root_agent = Workflow(
name='live_workflow',
description=(
'A Workflow of live voice agents: confirm the caller, verify their'
' date of birth, then share the call details.'
),
edges=[
(START, greeter_agent),
(greeter_agent, dob_verifier_agent),
(dob_verifier_agent, goals_agent),
],
)
20 changes: 20 additions & 0 deletions contributing/samples/live/live_workflow/live_workflow.evalset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"eval_set_id": "live_workflow",
"name": "live_workflow",
"description": "Live eval cases for the live workflow. Exercises the audio user simulator driving each stage: greeting/identity, DOB verification, and delivering the call goals.",
"eval_cases": [
{
"eval_id": "verified_patient_scenario",
"conversation_scenario": {
"starting_prompt": "Hello?",
"conversation_plan": "You are John Doe. Confirm your name when greeted. When asked for your date of birth, give July 12th, 1985, and confirm it when read back. Listen to the appointment details, ask what you should bring to the visit, then say you have no other questions and let the call wrap up.",
"user_persona": "NOVICE"
},
"session_input": {
"app_name": "live_workflow",
"user_id": "test_user_id",
"state": {}
}
}
]
}
109 changes: 109 additions & 0 deletions contributing/samples/live/live_workflow/test_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"criteria": {
"rubric_based_final_response_quality_v1": {
"threshold": 0.7,
"judge_model_options": {
"judge_model": "gemini-3.5-flash",
"num_samples": 1
},
"rubrics": [
{
"rubric_id": "no_details_before_verification",
"rubric_content": {
"text_property": "If the agent shares appointment details in this turn, the caller's name and date of birth must already have been confirmed earlier in the conversation."
}
},
{
"rubric_id": "states_appointment_when_asked",
"rubric_content": {
"text_property": "If the caller asks about their appointment in this turn, the agent states the appointment on Tuesday, June 16th at 3 PM with Dr. Example."
}
},
{
"rubric_id": "offers_further_help",
"rubric_content": {
"text_property": "If the agent gives the caller information they requested in this turn, it also asks whether there is anything else it can help with."
}
},
{
"rubric_id": "ends_with_goodbye",
"rubric_content": {
"text_property": "If the caller indicates they have no further questions, the agent ends the conversation warmly with a goodbye."
}
}
]
},
"rubric_based_tool_use_quality_v1": {
"threshold": 0.7,
"judge_model_options": {
"judge_model": "gemini-3.5-flash",
"num_samples": 1
},
"rubrics": [
{
"rubric_id": "validates_confirmed_dob",
"rubric_content": {
"text_property": "After the caller confirms the date of birth that was read back to them, the agent calls validate_date_of_birth with the confirmed date in YYYY-MM-DD format."
}
}
]
},
"rubric_based_multi_turn_trajectory_quality_v1": {
"threshold": 0.7,
"judge_model_options": {
"judge_model": "gemini-3.5-flash",
"num_samples": 1
},
"rubrics": [
{
"rubric_id": "verifies_identity_first",
"rubric_content": {
"text_property": "Across the call, the agent confirms the caller's name and validates their date of birth before disclosing any appointment details."
}
},
{
"rubric_id": "staged_handoff_order",
"rubric_content": {
"text_property": "The conversation proceeds through the intended stages in order: greeting and name confirmation, then date-of-birth verification, then appointment delivery."
}
},
{
"rubric_id": "calls_validation_tool",
"rubric_content": {
"text_property": "The agent calls validate_date_of_birth once, only after the caller confirms the read-back date, using YYYY-MM-DD format."
}
},
{
"rubric_id": "delivers_appointment_and_answers",
"rubric_content": {
"text_property": "After identity is verified, the agent delivers the appointment on Tuesday, June 16th at 3 PM with Dr. Example and answers the caller's follow-up question."
}
},
{
"rubric_id": "closes_conversation",
"rubric_content": {
"text_property": "The agent ends the call warmly with a goodbye once the caller has no further questions."
}
}
]
}
},
"live_model_config": {
"timeout_seconds": 300
},
"user_simulator_config": {
"type": "llm_audio",
"model": "gemini-3.5-flash",
"max_allowed_invocations": 10,
"audio_model": "gemini-3.1-flash-tts-preview",
"audio_model_configuration": {
"response_modalities": ["AUDIO"],
"speech_config": {
"voice_config": {
"prebuilt_voice_config": { "voice_name": "Kore" }
},
"language_code": "en-US"
}
}
}
}
Loading
Loading