LiveKit voice agents

Add persistent memory to LiveKit voice agents using the zep-livekit package.

The zep-livekit package provides seamless integration between Zep and LiveKit voice agents. Choose between user-specific conversation memory or structured knowledge graph memory for intelligent context retrieval in real-time voice interactions.

Install dependencies

$pip install zep-livekit zep-cloud livekit-agents openai

Environment setup

Set your API keys as environment variables:

$export ZEP_API_KEY="your_zep_api_key"
>export OPENAI_API_KEY="your_openai_api_key"
>export LIVEKIT_URL="your_livekit_url"
>export LIVEKIT_API_KEY="your_livekit_api_key"
>export LIVEKIT_API_SECRET="your_livekit_secret"

Agent types

ZepUserAgent: Uses user threads for conversation memory with automatic context injection
ZepGraphAgent: Accesses structured knowledge through custom entity models

User memory agent

1

Step 1: Setup required imports

1import asyncio
2import logging
3import os
4from livekit.agents import AutoSubscribe, JobContext, llm, WorkerOptions, cli
5from livekit.agents.voice_assistant import VoiceAssistant
6from livekit.plugins import openai, silero
7from zep_cloud.client import AsyncZep
8from zep_livekit import ZepUserAgent
2

Step 2: Initialize Zep client and create user

1async def entrypoint(ctx: JobContext):
2 # Initialize Zep client
3 zep_client = AsyncZep(api_key=os.environ.get("ZEP_API_KEY"))
4
5 # Create unique user and thread IDs
6 participant_name = ctx.room.remote_participants[0].name or "User"
7 user_id = f"livekit_{participant_name}_{ctx.room.name}"
8 thread_id = f"thread_{ctx.room.name}"
9
10 # Create user in Zep (if not exists)
11 try:
12 await zep_client.user.add(
13 user_id=user_id,
14 first_name=participant_name
15 )
16 except Exception as e:
17 logging.info(f"User might already exist: {e}")
18
19 # Create thread for conversation memory
20 try:
21 await zep_client.thread.create(thread_id=thread_id, user_id=user_id)
22 except Exception as e:
23 logging.info(f"Thread might already exist: {e}")
3

Step 3: Create agent with memory

1 # Create Zep memory agent with enhanced configuration
2 zep_agent = ZepUserAgent(
3 zep_client=zep_client,
4 user_id=user_id,
5 thread_id=thread_id,
6 context_mode="basic", # or "summary"
7 user_message_name=participant_name,
8 assistant_message_name="Assistant",
9 instructions="You are a helpful voice assistant with persistent memory. "
10 "Remember details from previous conversations and reference them naturally."
11 )
12
13 # Create voice assistant with persistent memory
14 assistant = VoiceAssistant(
15 vad=silero.VAD.load(),
16 stt=openai.STT(),
17 llm=openai.LLM(),
18 tts=openai.TTS(),
19 chat_ctx=[
20 llm.ChatMessage(
21 role="system",
22 content="You are a helpful voice assistant with persistent memory. "
23 "Remember details from previous conversations and reference them naturally."
24 )
25 ],
26 )
27
28 # Connect Zep memory to voice assistant
29 assistant.start(ctx.room)
30 await zep_agent.start(assistant)
4

Step 4: Run the voice assistant

1 # Voice assistant will now have persistent memory
2 logging.info("Voice assistant with Zep memory is running")
3
4 # Keep the agent running
5 await assistant.aclose()

Automatic Memory Integration: ZepUserAgent automatically captures voice conversation turns and injects relevant context from previous conversations, enabling natural continuity across voice sessions.

ZepUserAgent configuration

The ZepUserAgent supports several parameters for customizing memory behavior:

1zep_agent = ZepUserAgent(
2 zep_client=zep_client,
3 user_id="user_123",
4 thread_id="thread_456",
5 context_mode="basic", # "basic" or "summary" - how context is assembled
6 user_message_name="Alice", # Name attribution for user messages
7 assistant_message_name="Assistant", # Name attribution for AI messages
8 instructions="Custom system instructions for the agent"
9)

Parameters:

  • context_mode: Controls how memory context is retrieved ("basic" for detailed context, "summary" for condensed)
  • user_message_name: Optional name for attributing user messages in Zep memory
  • assistant_message_name: Optional name for attributing assistant messages in Zep memory
  • instructions: System instructions that override the default LiveKit agent instructions

Knowledge graph agent

1

Step 1: Define custom entity models

1from zep_cloud.external_clients.ontology import EntityModel, EntityText
2from pydantic import Field
3
4class Person(EntityModel):
5 """A person entity for voice interactions."""
6 role: EntityText = Field(
7 description="person's role or profession",
8 default=None
9 )
10 interests: EntityText = Field(
11 description="topics the person is interested in",
12 default=None
13 )
14
15class Topic(EntityModel):
16 """A conversation topic or subject."""
17 category: EntityText = Field(
18 description="category of the topic",
19 default=None
20 )
21 importance: EntityText = Field(
22 description="importance level of this topic to the user",
23 default=None
24 )
2

Step 2: Setup graph with ontology

1from zep_livekit import ZepGraphAgent
2
3async def setup_graph_agent(ctx: JobContext):
4 zep_client = AsyncZep(api_key=os.environ.get("ZEP_API_KEY"))
5
6 # Set ontology for structured knowledge
7 await zep_client.graph.set_ontology(
8 entities={
9 "Person": Person,
10 "Topic": Topic,
11 }
12 )
13
14 # Create knowledge graph
15 graph_id = f"livekit_graph_{ctx.room.name}"
16 try:
17 await zep_client.graph.create(
18 graph_id=graph_id,
19 name="LiveKit Voice Knowledge Graph"
20 )
21 except Exception as e:
22 logging.info(f"Graph might already exist: {e}")
3

Step 3: Create graph memory agent

1 # Create Zep graph agent
2 zep_agent = ZepGraphAgent(
3 zep_client=zep_client,
4 graph_id=graph_id,
5 facts_limit=15, # Max facts in context
6 entity_limit=8 # Max entities in context
7 )
8
9 # Create voice assistant with knowledge graph memory
10 assistant = VoiceAssistant(
11 vad=silero.VAD.load(),
12 stt=openai.STT(),
13 llm=openai.LLM(),
14 tts=openai.TTS(),
15 chat_ctx=[
16 llm.ChatMessage(
17 role="system",
18 content="You are a knowledgeable voice assistant. Use the provided "
19 "context about entities and facts to give informed responses."
20 )
21 ],
22 )
23
24 assistant.start(ctx.room)
25 await zep_agent.start(assistant)

Graph Memory Context: ZepGraphAgent automatically extracts structured knowledge from voice conversations and injects relevant facts and entities as context for more intelligent responses.

Room-based memory isolation

LiveKit rooms provide natural memory isolation boundaries:

1# Each room gets its own memory context
2room_name = ctx.room.name
3user_id = f"livekit_user_{room_name}"
4thread_id = f"thread_{room_name}"
5graph_id = f"graph_{room_name}"
6
7# Memory is isolated per room/session
8zep_agent = ZepUserAgent(
9 zep_client=zep_client,
10 user_id=user_id,
11 thread_id=thread_id,
12 context_mode="basic",
13 user_message_name="User",
14 assistant_message_name="Assistant"
15)

Voice-specific considerations

Turn Management: Voice conversations have different turn dynamics than text chat. Zep automatically handles:

  • Overlapping speech detection
  • Turn boundary identification
  • Context window management for real-time responses

Memory Persistence: Key voice interaction details are preserved:

  • Speaker identification
  • Conversation topics and themes
  • User preferences expressed through voice
  • Temporal relationships between topics

Complete example

1import asyncio
2import logging
3import os
4from livekit.agents import AutoSubscribe, JobContext, llm, WorkerOptions, cli
5from livekit.agents.voice_assistant import VoiceAssistant
6from livekit.plugins import openai, silero
7from zep_cloud.client import AsyncZep
8from zep_livekit import ZepUserAgent
9
10async def entrypoint(ctx: JobContext):
11 await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
12
13 # Setup Zep integration
14 zep_client = AsyncZep(api_key=os.environ.get("ZEP_API_KEY"))
15 participant_name = ctx.room.remote_participants[0].name or "User"
16 user_id = f"livekit_{participant_name}_{ctx.room.name}"
17 thread_id = f"thread_{ctx.room.name}"
18
19 # Create user and thread
20 try:
21 await zep_client.user.add(user_id=user_id, first_name=participant_name)
22 await zep_client.thread.create(thread_id=thread_id, user_id=user_id)
23 except Exception:
24 pass # Already exists
25
26 # Create voice assistant with Zep memory
27 zep_agent = ZepUserAgent(
28 zep_client=zep_client,
29 user_id=user_id,
30 thread_id=thread_id,
31 context_mode="basic",
32 user_message_name=participant_name,
33 assistant_message_name="Assistant",
34 instructions="You are a helpful voice assistant with persistent memory. "
35 "Remember details from previous conversations."
36 )
37
38 assistant = VoiceAssistant(
39 vad=silero.VAD.load(),
40 stt=openai.STT(),
41 llm=openai.LLM(),
42 tts=openai.TTS(),
43 chat_ctx=[
44 llm.ChatMessage(
45 role="system",
46 content="You are a helpful voice assistant with persistent memory. "
47 "Remember details from previous conversations."
48 )
49 ],
50 )
51
52 assistant.start(ctx.room)
53 await zep_agent.start(assistant)
54
55 logging.info("Voice assistant with Zep memory is running")
56 await assistant.aclose()
57
58if __name__ == "__main__":
59 cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))

Learn more