broad-boots-9735
02/16/2026, 6:56 PMtall-airline-16756
02/16/2026, 6:57 PMtall-airline-16756
02/16/2026, 6:57 PMsendFile (file is sent as a byte stream).
2. Agent: register a byte‑stream handler for the file topic to receive and reconstruct the file.
3. Extract: convert the received file into plain text (use a parser library appropriate for the file type).
4. Index: store the extracted text into a vector database or search index for retrieval.
5. RAG at query time: on relevant user turns, run a retrieval query, inject the retrieved context into the agent’s chat context, and let the LLM generate a response that uses the retrieved content.
6. UX: optionally stream progress/status updates to the user while processing and indexing.
Concrete steps and implementation notes
1) Upload file from the frontend
• Use the LiveKit client SDK’s sendFile method to send the file as a byte stream to your agent. This ensures secure transfer and lets the agent receive chunks incrementally.
2) Receive the file in the agent
• Register a byte stream handler in the agent for your chosen topic (e.g., "files").
• The handler should read incoming byte chunks asynchronously and reconstruct the file in memory or write to temporary storage.
3) Convert file to text (extraction)
• After reconstructing the file, convert it to text:
◦ PDFs → use a PDF parser (e.g., PyPDF2, pdfminer.six) or an OCR tool if it’s scanned.
◦ DOCX → use python-docx.
◦ Images → OCR (Tesseract or an OCR API).
• The LiveKit docs don’t specify internal parsers for every file type, so use external libraries or services for extraction.
4) Index text into a vector store
• Split the extracted text into chunks (e.g., 500–1000 tokens) with overlap.
• Generate embeddings (your chosen embedding model) for each chunk.
• Upsert those embeddings and chunk metadata into your vector DB (e.g., Pinecone, Milvus, Weaviate, or an in‑house store).
5) Trigger RAG lookups during conversation
• Use an agent lifecycle hook such as on_user_turn_completed to run retrieval when a user message arrives (or when you want to perform retrieval).
• Perform a similarity search against your vector DB to fetch top relevant chunks.
• Inject the retrieved text into the agent’s chat context as an assistant/system message or as additional context so the model can use it to generate an answer.
Example pattern (pseudocode)
async def on_user_turn_completed(self, turn_ctx, new_message):
# 1) Do a vector search for new_message.text
retrieved = await my_rag_lookup(new_message.text_content())
# 2) Inject as context for generation
turn_ctx.add_message(
role="assistant",
content=f"Retrieved context:\n{retrieved}"
)
# 3) Proceed with normal generation (agent will call LLM)
6) Real‑time feedback (optional)
• While the file is being processed/indexed, use session generation/status updates to notify the user that processing is in progress. This improves perceived responsiveness.
Practical considerations and tips
• Chunking strategy and embedding latency impact retrieval relevance and response time; tune chunk size and overlap for your content.
• Keep metadata (filename, upload time, uploader, doc type, page numbers) with indexed vectors to surface provenance in responses.
• For large files, process and index asynchronously; inform the user when the document is ready for queries.
• Sanitize and validate uploads (size limits, file types) before processing.
Where to look for examples
• There is an example repository demonstrating RAG integration with external data (LlamaIndex example). Use it as a reference for indexing and hooking retrieval into the agent flow.
Next steps I can help with
• If you want, tell me:
◦ Which file types you need to support (PDF, DOCX, images)?
◦ Which vector DB and embedding model you plan to use (or if you need recommendations)?
◦ Whether you want synchronous answers right after upload or asynchronous processing with a “document ready” notification?
I’ll provide a step‑by‑step code example tailored to your stack.
Sources: Vision | LiveKit Documentation | External data and RAG | LiveKit Documentation | Pipeline nodes and hooks | LiveKit Documentationbroad-boots-9735
02/16/2026, 7:06 PMtall-airline-16756
02/16/2026, 7:06 PMfiles topic).
2. Extracting text from a PDF or DOCX (I’ll include PDF extraction via pdfplumber and a DOCX path with python-docx).
3. Chunking and embedding text (example with OpenAI embeddings).
4. Indexing into FAISS.
5. Triggering retrieval in on_user_turn_completed, injecting retrieved context, and generating the reply.
Tell me which vector store you prefer, and I’ll produce the complete Python code.broad-boots-9735
02/16/2026, 7:15 PMtall-airline-16756
02/16/2026, 7:15 PMon_user_turn_completed, injecting retrieved context, and generating a reply.
Before using this code, install required packages:
• faiss-cpu, openai (or your embedding client), pdfplumber, python-docx, tiktoken (optional for token chunking).
Example (single-file illustrative agent):
import io
import asyncio
from typing import List
import faiss
import numpy as np
import pdfplumber
import docx
# Replace with your embeddings client. Example shows OpenAI-style interface.
import openai
# --- Simple text extraction helpers ---
def extract_text_from_pdf_bytes(b: bytes) -> str:
with pdfplumber.open(io.BytesIO(b)) as pdf:
pages = [p.extract_text() or "" for p in pdf.pages]
return "\n".join(pages)
def extract_text_from_docx_bytes(b: bytes) -> str:
doc = docx.Document(io.BytesIO(b))
paras = [p.text for p in doc.paragraphs]
return "\n".join(paras)
# --- Chunking helper ---
def chunk_text(text: str, max_chars: int = 2000, overlap: int = 200) -> List[str]:
chunks = []
start = 0
length = len(text)
while start < length:
end = min(start + max_chars, length)
chunks.append(text[start:end])
start = end - overlap
if start < 0:
start = 0
return chunks
# --- Embeddings helper (OpenAI-style) ---
def get_embeddings(texts: List[str]) -> List[List[float]]:
# Batch calls if needed.
# Replace 'text-embedding-3-small' with your model.
resp = openai.Embedding.create(model="text-embedding-3-small", input=texts)
return [d["embedding"] for d in resp["data"]]
# --- FAISS wrapper ---
class FaissIndex:
def __init__(self, dim: int):
self.dim = dim
self.index = faiss.IndexFlatL2(dim) # simple flat index
self.metadatas = [] # keep metadata and original text
def add(self, embeddings: List[List[float]], metadatas: List[dict]):
arr = np.array(embeddings).astype("float32")
self.index.add(arr)
self.metadatas.extend(metadatas)
def search(self, query_emb: List[float], top_k: int = 5):
q = np.array([query_emb]).astype("float32")
distances, indices = self.index.search(q, top_k)
results = []
for dist, idx in zip(distances[0], indices[0]):
if idx == -1 or idx >= len(self.metadatas):
continue
results.append({"distance": float(dist), "metadata": self.metadatas[idx]})
return results
# --- Simple in-memory document store and index manager ---
class DocumentManager:
def __init__(self, embed_dim=1536):
self.index = FaissIndex(embed_dim)
async def add_document_from_bytes(self, filename: str, content_bytes: bytes):
# Decide parser by file extension
if filename.lower().endswith(".pdf"):
text = extract_text_from_pdf_bytes(content_bytes)
elif filename.lower().endswith(".docx"):
text = extract_text_from_docx_bytes(content_bytes)
else:
# treat as plain text
text = content_bytes.decode("utf-8", errors="ignore")
chunks = chunk_text(text)
embeddings = get_embeddings(chunks)
metadatas = [{"filename": filename, "chunk": i, "text": chunks[i][:1000]} for i in range(len(chunks))]
self.index.add(embeddings, metadatas)
return {"status": "ok", "chunks_indexed": len(chunks)}
async def query(self, query_text: str, top_k: int = 3):
q_emb = get_embeddings([query_text])[0]
return self.index.search(q_emb, top_k=top_k)
# --- LiveKit Agent byte-stream handler & lifecycle hook (pseudocode integration) ---
# Replace the agent base classes and decorators with the actual LiveKit Agents SDK constructs.
class ExampleAgent:
def __init__(self):
# Set embedding dim to match your embedding model
self.docs = DocumentManager(embed_dim=1536)
async def handle_file_stream(self, filename: str, stream_async_iter):
# stream_async_iter yields bytes chunks
chunks = []
async for b in stream_async_iter:
chunks.append(b)
file_bytes = b"".join(chunks)
result = await self.docs.add_document_from_bytes(filename, file_bytes)
# Optionally notify user the file was processed via session.generate_reply or similar.
return result
async def on_user_turn_completed(self, turn_ctx, new_message):
# new_message.text_content() -> the user query
user_q = new_message.text_content()
# Run retrieval
results = await self.docs.query(user_q, top_k=4)
# Build a context string from retrieved results
context_parts = []
for r in results:
md = r["metadata"]
context_parts.append(f"From {md['filename']} (chunk {md['chunk']}):\n{md['text']}\n---\n")
retrieved_context = "\n".join(context_parts) if context_parts else "No relevant docs found."
# Inject the retrieved context into the turn context before generation
turn_ctx.add_message(role="assistant", content=f"Retrieved context:\n{retrieved_context}")
# The agent framework should now proceed to call the LLM to generate the reply
# Optionally you can call session.generate_reply() or let the framework continue.
# --- Usage example (conceptual) ---
async def demo_flow():
agent = ExampleAgent()
# Simulate receiving a PDF upload (in practice this comes from LiveKit's byte stream)
with open("example.pdf", "rb") as f:
file_bytes = f.read()
await agent.docs.add_document_from_bytes("example.pdf", file_bytes)
# Simulate an incoming user message/turn
class FakeMessage:
def __init__(self, text): self._t = text
def text_content(self): return self._t
class FakeTurnCtx:
def add_message(self, role, content):
print(f"Injected to turn ctx ({role}):\n{content[:1000]}...\n")
await agent.on_user_turn_completed(FakeTurnCtx(), FakeMessage("What does the document say about refund policy?"))
if __name__ == "__main__":
asyncio.run(demo_flow())
Notes and next steps
• Replace the embedding calls (get_embeddings) with your chosen provider if not using OpenAI. Ensure the embedding dimension passed to FAISS matches your model’s dimension.
• For production:
◦ Persist FAISS index and metadata (save/load).
◦ Process large files asynchronously and notify users when indexing completes.
◦ Add file validation (max size, allowed types) and sanitize inputs.
◦ Use more advanced FAISS indexes (IVF, HNSW) for scale.
◦ Keep provenance (page numbers, offsets) in metadata to surface exact text snippets.
• If you want, I can:
◦ Convert this pseudocode to match your exact LiveKit Agents SDK method names and event signatures.
◦ Add streaming progress updates to the user during upload/processing.
◦ Show persistence (save/load FAISS + metadata) and a production-ready indexing pipeline.
Which would you like next?broad-boots-9735
02/16/2026, 7:35 PMtall-airline-16756
02/16/2026, 7:36 PMsilero plugin loads voice utilities (VAD) commonly used for turn detection.
3) Create your environment variables
• Add a .env file with your LiveKit credentials:
◦ LIVEKIT_URL=your_livekit_url
◦ LIVEKIT_API_KEY=your_api_key
◦ LIVEKIT_API_SECRET=your_api_secret
4) Download required model files
• Download models used by plugins (VAD, noise cancellation, etc.):
◦ uv run agent.py download-files
5) Minimal agent code
• Save this as agent.py (or adapt starter repo). This is a minimal example that preloads VAD, defines an Agent, and runs via the provided CLI:
import logging
from dotenv import load_dotenv
from livekit.agents import Agent, AgentSession, AgentServer, JobContext, cli
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("my-agent")
logger.setLevel(<http://logging.INFO|logging.INFO>)
server = AgentServer()
def prewarm(proc):
# preload VAD to speed up connection handling
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
class MyAgent(Agent):
def __init__(self) -> None:
super().__init__(instructions="You are a helpful voice assistant.")
async def on_enter(self):
# sends an initial greeting to the session participants
self.session.generate_reply("Hello! How can I help you?")
@server.rtc_session()
async def entrypoint(ctx: JobContext):
session = AgentSession(
vad=ctx.proc.userdata["vad"],
stt="deepgram/nova-3-general",
llm="openai/gpt-4.1-mini",
tts="cartesia/sonic-2:6f84f4b8-58a2-430c-8c79-688dad597532"
)
await session.start(agent=MyAgent(), room=ctx.room)
await ctx.connect()
if __name__ == "__main__":
cli.run_app(server)
6) Run the agent locally
• Development console mode:
◦ uv run agent.py console
• Dev mode (connect to LiveKit Cloud for testing):
◦ uv run agent.py dev
• Production start:
◦ uv run agent.py start
7) Deploying (optional)
• Use the LiveKit CLI to scaffold and deploy:
◦ lk agent create
• That will prepare Docker/config files and help deploy your agent.
Practical tips
• Adjust STT/LLM/TTS model strings to match your providers.
• For production: persist session state as needed, add logging/metrics, validate uploaded files, and tune resource limits.
• If you plan to add RAG or file uploads, I can integrate the FAISS example into this agent structure.
Would you like:
• A tailored agent template that includes the FAISS RAG flow (file upload + indexing + retrieval)?
• Or help wiring specific STT/LLM/TTS providers you plan to use? Sources: Voice AI quickstart | LiveKit Documentation | Agents (Python) | livekit/livekit_composite | DeepWikibroad-boots-9735
02/16/2026, 7:47 PMtall-airline-16756
02/16/2026, 7:47 PMlk agent create
This creates Docker/config files (Dockerfile, livekit.toml, etc.), packages your agent code, and deploys it to LiveKit Cloud.
• Redeploy an updated agent:
lk agent deploy
Notes:
• Ensure your LiveKit credentials are configured (env vars or in livekit.toml) before running the commands.
• After lk agent create, you can customize the generated Dockerfile and livekit.toml then use lk agent deploy for subsequent updates.
Would you like a quick checklist of the env vars or example livekit.toml to use before running these commands?
Sources: Deploy LiveKit Agents with GitHub Actions | LiveKit | Agent deployment overview | LiveKit Documentation | Voice AI quickstart | LiveKit Documentationbroad-boots-9735
02/16/2026, 7:50 PMtall-airline-16756
02/16/2026, 7:51 PMlk agent create can proceed.
What happened
• lk agent create looks for one of these files to determine language and package your agent:
◦ For Python: pyproject.toml or requirements.txt
◦ For Node: package.json
• Your directory doesn’t contain any of those files, so the CLI stops with the error you saw.
How to fix it (quick options)
• If you already have a Python agent, ensure you’re in the correct directory (the one that contains pyproject.toml or requirements.txt). Then re-run the command.
• If you don’t have those files yet, create one now. The simplest approach for a Python agent:
Option A — create a minimal pyproject.toml (recommended)
1. In your project root, create pyproject.toml with minimal content:
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
1. Add your dependencies in pyproject.toml or add a requirements.txt if you prefer.
Option B — create requirements.txt
1. Create a requirements.txt file listing your runtime deps, for example:
livekit-agents[silero]
python-dotenv
openai
1. Save it in the project root.
After adding one of those files, run:
lk agent create
from the same directory.
Extra tips
• Confirm you’re in the exact folder that holds your agent entrypoint (e.g., agent.py or src/agent.py). The CLI packages the directory contents, so being in the right folder matters.
• If you used the starter repo, run lk agent create from that repo root where pyproject.toml is present.
• If you want, I can generate a minimal pyproject.toml and example requirements.txt tailored to your app. Tell me:
◦ Do you have an existing entrypoint file (e.g., agent.py or src/agent.py)? If so, what’s the path?
◦ Which dependencies do you need (STT/LLM/TTS plugins, RAG libs, FAISS, etc.)?
Tell me which option you prefer and I’ll produce exact file contents you can paste into your project.broad-boots-9735
02/16/2026, 7:51 PMtall-airline-16756
02/16/2026, 7:52 PMlk agent init <agent_name> --lang python
What this does
• Creates a new folder named <agent_name> and scaffolds a complete Python agent project (source files like agent.py, pyproject.toml, Dockerfile, .dockerignore, README, etc.).
• Gives you a ready starter you can run locally and then deploy.
Quick next steps
1. Run the init command:
lk agent init my-voice-agent --lang python
cd my-voice-agent
1. Install dependencies and download model files (from the starter):
uv pip install
uv run agent.py download-files
1. Run in dev/console mode:
uv run agent.py dev
# or
uv run agent.py console
1. Create/deploy to LiveKit Cloud:
# one-time create (generates livekit.toml, Dockerfile and deploys)
lk agent create --secrets-file .env
# subsequent updates
lk agent deploy
Troubleshooting tips
• If lk or uv isn’t installed or authenticated, install the LiveKit CLI and authenticate (or run lk cloud auth if your setup requires it).
• Run lk agent init from a directory where you want the new project created (it will make a new subfolder).
• If you already have a project and prefer to scaffold inside it, you can copy files from the starter repo instead of init.
Would you like me to:
• Generate the exact .env and pyproject.toml contents you can paste into your current folder?
• Or walk through the `lk`/`uv` install and auth steps if you’re seeing CLI errors? Sources: Examples | livekit/livekit_composite | DeepWiki | Agents (Node.js) | livekit/livekit_composite | DeepWiki | Voice AI quickstart | LiveKit Documentationbroad-boots-9735
02/16/2026, 7:58 PMimport logging
from dotenv import load_dotenv
from livekit import rtc
from livekit.agents import (
Agent,
AgentServer,
AgentSession,
JobContext,
JobProcess,
cli,
inference,
room_io,
)
from livekit.plugins import noise_cancellation, silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel
logger = logging.getLogger("agent")
load_dotenv(".env.local")
class Assistant(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""You are Jim the internal information technology support assistant for NorthBridge Consulting, a fictional consulting company. You help employees with technical issues, software access, device requests, and basic internal guidance.
General Behavior
- Communicate in a calm, friendly, and professional manner.
- Keep responses concise and natural.
- Ask only one question at a time.
- Prefer the simplest and safest action first.
- Never request sensitive information such as passwords.
- When identification is needed, ask only for the employee ID.
- Never mention internal systems, tools, or automation mechanisms.
Identity and Context
- The user is always an employee of NorthBridge Consulting.
- Request the employee ID only when required to perform actions or submit requests.
Company Approved Software Catalog
NorthBridge Consulting already provides and pays for the following software:
- CloudDocs, for document management
- MeetSpace, for video meetings
- InsightCRM, for client management
- SecureShare, for secure file exchange
- TimeTrack, for project hours and billing
- ExpenseFlow, for expense submissions
- PeopleHub, for human resources
- KnowledgeCore, for internal documentation
- TaskFlow, for internal task management
- DataBoard, for analytics and reporting
Software Access Rules
When an employee asks for access to software that exists in the approved catalog:
- Confirm assistance without mentioning approvals or back-end logic.
- Ask for the employee ID.
- After receiving the ID, confirm that the access request has been submitted.
- Inform the employee that updates will be sent by email.
Example behavior:
"I can help with that. Please provide your employee ID."
After receiving the ID:
"Thank you. Your access request has been submitted. You will receive updates by email soon."
When an employee asks for software not listed in the approved catalog:
- Inform the employee that the software is not currently available.
- Ask for a short description of the business need.
- After receiving the explanation, request the employee ID.
- Confirm that the request has been submitted for evaluation.
- Clarify that follow-up will occur by email.
Example behavior:
"That software is not currently available. Could you briefly describe why you need it?"
After receiving the justification:
"Thank you for the explanation. Please provide your employee ID so I can submit the request."
After receiving the ID:
"Your request has been submitted for review. You will receive updates by email."
Login and Access Issues
- Trigger when employees report sign-in problems or access errors.
- Provide reassurance and simple checks.
- Never request passwords.
- Escalate when needed.
Troubleshooting Behavior
- Trigger when employees report systems not working, slowness, or errors.
- Provide one simple step at a time.
- Confirm results before continuing.
Hardware and Device Requests
- Handle requests for new devices or hardware problems.
- Offer ticket creation or contact with responsible staff.
Ticketing and Escalation
- When issues require investigation or approval, submit a support request.
- Clearly state that updates will be provided by email.
- Do not expose internal identifiers or workflows.
Tone and Interaction Safety
- Maintain a supportive and professional tone.
- Avoid technical jargon unless clearly necessary.
- Never blame the employee.
- Keep conversations clear and efficient.
""",
)
# To add tools, use the @function_tool decorator.
# Here's an example that adds a simple weather tool.
# You also have to add `from livekit.agents import function_tool, RunContext` to the top of this file
# @function_tool
# async def lookup_weather(self, context: RunContext, location: str):
# """Use this tool to look up current weather information in the given location.
#
# If the location is not supported by the weather service, the tool will indicate this. You must tell the user the location's weather is unavailable.
#
# Args:
# location: The location to look up weather information for (e.g. city name)
# """
#
# <http://logger.info|logger.info>(f"Looking up weather for {location}")
#
# return "sunny with a temperature of 70 degrees."
server = AgentServer()
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
@server.rtc_session()
async def my_agent(ctx: JobContext):
# Logging setup
# Add any other context you want in all log entries here
ctx.log_context_fields = {
"room": ctx.room.name,
}
# Set up a voice AI pipeline using OpenAI, Cartesia, Deepgram, and the LiveKit turn detector
session = AgentSession(
# Speech-to-text (STT) is your agent's ears, turning the user's speech into text that the LLM can understand
# See all available models at <https://docs.livekit.io/agents/models/stt/>
stt=inference.STT(model="deepgram/nova-3", language="multi"),
# A Large Language Model (LLM) is your agent's brain, processing user input and generating a response
# See all available models at <https://docs.livekit.io/agents/models/llm/>
llm=inference.LLM(model="openai/gpt-4.1-mini"),
# Text-to-speech (TTS) is your agent's voice, turning the LLM's text into speech that the user can hear
# See all available models as well as voice selections at <https://docs.livekit.io/agents/models/tts/>
tts=inference.TTS(
model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"
),
# VAD and turn detection are used to determine when the user is speaking and when the agent should respond
# See more at <https://docs.livekit.io/agents/build/turns>
turn_detection=MultilingualModel(),
vad=ctx.proc.userdata["vad"],
# allow the LLM to generate a response while waiting for the end of turn
# See more at <https://docs.livekit.io/agents/build/audio/#preemptive-generation>
preemptive_generation=True,
)
# To use a realtime model instead of a voice pipeline, use the following session setup instead.
# (Note: This is for the OpenAI Realtime API. For other providers, see <https://docs.livekit.io/agents/models/realtime/))>
# 1. Install livekit-agents[openai]
# 2. Set OPENAI_API_KEY in .env.local
# 3. Add `from livekit.plugins import openai` to the top of this file
# 4. Use the following session setup instead of the version above
# session = AgentSession(
# llm=openai.realtime.RealtimeModel(voice="marin")
# )
# # Add a virtual avatar to the session, if desired
# # For other providers, see <https://docs.livekit.io/agents/models/avatar/>
# avatar = hedra.AvatarSession(
# avatar_id="...", # See <https://docs.livekit.io/agents/models/avatar/plugins/hedra>
# )
# # Start the avatar and wait for it to join
# await avatar.start(session, room=ctx.room)
# Start the session, which initializes the voice pipeline and warms up the models
await session.start(
agent=Assistant(),
room=ctx.room,
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=lambda params: (
noise_cancellation.BVCTelephony()
if params.participant.kind
== rtc.ParticipantKind.PARTICIPANT_KIND_SIP
else noise_cancellation.BVC()
),
),
),
)
# Join the room and connect to the user
await ctx.connect()
if __name__ == "__main__":
cli.run_app(server)tall-airline-16756
02/16/2026, 7:58 PM