Guava (Elixir)

Guava Python SDKLicense: MITElixir

The Elixir SDK for the Guava voice-agent platform.

Tracks the Guava Python SDKv0.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.md for how it maps to the Python SDK.

Installation

def deps do
[{:guava, "~> 0.34"}]
end

Requires Elixir ~> 1.15 and Erlang/OTP 26+.

Documentation

Full guides live in docs/:

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 failure

Building 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
end

Attach 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."
end

Client 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/asdf compile Erlang from source (needs build-essential, libssl-dev, libncurses-dev, autoconf). If you can't install those, drop a precompiled Erlang + Elixir into ~/.local with no compilation and no sudo — see docs/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.