This tutorial builds a complete voice agent for a restaurant. By the end you will have a working agent that greets callers, collects a reservation, and confirms the details.
You will use the ADK to define the agent, iterate on it, and push it for testing. Merging and deployment still happen in Agent Studio.What you will build
The agent handles calls to Maison, a fictional restaurant. When a caller asks to make a reservation, the agent:- Enters a booking flow and collects the caller’s name, party size, and preferred date and time
- Confirms the details back to the caller
- Stores the booking on the conversation state
What you will learn
This tutorial covers:- creating an empty project in Agent Studio
- initializing a local project and pulling its configuration
- working on a branch
- defining entities for structured data collection
- building a multi-step flow with default steps and a function step
- writing a start function and a booking function
- triggering a flow from a topic
- tuning speech recognition with keyphrase boosting and transcript corrections
- adjusting spoken output with pronunciation rules
- previewing changes with
poly statusandpoly diff - pushing and testing with
poly chat
Prerequisites
Before you start:- you have Python 3.14 or later installed
- you have
uvinstalled - you have installed the ADK:
pip install polyai-adk - you have a PolyAI API key exported as
POLY_ADK_KEY - you have created an empty project in Agent Studio — the ADK cannot create projects, it can only sync them. Open Agent Studio, create a new project, and make a note of its account ID and project ID. You will need both to initialize the local project.
Part 1 — Set up the project
Initialize
Create a directory and runpoly init inside it:
poly init walks you through interactive dropdowns for region, account, and project — pick the empty project you created earlier. Single options (one region, one account) are auto-selected. If you’d rather pass the IDs from the Agent Studio URL directly, use poly init --region <region> --account_id <account_id> --project_id <project_id>.
poly init with no flags — you will be prompted for each one in turn. You can navigate with the arrow keys or start typing to filter the list.
poly init creates a subdirectoryThe project is created at {cwd}/{account_id}/{project_id}, not directly in your current directory. After init completes, change into the project directory before running any other commands:poly init also pulls the current configuration from Agent Studio automatically. There is no need to run poly pull separately.
Your project directory now contains the initial configuration as YAML and Python files:
_gen/ directory contains auto-generated platform code. Do not edit it — it is overwritten on every pull.
Files like config/entities.yaml, flows/, and topics/ are only created when you add those resources to the project. You will create them in this tutorial.
Create a working branch
It is good practice to make changes on a branch. Create one now:booking-flow branch. Any changes you push will go to that branch in Agent Studio, leaving main (and Sandbox) untouched.
Part 2 — Define the agent
Personality
Openagent_settings/personality.yaml. Adjust the adjectives to suit the Maison brand:
adjectives— a map of preset tonal traits. Each is set totrueorfalse; every selected trait is combined into the agent’s personality.custom— a free-text description that can extend or replace the adjectives. It accepts{{attr:...}}and{{vrbl:...}}references, so the personality can vary per variant or per call.
Allowed adjective values
adjectives keys must come from a fixed set: Polite, Calm, Kind, Funny, Energetic, Thoughtful, and Other. Any other key causes poly push to fail with a validation error.Role
Openagent_settings/role.yaml and describe what the agent is:
Rules
Openagent_settings/rules.txt. Rules give the agent standing instructions that apply across every turn. Add the following:
{{fn:start_booking_flow}} references a global function you will define later. The model uses these references to understand when to call them.
Part 3 — Define the entities
Entities are the structured data values the agent can collect from a caller. Createconfig/entities.yaml:
relative_date and round-tripsThe relative_date: true field is valid and is sent to the platform on push. However, date entity config may not be returned by the platform on pull, so after a poly pull you may see config: {} for the reservation_date entity. This is a known platform behavior and does not affect how the entity works at runtime.Automatic ASR biasingWhen a default step lists these entities in
extracted_entities, the platform automatically configures speech recognition to be more accurate for that kind of input — dates, times, numbers, and names.Part 4 — Build the booking flow
Flows live underflows/. Each flow gets its own directory. The directory name must be the snake_case version of the flow’s name field — so a flow named Booking Flow must live in flows/booking_flow/.
Create the directory structure:
Flow configuration
Createflows/booking_flow/flow_config.yaml:
Step 1 — Collect name
Createflows/booking_flow/steps/collect_name.yaml:
Step 2 — Collect party size
Createflows/booking_flow/steps/collect_party_size.yaml:
Step 3 — Collect date and time
Createflows/booking_flow/steps/collect_date.yaml:
child_step: confirm_booking uses the Python filename (without .py) rather than a step name — that is the convention for pointing to a function step.
Step 4 — Confirm booking (function step)
Function steps are deterministic Python. They run without model interpretation. Createflows/booking_flow/function_steps/confirm_booking.py:
Part 5 — Add a global function to enter the flow
Global functions live in the top-levelfunctions/ directory. Create functions/start_booking_flow.py:
{{fn:start_booking_flow}} in your rules resolves to. When the model decides to call it, the agent enters the booking flow at its start step.
Part 6 — Add a start function
The start function runs once at the beginning of every call, before the first user input. Use it to initialize state. Create or updatefunctions/start_function.py:
Part 7 — Add a topic
Topics tell the agent what kinds of caller utterances map to which actions. Createtopics/Make a Reservation.yaml:
content and example_queries to understand when this topic applies, and actions to know what to do.
Part 8 — Tune speech recognition
Voice agents listen on a noisy channel, and domain-specific vocabulary is usually the first thing ASR gets wrong. The ADK exposes two speech-recognition resources that do not require any code: keyphrase boosting and transcript corrections. Both live undervoice/speech_recognition/.
Keyphrase boosting
Createvoice/speech_recognition/keyphrase_boosting.yaml:
maximum so the agent never mis-hears it, and apply lighter boosts to the phrases that disambiguate booking intent.
Transcript corrections
Spoken times and numbers often come through in forms that are hard to parse. Createvoice/speech_recognition/transcript_corrections.yaml:
Part 9 — Adjust how the agent speaks
Response control sits on the other side of the conversation: it shapes what the agent says before TTS. For a French-brand restaurant, the most common issue is pronunciation. Createvoice/response_control/pronunciations.yaml:
Part 10 — (Optional) Send an SMS confirmation
SMS templates are fully supported by the ADK, but are not editable in the Agent Studio UI and template references of the form{{twilio_sms:...}} do not resolve inside UI-editable fields. To keep SMS working reliably, trigger it from code instead of from a prompt reference — that keeps every moving part inside files the ADK owns.
Skip this part if you are only testing via chat
conv.send_sms_template needs conv.caller_number, which is only populated on the voice channel. In poly chat, the template won’t actually be dispatched — but the code path is wired the same way and will fire the moment a real call comes in.Define the template
Createconfig/sms_templates.yaml:
{{vrbl:...}} placeholders pull from conv.state values — so the function step has to set them before sending.
Send it from the function step
Replace the body offlows/booking_flow/function_steps/confirm_booking.py with:
{{twilio_sms:...}} and the UI gap for SMS templates stops mattering for this tutorial.
See the SMS templates reference for the full field list, environment-specific sender numbers, and the conv.send_sms helper for free-form messages.
Part 11 — Review your changes
Before pushing, check what has changed:Variables in
poly statusThe variables/ entries appear because the ADK scans your function code for conv.state.* assignments and tracks each one as a variable. These entries are virtual — they do not correspond to files on disk and are not something you need to create or manage. This is expected output.poly diff:
Part 12 — Push to Agent Studio
Push the changes to your branch in Agent Studio:booking-flow branch. Sandbox remains on main and is unaffected.
Part 13 — Merge and test
By default,poly chat connects to your current branch’s last pushed state. On main it falls back to the sandbox environment. To test booking-flow against sandbox alongside the rest of the project, merge it into main first.
Merging from the CLI or Agent StudioMerge from the CLI with
poly branch merge '<commit message>' (run from the booking-flow branch), or open the project in Agent Studio, switch to the booking-flow branch, and merge through the web UI. See the Branch merging reference for the full conflict-resolution flow, including --interactive and --resolutions.poly chat against the sandbox environment:
Part 14 — After the merge
After merging in Agent Studio, switch back tomain locally:
YAML key order changes after a round-tripAfter a push and pull, the platform returns YAML with keys in alphabetical order. Fields you wrote in logical order (such as
name: before description:) will be reordered. This is cosmetic and does not affect behavior, but poly diff will show changes after the first round-trip even when the content is the same.What to explore next
This tutorial covered a single flow with four steps. From here you can extend the agent in several directions. Multi-location support with variants: If Maison has multiple locations, useconfig/variant_attributes.yaml to define per-location phone numbers, opening hours, and capacity limits. The agent reads the right values for each location at runtime using {{attr:...}} in prompts and conv.variant.attribute_name in code.
Handling cancellations: Add a second topic and flow for callers who want to cancel or modify a reservation. The flow structure is similar — collect a name or reference, confirm the record, then update state.
External API calls: Replace the stub booking logic in confirm_booking.py with a real HTTP call to your reservation system. Function steps are the right place for this — they run deterministically and can store results in conv.state for the model to reference.
Richer error paths: Add an explicit error step to the flow for when the booking cannot be completed. Route to it from confirm_booking.py using flow.goto_step("Error") and return a context string explaining what happened.
Call handoffs: Define SIP transfer destinations in config/handoffs.yaml and trigger them from code with conv.call_handoff(...). Handoffs are ADK-only — they do not have a matching editor in the Agent Studio UI — so, like SMS, the most reliable pattern is to call them from a function or function step rather than relying on a {{ho:...}} placeholder. See the handoffs reference.
Related pages
Flows
Full reference for flow configuration, step types, and conditions.
Entities
All entity types and their configuration fields.
Functions
How global functions, transition functions, and function steps differ.
Topics
How topics connect caller intent to agent actions.
Speech recognition
Keyphrase boosting, transcript corrections, and ASR settings.
Response control
Pronunciations and phrase filtering for spoken output.
Variants
Per-location configuration without duplicating your project.

