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,silero]>=1.0.0"

Version Requirements: This integration requires LiveKit Agents v1.0+ (not v0.x). The examples use the current AgentSession API pattern introduced in v1.0.

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 import agents
5from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli
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 agent session with components
2 session = agents.AgentSession(
3 stt=openai.STT(),
4 llm=openai.LLM(model="gpt-4o-mini"),
5 tts=openai.TTS(),
6 vad=silero.VAD.load(),
7 )
8
9 # Create Zep memory agent with enhanced configuration
10 zep_agent = ZepUserAgent(
11 zep_client=zep_client,
12 user_id=user_id,
13 thread_id=thread_id,
14 context_mode="basic", # or "summary"
15 user_message_name=participant_name,
16 assistant_message_name="Assistant",
17 instructions="You are a helpful voice assistant with persistent memory. "
18 "Remember details from previous conversations and reference them naturally."
19 )
20
21 # Start the session with the agent
22 await session.start(agent=zep_agent, room=ctx.room)
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 session running
5 await session.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 agent session with components
2 session = agents.AgentSession(
3 stt=openai.STT(),
4 llm=openai.LLM(model="gpt-4o-mini"),
5 tts=openai.TTS(),
6 vad=silero.VAD.load(),
7 )
8
9 # Create Zep graph agent
10 zep_agent = ZepGraphAgent(
11 zep_client=zep_client,
12 graph_id=graph_id,
13 facts_limit=15, # Max facts in context
14 entity_limit=8, # Max entities in context
15 instructions="You are a knowledgeable voice assistant. Use the provided "
16 "context about entities and facts to give informed responses."
17 )
18
19 # Start the session with the graph agent
20 await session.start(agent=zep_agent, room=ctx.room)

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 import agents
5from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli
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 agent session
27 session = agents.AgentSession(
28 stt=openai.STT(),
29 llm=openai.LLM(model="gpt-4o-mini"),
30 tts=openai.TTS(),
31 vad=silero.VAD.load(),
32 )
33
34 # Create voice assistant with Zep memory
35 zep_agent = ZepUserAgent(
36 zep_client=zep_client,
37 user_id=user_id,
38 thread_id=thread_id,
39 context_mode="basic",
40 user_message_name=participant_name,
41 assistant_message_name="Assistant",
42 instructions="You are a helpful voice assistant with persistent memory. "
43 "Remember details from previous conversations."
44 )
45
46 # Start the session with the agent
47 await session.start(agent=zep_agent, room=ctx.room)
48
49 logging.info("Voice assistant with Zep memory is running")
50 await session.aclose()
51
52if __name__ == "__main__":
53 cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))

Learn more