# Live API Guide
The Gemini Live API enables real-time, bidirectional streaming communication with Gemini models through WebSocket connections. This allows for interactive conversations with low latency, supporting text, audio, and video inputs.
## Overview
The Live API is ideal for:
- Interactive chatbots and virtual assistants
- Real-time voice conversations
- Live video analysis
- Multi-turn conversations with tool calling
- Low-latency applications requiring immediate responses
## Key Features
- **Bidirectional Streaming**: Send and receive messages in real-time
- **Multi-Modal Support**: Text, audio, and video inputs
- **Tool Calling**: Execute functions during conversations
- **Automatic Reconnection**: Built-in reconnection logic with exponential backoff
- **Event-Driven**: Callback-based architecture for handling responses
## Getting Started
### Basic Setup
```elixir
alias Gemini.Live.Session
# Start a session
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
on_message: fn message ->
IO.inspect(message, label: "Received")
end
)
# Connect to the Live API
:ok = Session.connect(session)
# Send a message
:ok = Session.send(session, "Hello! How are you?")
# Close when done
Session.close(session)
```
### With Callbacks
The session supports several callbacks for handling different events:
```elixir
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
# Called when successfully connected
on_connect: fn ->
IO.puts("🟢 Connected to Live API")
end,
# Called when a message is received
on_message: fn message ->
case message do
%{server_content: content} ->
handle_model_response(content)
%{tool_call: calls} ->
handle_function_calls(calls)
%{setup_complete: _} ->
IO.puts("✅ Setup complete")
end
end,
# Called when disconnected
on_disconnect: fn reason ->
IO.puts("🔴 Disconnected: #{inspect(reason)}")
end,
# Called on errors
on_error: fn error ->
IO.puts("❌ Error: #{inspect(error)}")
end
)
Session.connect(session)
```
## Sending Messages
### Simple Text Messages
```elixir
# Send a simple text message
Session.send(session, "What is the capital of France?")
```
### Structured Content
```elixir
# Send structured content with multiple turns
Session.send_client_content(session, [
%{role: "user", parts: [%{text: "I'm going to tell you a story."}]},
%{role: "user", parts: [%{text: "Once upon a time..."}]}
], true) # true = turn_complete
```
### Real-Time Audio Input
```elixir
# Send audio chunks for real-time transcription
audio_data = File.read!("audio.pcm")
Session.send_realtime_input(session, [
%{
data: Base.encode64(audio_data),
mime_type: "audio/pcm"
}
])
```
## Receiving Messages
Messages are received through the `on_message` callback. The message structure depends on the type:
### Setup Complete
```elixir
%Gemini.Live.Message.ServerMessage{
setup_complete: %{message: "Setup complete"}
}
```
### Model Response
```elixir
%Gemini.Live.Message.ServerMessage{
server_content: %{
model_turn: %{
role: "model",
parts: [%{text: "Paris is the capital of France."}]
},
turn_complete: true
}
}
```
### Tool/Function Call
```elixir
%Gemini.Live.Message.ServerMessage{
tool_call: %{
function_calls: [
%{
name: "get_weather",
args: %{location: "San Francisco"}
}
]
}
}
```
## Function Calling
The Live API supports tool calling during conversations:
```elixir
# Define tools
tools = [
%{
function_declarations: [
%{
name: "get_weather",
description: "Get current weather for a location",
parameters: %{
type: "object",
properties: %{
location: %{
type: "string",
description: "City name"
}
},
required: ["location"]
}
}
]
}
]
# Start session with tools
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
tools: tools,
on_message: &handle_message/1
)
Session.connect(session)
Session.send(session, "What's the weather in Tokyo?")
# In your message handler, respond to tool calls
def handle_message(%{tool_call: %{function_calls: calls}}) do
# Execute the function
results = Enum.map(calls, fn call ->
case call["name"] do
"get_weather" ->
location = call["args"]["location"]
weather = get_weather_data(location)
%{
name: call["name"],
response: weather
}
end
end)
# Send the results back
Session.send_tool_response(session, results)
end
```
## Configuration Options
### Generation Config
```elixir
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
generation_config: %{
temperature: 0.8,
top_p: 0.95,
top_k: 40,
max_output_tokens: 1024
}
)
```
### System Instructions
```elixir
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
system_instruction: "You are a helpful travel assistant. Always provide concise recommendations."
)
```
### Safety Settings
```elixir
alias Gemini.Types.SafetySetting
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
safety_settings: [
%SafetySetting{
category: :harassment,
threshold: :block_medium_and_above
}
]
)
```
## Advanced Usage
### Streaming Audio Conversations
```elixir
defmodule VoiceAssistant do
alias Gemini.Live.Session
def start do
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
generation_config: %{
response_modalities: ["AUDIO"],
speech_config: %{
voice_config: %{
prebuilt_voice_config: %{
voice_name: "Kore"
}
}
}
},
on_message: &handle_audio_response/1
)
Session.connect(session)
# Start capturing audio from microphone
stream_audio_input(session)
end
defp stream_audio_input(session) do
# Continuously stream audio chunks
audio_stream = capture_microphone()
Enum.each(audio_stream, fn chunk ->
Session.send_realtime_input(session, [
%{data: Base.encode64(chunk), mime_type: "audio/pcm"}
])
end)
end
defp handle_audio_response(%{server_content: content}) do
# Extract and play audio response
case content do
%{model_turn: %{parts: parts}} ->
Enum.each(parts, fn part ->
if part["inlineData"] do
audio_data = Base.decode64!(part["inlineData"]["data"])
play_audio(audio_data)
end
end)
end
end
end
```
### Multi-Turn Conversations with Context
```elixir
defmodule ConversationManager do
alias Gemini.Live.Session
def start_conversation do
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
system_instruction: "You are a math tutor helping students learn algebra.",
on_message: &handle_response/1
)
Session.connect(session)
# Multi-turn conversation
Session.send(session, "I'm having trouble with quadratic equations.")
# Wait for response, then continue
receive do
{:response, _} ->
Session.send(session, "Can you give me an example?")
end
session
end
defp handle_response(message) do
case message do
%{server_content: %{model_turn: turn}} ->
send(self(), {:response, turn})
end
end
end
```
### Handling Reconnection
The session automatically handles reconnection with exponential backoff:
```elixir
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
on_connect: fn ->
IO.puts("Connected - ready to send messages")
end,
on_disconnect: fn reason ->
IO.puts("Disconnected: #{inspect(reason)}")
IO.puts("Automatic reconnection will be attempted...")
end
)
# Connection is automatically restored on network issues
# Messages sent during disconnection are queued
```
## Error Handling
### Common Errors
```elixir
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
on_error: fn error ->
case error do
{:authentication_failed, _} ->
IO.puts("Check your API key configuration")
{:rate_limit_exceeded, _} ->
IO.puts("Rate limit hit - backing off...")
{:invalid_message, _} ->
IO.puts("Message format error")
other ->
IO.puts("Unexpected error: #{inspect(other)}")
end
end
)
```
### Timeouts and Retries
```elixir
defmodule ResilientChat do
alias Gemini.Live.Session
def send_with_retry(session, message, retries \\ 3) do
case Session.send(session, message) do
:ok ->
:ok
{:error, :not_connected} when retries > 0 ->
# Wait for reconnection
Process.sleep(1000)
send_with_retry(session, message, retries - 1)
error ->
error
end
end
end
```
## Best Practices
### 1. Always Handle Callbacks
```elixir
# Good: Handle all message types
on_message: fn message ->
case message do
%{setup_complete: _} -> handle_setup()
%{server_content: content} -> handle_content(content)
%{tool_call: calls} -> handle_tools(calls)
_ -> Logger.warn("Unhandled message: #{inspect(message)}")
end
end
```
### 2. Manage Session Lifecycle
```elixir
defmodule MyApp.ChatServer do
use GenServer
alias Gemini.Live.Session
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
def init(_opts) do
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
on_message: fn msg -> send(self(), {:live_message, msg}) end
)
Session.connect(session)
{:ok, %{session: session}}
end
def terminate(_reason, state) do
Session.close(state.session)
:ok
end
end
```
### 3. Use Structured Logging
```elixir
require Logger
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
on_connect: fn ->
Logger.info("Live API session connected")
end,
on_message: fn message ->
Logger.debug("Received message", message: inspect(message))
end,
on_error: fn error ->
Logger.error("Live API error", error: inspect(error))
end
)
```
### 4. Rate Limiting Awareness
```elixir
defmodule RateLimitedChat do
use GenServer
alias Gemini.Live.Session
@max_messages_per_minute 60
def send_message(pid, message) do
GenServer.call(pid, {:send, message})
end
def handle_call({:send, message}, _from, state) do
if can_send?(state) do
Session.send(state.session, message)
{:reply, :ok, update_rate_limit(state)}
else
{:reply, {:error, :rate_limited}, state}
end
end
defp can_send?(state) do
length(state.recent_messages) < @max_messages_per_minute
end
end
```
## API Reference
For detailed API documentation, see:
- `Gemini.Live.Session` - Main session management
- `Gemini.Live.Message` - Message types and serialization
- `Gemini.Types.Live` - Configuration types
## Troubleshooting
### Connection Issues
```elixir
# Check authentication
config = Application.get_env(:gemini_ex, :api_key)
IO.inspect(config, label: "API Key configured?")
# Enable debug logging
Logger.configure(level: :debug)
# Test basic connectivity
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
on_error: fn error -> IO.inspect(error, label: "Error") end
)
```
### Message Not Received
```elixir
# Ensure callbacks are configured
{:ok, session} = Session.start_link(
model: "gemini-2.0-flash-exp",
on_message: fn msg ->
IO.inspect(msg, label: "Message received", limit: :infinity)
end
)
# Check session status
Session.status(session)
# => :connected (or :disconnected, :connecting, :error)
```
### Tool Calls Not Working
```elixir
# Verify tool declaration format
tools = [
%{
function_declarations: [
%{
name: "my_function",
description: "Clear description here",
parameters: %{
type: "object",
properties: %{
param: %{type: "string"}
},
required: ["param"]
}
}
]
}
]
# Ensure you're responding to tool calls
on_message: fn
%{tool_call: calls} ->
# Execute and respond
Session.send_tool_response(session, results)
_ ->
:ok
end
```
## Examples
See the [examples directory](https://github.com/nshkrdotcom/gemini_ex/tree/main/examples) for complete working examples:
- `examples/live_chat.exs` - Interactive chat session
- `examples/live_voice.exs` - Voice conversation
- `examples/live_tools.exs` - Function calling with Live API
## Related Documentation
- [Function Calling Guide](function_calling.md)
- [Authentication System](../../AUTHENTICATION_SYSTEM.md)
- [Streaming Architecture](../../STREAMING_ARCHITECTURE.md)