Gradium provides streaming speech-to-text and text-to-speech models for realtime voice agents. Gradium plugs into a LiveKit Agents pipeline as the STT and TTS stages through the official livekit-plugins-gradium plugin, and SurrealDB Agent Memory hooks into the same pipeline's turn lifecycle: recall before the model speaks, store after. Use the Python SDK (surrealdb); there is no dedicated Gradium adapter.
This is an integration guide. The code shows where SurrealDB Agent Memory fits in a LiveKit agent's turn lifecycle with Gradium speech models; adapt the hook names to your installed livekit-agents version.
Installation
pip install "livekit-agents[gradium,openai]"
pip install --pre surrealdbSet the connection details. Create the Gradium API key in the Gradium Studio console:
export GRADIUM_API_KEY="gd_..."
export AGENT_MEMORY_ENDPOINT="https://memory.example.com"
export AGENT_MEMORY_CONTEXT="acme-prod"
export AGENT_MEMORY_API_KEY="sk-mem-..."Recall before the model, store after
LiveKit calls on_user_turn_completed once Gradium has transcribed the caller's speech, before the LLM runs. Recall relevant memory there and add it to the turn context; store the exchange when the turn finishes. Gradium's STT performs semantic turn detection itself, so the session needs no separate VAD plugin:
import os
from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession, ChatContext, ChatMessage
from livekit.plugins import gradium, openai
from surrealdb import AsyncAgentMemory
class MemoryAgent(Agent):
def __init__(self, memory: AsyncAgentMemory, scope: list[str]):
super().__init__(instructions="You are a helpful voice assistant.")
self._memory = memory
self._scope = scope
async def on_user_turn_completed(self, turn_ctx: ChatContext, new_message: ChatMessage):
# Recall relevant memory and inject it as context for this turn.
block = await self._memory.query_context(
new_message.text_content, k=8, lens=self._scope,
)
if block:
turn_ctx.add_message(role="system", content=f"## Memory\n{block}")
# Store the caller's turn for future recall (non-blocking).
await self._memory.remember(new_message.text_content, scopes=self._scope)
server = AgentServer()
@server.rtc_session()
async def entrypoint(ctx: agents.JobContext):
memory = AsyncAgentMemory(
endpoint=os.environ["AGENT_MEMORY_ENDPOINT"],
context=os.environ["AGENT_MEMORY_CONTEXT"],
api_key=os.environ["AGENT_MEMORY_API_KEY"],
)
session = AgentSession(
stt=gradium.STT(model_name="default", language="en"),
llm=openai.LLM(model="gpt-4o"),
tts=gradium.TTS(model_name="default", voice_id="4SZHfMpw-p46Ywgs"),
)
await session.start(
agent=MemoryAgent(memory, scope=["org/acme/user/alice"]),
room=ctx.room,
)
if __name__ == "__main__":
agents.cli.run_app(server)Pass voice_id explicitly; the plugin's default voice has changed between releases. Gradium's flagship voices cover English, French, Spanish, Portuguese, and German, and gradium.STT accepts the same five languages through its language option.
Scope per caller
Bind a scope to the caller's identity so each person's memory stays isolated. It is a slash path or an array of paths, for example ["org/acme/user/alice"]. Derive it from the LiveKit participant identity when the room connects. Register paths with agent-memory scopes create before first use.
Latency
Voice turns are latency-sensitive. Gradium's streaming models and semantic turn detection keep the speech stages fast, which leaves the recall call as the main added latency in the loop. Keep recall to a single query_context call with a modest k, and let the write to remember run without blocking the response. For heavier synthesis, run reflect or consolidate between calls rather than inside a turn.
Next steps
Python SDK: the full client surface
Recalling memories: recall modes and filters
LiveKit: the same lifecycle with other speech providers