recipes

Cookbook

Small, copy-paste-ready patterns for things people build with minmo. Each one assumes a VoiceAgent is already created — see the quickstart if you need that part.

Multiple tools on one agent

Register as many functions as you need — each becomes its own tool with its own JSON schema, inferred from the signature and docstring.

@agent.tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"It's sunny in {city}."


@agent.tool
def get_account_balance(account_id: str) -> str:
    """Look up a customer's account balance by id."""
    return "$42.00"


@agent.tool
def book_appointment(date: str, time: str, reason: str) -> str:
    """Book an appointment on a given date and time."""
    return f"Booked for {date} at {time}: {reason}"

Mixing server-hosted and client-side tools

A tool that only needs data your frontend already has (auth, session state) doesn't need a round trip to your server — declare it transport="client" and answer it yourself when the provider sends tool.call over the live session.

@agent.tool  # hosted by minmo, transport="http" is the default
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"It's sunny in {city}."


@agent.tool(transport="client")
def get_account_balance(account_id: str) -> str:
    """Look up the current user's balance — answered client-side."""
    return "$42.00"

Asserting tool-call behavior in CI

simulate() drives your llm directly and checks which tools it picks — no live session, so it's cheap enough to run on every commit.

from minmo.testing import simulate

result = simulate(agent, transcript=["What's the weather in Paris?"])
assert result["tool_calls"][0]["name"] == "get_weather"
assert result["tool_calls"][0]["arguments"]["city"] == "Paris"

Switching to Hume

Hume needs an extra secret_key (its OAuth client secret) alongside the usual api_key, and optionally a specific voice.

from minmo import VoiceAgent
from minmo.providers.hume import HumeProvider

agent = VoiceAgent(
    prompt="You are a friendly voice assistant.",
    api_key="YOUR_HUME_API_KEY",
    provider=HumeProvider(),
    provider_options={
        "secret_key": "YOUR_HUME_SECRET_KEY",
        "voice": {"name": "ITO", "provider": "HUME_AI"},
    },
)

result = agent.deploy()
print(result["info"])

token = agent.mint_token()  # OAuth access token, start an EVI session with it

Switching to OpenAI Realtime

OpenAI Realtime doesn't host anything on their servers — deploy() just builds the config in memory, so call mint_token() right after, in the same process, to actually get a usable session token.

from minmo import VoiceAgent
from minmo.providers.openai_realtime import OpenAIRealtimeProvider

agent = VoiceAgent(
    prompt="You are a friendly voice assistant.",
    api_key="YOUR_OPENAI_API_KEY",
    provider=OpenAIRealtimeProvider(),
    provider_options={"model": "gpt-4o-realtime-preview", "voice": "alloy"},
)

agent.deploy()
token = agent.mint_token()  # send this session token to your client

Switching to ElevenLabs

ElevenLabs supports both tool transports, same as AssemblyAI — mix transport="http" and transport="client" tools freely.

from minmo import VoiceAgent
from minmo.providers.elevenlabs import ElevenLabsProvider

agent = VoiceAgent(
    prompt="You are a friendly voice assistant.",
    api_key="YOUR_ELEVENLABS_API_KEY",
    provider=ElevenLabsProvider(),
    provider_options={"llm": "gemini-2.5-flash", "voice_id": "21m00Tcm4TlvDq8ikWAM"},
)

result = agent.deploy(local=True)
print(result["info"])

signed_url = agent.mint_token()  # open a conversation with this WebSocket URL

Logging sessions to disk

Pass log_path when creating the agent, then call log_session() after a call ends (e.g. from your provider's webhook or end-of-call handler) to persist transcript + tool calls, and optionally react to it.

agent = VoiceAgent(
    prompt="You are a friendly voice assistant.",
    api_key="YOUR_ASSEMBLYAI_API_KEY",
    log_path="./sessions.jsonl",
    on_session_end=lambda record: print("saved:", record["session_id"]),
)

# after a call ends:
agent.log_session(
    session_id="sess_123",
    transcript=[{"role": "user", "content": "What's the weather in Paris?"}],
    tool_calls=[{"name": "get_weather", "arguments": {"city": "Paris"}}],
)

Inspect the latest one anytime with minmo logs on the command line.

Deploying tool hosting yourself

local=True is for development — it spins up a local server and an ngrok tunnel. In production, run minmo's own server factory behind your real domain and point deploy() at it.

from minmo.server import create_tool_server

app = create_tool_server(agent)  # a FastAPI app — serve it with uvicorn/gunicorn

# elsewhere, once that server is live at your domain:
agent.deploy(local=False, host_url="https://tools.yourdomain.com")
# or export MINMO_HOST_URL=https://tools.yourdomain.com and drop host_url