The Elixir SDK for the Guava voice-agent platform.
Tracks the Guava Python SDK
v0.34.0. The Elixir package version mirrors the Python version it tracks, so
~> 0.34 here corresponds to Python 0.34.x.
Community-maintained and actively developed. Support is best-effort — issues and pull requests are welcome. The public API is adapted to idiomatic Elixir; see
PARITY.mdfor how it maps to the Python SDK.
Installation
def deps do
[{:guava, "~> 0.34"}]
endRequires Elixir ~> 1.15 and Erlang/OTP 26+.
Documentation
Full guides live in docs/:
- Architecture
- Getting started
- Agents
- Calls
- Tasks & Fields
- Handlers
- Channels
- Campaigns
- Messaging
- Client
- RAG & LLM
- Testing
- Deployment
Authentication
Credentials resolve from config :guava, api_key: ..., then a deploy token file,
then GUAVA_API_KEY, then a logged-in CLI session:
{:ok, client} = Guava.Client.new(api_key: "gva-...")
client = Guava.Client.new!() # raises on failureBuilding an agent
An agent is a module that uses Guava.Agent — like a GenServer/LiveView.
Each call gets its own process; your callbacks thread a per-call state and
use the Guava.Call handle to drive the conversation. Implement only what you need.
defmodule MyAgent do
use Guava.Agent, name: "Nova", organization: "Acme", purpose: "Answer questions and route callers."
@impl true
def init(_call_info), do: {:ok, %{}}
@impl true
def handle_start(call, state) do
Guava.Call.set_task(call, "greet", objective: "Greet the caller and ask how you can help.")
{:noreply, state}
end
@impl true
def handle_question(_call, question, state), do: {:reply, MyKB.answer(question), state}
@impl true
def handle_action("sales", call, state) do
Guava.Call.transfer(call, "+14155550100", "Transferring you to sales.")
{:noreply, state}
end
endAttach it to a channel. In your supervision tree:
children = [
{Guava.Channel, agent: MyAgent, listen: {:phone, "+14155550123"}}
]Or, for scripts, use the blocking helpers:
Guava.listen_phone(MyAgent, "+14155550123")
Guava.call_phone(MyAgent, "+14155550100", "+16285550123", %{"customer" => "Ada"})
Guava.run({Guava.Channel, agent: MyAgent, campaign: "camp_abc"})A persona-only agent (no callbacks) is valid:
defmodule Greeter do
use Guava.Agent, name: "Nova", organization: "Acme", purpose: "Greet callers warmly."
endClient operations
Each returns {:ok, result} | {:error, %Guava.Error{}}, with a ! variant that raises:
{:ok, numbers} = Guava.Client.list_numbers(client)
:ok = Guava.Client.send_sms!(client, "+1415…", "+1628…", "Hello!")Testing an agent (no phone)
Guava.Testing.session(MyAgent, fn session ->
Guava.Testing.Session.say(session, "Hi, what are your hours?")
Guava.Testing.Session.wait_for_turn(session)
Guava.Testing.Session.evaluate(session, ["The agent stated its hours."], [])
end)Development
Develop locally with the standard Elixir toolchain — no Docker required. The
repo pins Erlang + Elixir in .tool-versions, so a version
manager (mise or asdf) sets
you up with the exact toolchain:
mise install # or: asdf install — reads .tool-versions
mix deps.get
mix test # unit + local integration
mix test --include live # also live tests (requires GUAVA_API_KEY)
mix credo --strict
No sudo?
mise/asdfcompile Erlang from source (needsbuild-essential,libssl-dev,libncurses-dev,autoconf). If you can't install those, drop a precompiled Erlang + Elixir into~/.localwith no compilation and no sudo — seedocs/local-development.md.
Docker (optional fallback)
A pinned container is available via the ./emix wrapper (see Dockerfile.dev)
if you'd rather not install the toolchain. _build/deps live in named volumes,
so it won't touch your native build:
./emix deps.get
./emix test
./emix credo --strict
See PARITY.md for parity status and intentional deviations.