I am using livekit-test. How do I integrate custom...
# ask-ai
l
I am using livekit-test. How do I integrate custom knowledgebase to test the voice ai agent ?
t
Thanks for reaching out, Jay. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about integrating a custom knowledge base with livekit-test to test a voice AI agent. You can integrate a custom knowledge base using retrieval-augmented generation (RAG) inside your agent’s testing flow. Below is a concise, practical plan you can follow, plus short examples you can adapt. 1) How it fits into the agent • Add a RAG lookup during the user turn lifecycle (for example, in
on_user_turn_completed
) so the agent can inject retrieved knowledge into the chat context before generating the assistant reply. • This ensures the voice agent’s TTS/response is grounded on your custom knowledge content. 2) Recommended implementation approaches • Live lookup: Call your vector DB / search API (LlamaIndex, Pinecone, Weaviate, custom service) during
on_user_turn_completed
, then add the retrieved snippet to
turn_ctx
as an assistant/system message to provide context for the next response. • Example pattern (pseudo-Python):
Copy code
async def on_user_turn_completed(self, turn_ctx: ChatContext, new_message: ChatMessage) -> None:
    # 1) Get user query
    query = new_message.text_content()
    # 2) Run RAG lookup against your knowledge base
    rag_result = await my_rag_lookup(query)   # implement this to call your vector DB or search service
    # 3) Inject retrieved content into the conversation context
    turn_ctx.add_message(
        role="assistant",
        content=f"Relevant knowledgebase content:\n{rag_result}"
    )
3) Testing strategy with livekit-test • Mock the RAG calls during tests so responses are deterministic and you can assert expected behavior. • Use LiveKit’s testing helpers to run a session, simulate voice/user turns, and check the assistant output. • Mocking example (conceptual):
Copy code
from livekit.agents import mock_tools

def fake_rag(query: str) -> str:
    if "refund" in query.lower():
        return "Our refund policy: returns within 30 days."
    return "No relevant info."

with mock_tools(MyAssistant, {"my_rag_lookup": fake_rag}):
    result = await session.run(user_input="What is your refund policy?")
    # assert the assistant message references the 30-day refund policy
4) End-to-end checks you should run • Single-turn grounding: Verify the assistant uses injected knowledge in its immediate reply. • Multi-turn consistency: Ask follow-ups that rely on the same KB content to ensure context persists. • Voice-specific checks: Ensure the TTS output is using the assistant message that contains the retrieved knowledge (if you synthesize speech from the assistant message). 5) Automated evaluation • Use an LLM-as-judge to evaluate whether the assistant’s response matches the intended intent and correctly reflects the knowledge base content. • Example: after running a test turn, call
judge()
on the assistant message with an intent like “Explains refund policy correctly.” 6) Quick checklist before running tests • Confirm your RAG function returns short, relevant snippets suitable for injecting into chat context. • Ensure your test harness can mock or stub external KB calls. • Confirm the TTS pipeline reads from the assistant message created after RAG injection. If you’d like, tell me: • Which vector DB or search system you plan to use (or if you’ll mock it), and • Whether you want examples for a specific language (Python, Node.js) or how to wire this into TTS voice testing— and I’ll provide a tailored code example you can drop into your livekit-test workflow. Sources: External data and RAG | LiveKit Documentation | Testing and evaluation | LiveKit Documentation
l
I am using Python and ChromaDB
t
Thanks — I’m responding to your follow-up about using Python with ChromaDB to integrate a custom knowledge base into your livekit-test workflow for a voice AI agent. Below is a focused, practical guide you can adapt. Summary of approach • Run a RAG lookup against ChromaDB during the agent lifecycle (e.g., in
on_user_turn_completed
). • Inject the retrieved snippet into the chat context so the assistant reply (and resulting TTS) is grounded in KB content. • During tests, mock the ChromaDB lookup for deterministic, reproducible assertions. • Optionally use an LLM judge to validate correctness. Example components (Python) 1) Minimal ChromaDB RAG lookup (concept) • Implement a function that queries Chroma, ranks results, and returns a short, relevant snippet.
Copy code
from chromadb import Client
from chromadb.config import Settings

# Initialize client (adjust settings/embedding method as needed)
client = Client(Settings(chroma_db_impl="chromadb.db.sqlite", persist_directory="./chroma_store"))
collection = client.get_or_create_collection("my_kb")

async def chroma_rag_lookup(query: str, k: int = 3) -> str:
    # If you use synchronous Chroma calls, you can keep this non-async.
    results = collection.query(query_texts=[query], n_results=k)
    # results["documents"] typically contains nested lists: choose top hits
    docs = results.get("documents", [[]])[0]
    # Concatenate or select the most relevant snippet(s)
    if not docs:
        return "No relevant information found."
    # Return a short summary/concatenation
    return "\n\n".join(docs[:k])
2) Injecting KB results into the agent lifecycle • Use
on_user_turn_completed
to fetch Chroma results and add them to the turn context so the next assistant generation uses them.
Copy code
async def on_user_turn_completed(self, turn_ctx: ChatContext, new_message: ChatMessage) -> None:
    query = new_message.text_content()
    rag_content = await chroma_rag_lookup(query)   # await only if your lookup is async
    if rag_content:
        turn_ctx.add_message(
            role="assistant",
            content=f"Relevant knowledgebase content:\n{rag_content}"
        )
Notes: • Keep injected snippets short and clearly labeled so the model knows to use them as grounding. • If you summarize results (preferred), consider running a concise summarization step before injection to reduce token usage. 3) Mocking Chroma in tests (deterministic) • During livekit-test runs, mock the
chroma_rag_lookup
function so tests don’t depend on the DB and return predictable content.
Copy code
from livekit.agents import mock_tools

def fake_chroma_rag(query: str) -> str:
    if "refund" in query.lower():
        return "Our refund policy: returns accepted within 30 days after purchase."
    return "No relevant information found."

with mock_tools(MyAssistantClass, {"chroma_rag_lookup": fake_chroma_rag}):
    result = await session.run(user_input="What is your refund policy?")
    await result.expect.next_event().is_message(role="assistant").judge(
        llm, intent="Explains refund policy correctly based on KB content."
    )
4) Voice / TTS considerations • Confirm your TTS pipeline consumes the assistant message created after your RAG injection. • If you synthesize speech immediately after generation, ensure the final assistant message contains the KB-grounded content before TTS runs. 5) Multi-turn and evaluation • Test multi-turn flows where later questions reference earlier KB content to validate context persistence. • Use an LLM-as-judge to assert whether the assistant response correctly uses KB content and answers intent. 6) Practical tips • Store and return short snippets (1–3 paragraphs) to avoid excessive token use. • Consider caching frequent queries to speed tests. • If your embeddings/Chroma setup is synchronous, use synchronous calls in the agent lifecycle or wrap them with an executor to avoid blocking the event loop. If you’d like, I can: • Provide a complete runnable example that wires ChromaDB, the agent lifecycle hook, and a minimal livekit-test session in Python. • Show how to summarize retrieved docs before injection (to save tokens). • Help with the exact mocking pattern for your test runner (pytest / asyncio specifics). Which would you like next? A full runnable example or the summarization + token-saving pattern?