Packages

Elixir SDK for the Guava voice-agent platform.

Current section

Files

Jump to
guava README.md
Raw

README.md

# Guava (Elixir)
[![CI](https://github.com/goguava-ai/elixir-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/goguava-ai/elixir-sdk/actions/workflows/ci.yml)
[![Hex.pm](https://img.shields.io/hexpm/v/guava.svg)](https://hex.pm/packages/guava)
[![Hexdocs](https://img.shields.io/badge/hex-docs-8e7ce6.svg)](https://hexdocs.pm/guava)
[![Guava Python SDK](https://img.shields.io/badge/Guava%20Python%20SDK-v0.36.0-6C4AB6)](PARITY.md)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/goguava-ai/elixir-sdk/blob/main/LICENSE)
[![Elixir](https://img.shields.io/badge/Elixir-1.15%20%7C%201.17%20%7C%201.18-663399)](https://github.com/goguava-ai/elixir-sdk/blob/main/.github/workflows/ci.yml)
The Elixir SDK for the [Guava](https://goguava.ai) voice-agent platform.
**Tracks the [Guava Python SDK](https://github.com/goguava-ai/python-sdk)
`v0.36.0`.** The Elixir package version mirrors the Python version it tracks, so
`~> 0.36` here corresponds to Python `0.36.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`](PARITY.md) for how it maps to the Python SDK.
## Installation
```elixir
def deps do
[{:guava, "~> 0.36"}]
end
```
Requires Elixir ~> 1.15 and Erlang/OTP 26+.
## Documentation
Full guides live in [`docs/`](docs/README.md):
- [Architecture](docs/architecture.md)
- [Getting started](docs/getting-started.md)
- [Agents](docs/agents.md)
- [Calls](docs/calls.md)
- [Tasks & Fields](docs/tasks-and-fields.md)
- [Handlers](docs/handlers.md)
- [Channels](docs/channels.md)
- [Campaigns](docs/campaigns.md)
- [Messaging](docs/messaging.md)
- [Client](docs/client.md)
- [RAG & LLM](docs/rag-and-llm.md)
- [Testing](docs/testing.md)
- [Deployment](docs/deployment.md)
- [Local development](docs/local-development.md)
## Authentication
The quickest way to get started locally is to log in with the
[Guava CLI](https://goguava.ai/docs/quickstart) — the SDK picks the session up
automatically:
```bash
guava login
```
For servers and CI, provide an API key instead. Credentials resolve from
`config :guava, api_key: ...`, then a deploy token file, then `GUAVA_API_KEY`,
then a logged-in CLI session:
```elixir
{:ok, client} = Guava.Client.new(api_key: "gva-...")
client = Guava.Client.new!() # raises on failure
```
## Building an agent
An agent is a module that `use`s `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.
```elixir
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:
```elixir
children = [
{Guava.Channel, agent: MyAgent, listen: {:phone, "+14155550123"}}
]
```
Or, for scripts, use the blocking helpers:
```elixir
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:
```elixir
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:
```elixir
{:ok, numbers} = Guava.Client.list_numbers(client)
:ok = Guava.Client.send_sms!(client, "+1415…", "+1628…", "Hello!")
```
## Testing an agent (no phone)
```elixir
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`](https://github.com/goguava-ai/elixir-sdk/blob/main/.tool-versions), so a version
manager ([`mise`](https://mise.jdx.dev) or [`asdf`](https://asdf-vm.com)) sets
you up with the exact toolchain:
```bash
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`](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:
```bash
./emix deps.get
./emix test
./emix credo --strict
```
### Keeping in sync with the Python SDK
This port tracks the Python `guava-sdk` (see [`PARITY.md`](PARITY.md)). Two Claude
skills in `.claude/skills/` drive updates:
1. **`check-upstream-parity`** — diffs the tracked version against the latest PyPI
release and writes a prioritized, read-only drift report under `sync/`.
2. **Reconcile** the changes in `lib/`/`test/` (manually or with an agent), guided
by the report and `PARITY.md` — the human-in-the-loop step.
3. **`release`** — bumps the version, runs the verification gate, tags, and cuts
the GitHub release, then hands off the interactive, OTP-gated `mix hex.publish`.
See [`PARITY.md`](PARITY.md#keeping-in-sync-with-the-python-sdk) for the full runbook.
See [`PARITY.md`](PARITY.md) for parity status and intentional deviations.