inbound form filling.md
Inbound w/ Form Filling
In this example, we'll build an inbound voice agent for a fictional restaurant. Callers can join the waitlist by providing their name, party size, and a callback number — which the agent collects conversationally using a structured task.
Define the Agent
guava.Agent is our starting point for building Guava agents. We'll create one with a name and purpose scoped to the restaurant.
export const AGENT_PY = `import guava
agent = guava.Agent(
name="Mia",
organization="Thai Palace",
purpose="Helping callers join the restaurant waitlist",
)`;
export const AGENT_TS = `import * as guava from "@guava-ai/guava-sdk";
const agent = new guava.Agent({
name: "Mia",
organization: "Thai Palace",
purpose: "Helping callers join the restaurant waitlist",
});`;
Accept or reject the call
on_call_received fires before the call starts and gives you a chance to accept or reject based on caller info. Here we accept all calls.
export const ACCEPT_PY = `@agent.on_call_received
def on_call_received(call_info: guava.CallInfo) -> guava.IncomingCallAction:
return guava.AcceptCall()`;
export const ACCEPT_TS = `agent.onCallReceived(async (_callInfo: guava.CallInfo) => {
return { action: "accept" };
});`;
Set up the form
on_call_start fires at the beginning of every accepted call. We use set_task to hand the agent a structured checklist of fields to collect. The agent gathers each piece of information conversationally — it knows when all fields are filled and automatically moves on.
export const TASK_PY = `@agent.on_call_start
def on_call_start(call: guava.Call) -> None:
call.set_task(
"waitlist",
objective="You are a virtual assistant for Thai Palace. Add callers to the waitlist.",
checklist=[
guava.Field(key="caller_name", field_type="text", description="Name for the waitlist"),
guava.Field(key="party_size", field_type="integer", description="Number of people"),
guava.Field(
key="phone_number",
field_type="text",
description="Phone number to text when the table is ready",
),
"Read the phone number back to the caller to confirm.",
],
)`;
export const TASK_TS = `agent.onCallStart(async (call: guava.Call) => {
await call.setTask({
taskId: "waitlist",
objective: "You are a virtual assistant for Thai Palace. Add callers to the waitlist.",
checklist: [
guava.Field({ key: "caller_name", fieldType: "text", description: "Name for the waitlist" }),
guava.Field({ key: "party_size", fieldType: "integer", description: "Number of people" }),
guava.Field({
key: "phone_number",
fieldType: "text",
description: "Phone number to text when the table is ready",
}),
"Read the phone number back to the caller to confirm.",
],
});
});`;
Handle task completion
on_task_complete fires once every field in the checklist is collected. This is the right place to save the data to your backend, trigger a notification, or hang up.
export const COMPLETE_PY = `@agent.on_task_complete("waitlist")
def on_waitlist_done(call: guava.Call) -> None:
logger.info(
"Added %s, party of %d, to waitlist.",
call.get_field("caller_name"),
call.get_field("party_size"),
)
call.hangup("Thank the caller and let them know we'll text when their table is ready.")`;
export const COMPLETE_TS = `agent.onTaskComplete("waitlist", async (call: guava.Call) => {
logger.info(
"Added %s, party of %d, to waitlist.",
await call.getField("caller_name"),
await call.getField("party_size"),
);
await call.hangup("Thank the caller and let them know we'll text when their table is ready.");
});`;
Start the agent
Attach the agent to a channel to start receiving inbound calls.
export const RUN_PY = `# Run this to attach your agent to a phone number. Call your agent's number to talk to it.
agent.listen_phone(os.environ["GUAVA_AGENT_NUMBER"])
# Run this to receive a WebRTC link where you can talk to your agent in the browser.
agent.listen_webrtc()
# Run this to talk to your agent using your local audio device.
agent.call_local()
# Run this to test your agent in a text-based chat session in the terminal (no audio required).
agent.chat()`;
export const RUN_TS = `// Run this to attach your agent to a phone number. Call your agent's number to talk to it.
agent.listenPhone(process.env.GUAVA_AGENT_NUMBER!);
// Run this to receive a WebRTC link where you can talk to your agent in the browser.
agent.listenWebrtc();
// Run this to talk to your agent using your local audio device.
agent.callLocal();
// Run this to test your agent in a text-based chat session in the terminal (no audio required).
agent.chat();`;
Complete example
export const FULL_PY = `import os
import guava
import logging
import argparse
from guava import logging_utils
logger = logging.getLogger("thai_palace")
agent = guava.Agent(
name="Mia",
organization="Thai Palace",
purpose="Helping callers join the restaurant waitlist",
)
@agent.on_call_received
def on_call_received(call_info: guava.CallInfo) -> guava.IncomingCallAction:
return guava.AcceptCall()
@agent.on_call_start
def on_call_start(call: guava.Call) -> None:
call.set_task(
"waitlist",
objective="You are a virtual assistant for Thai Palace. Add callers to the waitlist.",
checklist=[
guava.Field(key="caller_name", field_type="text", description="Name for the waitlist"),
guava.Field(key="party_size", field_type="integer", description="Number of people"),
guava.Field(
key="phone_number",
field_type="text",
description="Phone number to text when the table is ready",
),
"Read the phone number back to the caller to confirm.",
],
)
@agent.on_task_complete("waitlist")
def on_waitlist_done(call: guava.Call) -> None:
logger.info(
"Added %s, party of %d, to waitlist.",
call.get_field("caller_name"),
call.get_field("party_size"),
)
call.hangup("Thank the caller and let them know we'll text when their table is ready.")
if __name__ == "__main__":
logging_utils.configure_logging()
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--phone", action="store_true", help="Listen for phone calls.")
group.add_argument("--webrtc", action="store_true", help="Create on a WebRTC code.")
group.add_argument("--local", action="store_true", help="Start a local call.")
group.add_argument("--chat", action="store_true", help="Start a text-based chat session for testing.")
args = parser.parse_args()
if args.phone:
agent.listen_phone(os.environ["GUAVA_AGENT_NUMBER"])
elif args.webrtc:
agent.listen_webrtc()
elif args.chat:
agent.chat()
else:
agent.call_local()`;
export const FULL_TS = `import * as guava from "@guava-ai/guava-sdk";
import { getDefaultLogger } from "@guava-ai/guava-sdk";
const logger = getDefaultLogger();
const agent = new guava.Agent({
name: "Mia",
organization: "Thai Palace",
purpose: "Helping callers join the restaurant waitlist",
});
agent.onCallReceived(async (_callInfo: guava.CallInfo) => {
return { action: "accept" };
});
agent.onCallStart(async (call: guava.Call) => {
await call.setTask({
taskId: "waitlist",
objective: "You are a virtual assistant for Thai Palace. Add callers to the waitlist.",
checklist: [
guava.Field({ key: "caller_name", fieldType: "text", description: "Name for the waitlist" }),
guava.Field({ key: "party_size", fieldType: "integer", description: "Number of people" }),
guava.Field({
key: "phone_number",
fieldType: "text",
description: "Phone number to text when the table is ready",
}),
"Read the phone number back to the caller to confirm.",
],
});
});
agent.onTaskComplete("waitlist", async (call: guava.Call) => {
logger.info(
"Added %s, party of %d, to waitlist.",
await call.getField("caller_name"),
await call.getField("party_size"),
);
await call.hangup("Thank the caller and let them know we'll text when their table is ready.");
});
const args = process.argv.slice(2);
if (args.includes("--webrtc")) {
agent.listenWebrtc();
} else if (args.includes("--phone")) {
agent.listenPhone(process.env.GUAVA_AGENT_NUMBER!);
} else if (args.includes("--local")) {
agent.callLocal();
} else if (args.includes("--chat")) {
agent.chat();
} else {
console.error("Usage: guava-example restaurant-waitlist --phone | --webrtc | --local | --chat");
process.exit(1);
}`;