Building a Knowledge Base Chatbot with CrewAI: Complete Guide
Knowledge base chatbots have become essential tools for businesses looking to automate customer support, internal documentation access, and information retrieval. CrewAI, a powerful multi-agent framework built on top of LangChain, provides an elegant way to build intelligent chatbots that can reason, search, and respond accurately using your custom knowledge base. In this complete guide, you will learn how to build a production-ready knowledge base chatbot using CrewAI from scratch.
What Is CrewAI?
CrewAI is an open-source Python framework designed to orchestrate role-playing autonomous AI agents. Each agent has a specific role, goal, and backstory, and they work together as a "crew" to accomplish complex tasks. Unlike single-prompt chatbot solutions, CrewAI enables you to build collaborative agent systems where multiple specialized agents can share information, delegate tasks, and produce higher-quality outputs.
For knowledge base chatbots, CrewAI shines because it allows you to separate concerns: one agent can handle document retrieval, another can synthesize answers, and a third can verify accuracy. This modular approach leads to more reliable and trustworthy responses.
Why Use CrewAI for Knowledge Base Chatbots?
Traditional retrieval-augmented generation (RAG) pipelines often struggle with complex queries that require multi-step reasoning or cross-referencing multiple documents. CrewAI addresses these limitations through several key advantages:
- Multi-agent collaboration: Different agents can specialize in different tasks, such as retrieval, synthesis, and fact-checking.
- Role-based design: Agents have clearly defined roles and goals, making the system easier to reason about and debug.
- Tool integration: CrewAI seamlessly integrates with LangChain tools, allowing agents to search vector databases, APIs, and external services.
- Process control: You can define sequential or hierarchical workflows to control how agents interact.
- Observability: Built-in logging and verbose modes help you understand agent decision-making.
Prerequisites and Setup
Before building the chatbot, ensure you have Python 3.10 or higher installed. You will also need an OpenAI API key or another supported LLM provider. Let us start by setting up the project environment.
Installing Dependencies
Create a new project directory and install the required packages:
mkdir crewai-kb-chatbot
cd crewai-kb-chatbot
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install crewai crewai-tools langchain chromadb sentence-transformers pypdf
Next, set your API keys as environment variables:
export OPENAI_API_KEY="your-openai-api-key-here"
Project Structure
Organize your project with the following structure:
crewai-kb-chatbot/
├── data/
│ └── knowledge_base/
│ ├── product_docs.pdf
│ ├── faq.md
│ └── policies.txt
├── crew/
│ ├── __init__.py
│ ├── agents.py
│ ├── tasks.py
│ ├── tools.py
│ └── crew.py
├── app.py
└── requirements.txt
Building the Knowledge Base
The foundation of any knowledge base chatbot is the data it can access. We will use ChromaDB as our vector store and sentence-transformers for embeddings. This combination is free, local, and performs well for most use cases.
Creating the Document Search Tool
CrewAI provides a built-in PDFSearchTool and DirectoryReadTool, but for maximum flexibility, we will build a custom tool that searches across multiple file types in a directory.
Create crew/tools.py:
import os
from crewai.tools import BaseTool
from langchain_community.document_loaders import (
DirectoryLoader,
TextLoader,
PyPDFLoader,
UnstructuredMarkdownLoader
)
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
class KnowledgeBaseTool(BaseTool):
name: str = "knowledge_base_search"
description: str = (
"Search the company knowledge base for relevant information. "
"Input should be a search query string. Returns relevant "
"document excerpts that can be used to answer user questions."
)
def _run(self, query: str) -> str:
persist_dir = os.getenv("CHROMA_PERSIST_DIR", "./chroma_db")
embeddings = HuggingFaceEmbeddings(
model_name="all-MiniLM-L6-v2"
)
vectorstore = Chroma(
persist_directory=persist_dir,
embedding_function=embeddings
)
results = vectorstore.similarity_search_with_relevance_scores(
query, k=5
)
if not results:
return "No relevant documents found in the knowledge base."
output = []
for i, (doc, score) in enumerate(results, 1):
source = doc.metadata.get("source", "unknown")
output.append(
f"--- Result {i} (Relevance: {score:.2f}) ---\n"
f"Source: {source}\n"
f"Content: {doc.page_content}\n"
)
return "\n".join(output)
def build_vectorstore(data_dir: str = "./data/knowledge_base"):
"""Build and persist the vector store from documents."""
loaders = {
".txt": lambda p: TextLoader(p),
".md": lambda p: UnstructuredMarkdownLoader(p),
".pdf": lambda p: PyPDFLoader(p),
}
documents = []
for root, _, files in os.walk(data_dir):
for file in files:
ext = os.path.splitext(file)[1].lower()
if ext in loaders:
path = os.path.join(root, file)
try:
docs = loaders[ext](path).load()
documents.extend(docs)
except Exception as e:
print(f"Error loading {path}: {e}")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " ", ""]
)
chunks = text_splitter.split_documents(documents)
embeddings = HuggingFaceEmbeddings(
model_name="all-MiniLM-L6-v2"
)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
print(f"Indexed {len(chunks)} chunks from {len(documents)} documents.")
return vectorstore
Defining the Agents
Now we define the agents that will form our crew. For a knowledge base chatbot, we will create three specialized agents: a researcher, an answer synthesizer, and a quality reviewer.
Create crew/agents.py:
from crewai import Agent, LLM
# Define the LLM
llm = LLM(
model="gpt-4o-mini",
temperature=0.3,
max_tokens=2000
)
researcher = Agent(
role="Knowledge Base Researcher",
goal="Find the most relevant and accurate information from the "
"knowledge base to answer the user's question",
backstory=(
"You are an expert research analyst with years of experience "
"in information retrieval. You have a keen eye for finding "
"the most relevant documents and extracting key information. "
"You never fabricate information and always cite your sources."
),
tools=[], # Tools will be injected at crew creation
llm=llm,
verbose=True,
allow_delegation=False
)
synthesizer = Agent(
role="Answer Synthesizer",
goal="Create clear, accurate, and helpful answers based on the "
"research findings",
backstory=(
"You are a skilled technical writer who specializes in "
"explaining complex topics in simple terms. You always "
"structure your answers logically and include relevant "
"details without overwhelming the reader."
),
llm=llm,
verbose=True,
allow_delegation=False
)
reviewer = Agent(
role="Quality Reviewer",
goal="Ensure the answer is accurate, complete, and does not "
"contain hallucinated information",
backstory=(
"You are a meticulous quality assurance specialist. You "
"cross-check every claim against the source material and "
"flag any inconsistencies or unsupported statements. Your "
"standards are uncompromising."
),
llm=llm,
verbose=True,
allow_delegation=False
)
Defining the Tasks
Each agent needs a task to perform. Tasks define what each agent should accomplish and what output they should produce.
Create crew/tasks.py:
from crewai import Task
def create_research_task(question: str):
return Task(
description=(
f"Research the following question using the knowledge "
f"base search tool: '{question}'\n\n"
f"Steps:\n"
f"1. Search the knowledge base with the most relevant query\n"
f"2. If initial results are insufficient, try alternative "
f"search terms\n"
f"3. Collect all relevant excerpts and organize them by topic\n"
f"4. Note the source of each piece of information\n\n"
f"Return a structured summary of all findings with source "
f"references."
),
expected_output=(
"A structured research report containing relevant excerpts "
"from the knowledge base, organized by topic, with source "
"citations for each piece of information."
),
agent=None # Will be assigned at crew creation
)
def create_synthesis_task(question: str):
return Task(
description=(
f"Using the research findings, write a comprehensive "
f"answer to the user's question: '{question}'\n\n"
f"Requirements:\n"
f"- Base your answer ONLY on the research findings\n"
f"- Do not include information not present in the sources\n"
f"- Structure the answer with clear paragraphs\n"
f"- Include relevant details and examples from the sources\n"
f"- If the sources do not contain enough information, "
f"explicitly state what is missing\n\n"
f"Write the answer as if speaking directly to the user."
),
expected_output=(
"A clear, well-structured answer to the user's question, "
"based exclusively on the research findings, with "
"appropriate detail and context."
),
agent=None
)
def create_review_task(question: str):
return Task(
description=(
f"Review the synthesized answer for the question: "
f"'{question}'\n\n"
f"Check for:\n"
f"- Factual accuracy against the research findings\n"
f"- Completeness (does it fully address the question?)\n"
f"- Hallucinations (any claims not supported by sources?)\n"
f"- Clarity and readability\n\n"
f"If issues are found, provide a corrected version. "
f"If the answer is satisfactory, return it as-is with "
f"a brief approval note."
),
expected_output=(
"The final reviewed and approved answer, or a corrected "
"version if issues were found, along with a brief note "
"on any changes made."
),
agent=None
)
Assembling the Crew
Now we bring everything together by creating the crew that orchestrates the agents and tasks.
Create crew/crew.py:
from crewai import Crew, Process
from crew.agents import researcher, synthesizer, reviewer
from crew.tasks import (
create_research_task,
create_synthesis_task,
create_review_task
)
from crew.tools import KnowledgeBaseTool
def create_kb_crew(question: str) -> Crew:
# Instantiate the knowledge base tool
kb_tool = KnowledgeBaseTool()
# Assign the tool to the researcher agent
researcher.tools = [kb_tool]
# Create tasks and assign agents
research_task = create_research_task(question)
research_task.agent = researcher
synthesis_task = create_synthesis_task(question)
synthesis_task.agent = synthesizer
review_task = create_review_task(question)
review_task.agent = reviewer
# Build the crew
crew = Crew(
agents=[researcher, synthesizer, reviewer],
tasks=[research_task, synthesis_task, review_task],
process=Process.sequential,
verbose=True
)
return crew
def ask_question(question: str) -> str:
"""Ask a question to the knowledge base chatbot."""
crew = create_kb_crew(question)
result = crew.kickoff()
return str(result)
Building the Chatbot Interface
Now let us create a simple command-line interface that allows users to interact with the chatbot. We will also include a setup step to build the vector store on first run.
Create app.py:
import os
import sys
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from crew.tools import build_vectorstore
from crew.crew import ask_question
def initialize_knowledge_base():
"""Initialize the vector store if it does not exist."""
chroma_dir = "./chroma_db"
if not os.path.exists(chroma_dir) or not os.listdir(chroma_dir):
print("Building knowledge base index for the first time...")
print("This may take a few minutes depending on your data.\n")
build_vectorstore("./data/knowledge_base")
print("Knowledge base ready!\n")
else:
print("Knowledge base already indexed.\n")
def main():
print("=" * 60)
print(" Knowledge Base Chatbot powered by CrewAI")
print("=" * 60)
print("Type your question and press Enter.")
print("Type 'quit' or 'exit' to stop.\n")
initialize_knowledge_base()
while True:
try:
question = input("\nYou: ").strip()
if question.lower() in ["quit", "exit"]:
print("Goodbye!")
break
if not question:
continue
print("\nBot: Thinking...")
answer = ask_question(question)
print(f"\nBot: {answer}")
except KeyboardInterrupt:
print("\n\nGoodbye!")
break
except Exception as e:
print(f"\nError: {e}")
if __name__ == "__main__":
main()
Running the Chatbot
To run the chatbot, first place your documents in the data/knowledge_base/ directory. These can be PDF files, Markdown files, or plain text files. Then execute:
python app.py
On the first run, the application will index your documents into ChromaDB. Subsequent runs will load the existing index directly. Here is an example interaction:
============================================================
Knowledge Base Chatbot powered by CrewAI
============================================================
Knowledge base already indexed.
You: What is our refund policy?
Bot: Thinking...
Bot: According to the company policies document, our refund policy
states that customers can request a full refund within 30 days of
purchase. After 30 days, partial refunds may be available on a
case-by-case basis. To request a refund, customers should contact
support@company.com with their order number and reason for the
refund request. Refunds are typically processed within 5-7
business days.
Adding a Web Interface with FastAPI
For production use, a web API is often more practical than a CLI. Let us add a FastAPI endpoint so the chatbot can be integrated into web applications.
Install FastAPI and Uvicorn:
pip install fastapi uvicorn
Create api.py:
from fastapi import FastAPI
from pydantic import BaseModel
from crew.crew import ask_question
from crew.tools import build_vectorstore
import os
app = FastAPI(title="Knowledge Base Chatbot API")
class Question(BaseModel):
query: str
class Answer(BaseModel):
response: str
status: str
@app.on_event("startup")
async def startup_event():
chroma_dir = "./chroma_db"
if not os.path.exists(chroma_dir) or not os.listdir(chroma_dir):
build_vectorstore("./data/knowledge_base")
@app.post("/ask", response_model=Answer)
async def ask(question: Question):
try:
result = ask_question(question.query)
return Answer(response=result, status="success")
except Exception as e:
return Answer(response=str(e), status="error")
@app.get("/health")
async def health():
return {"status": "healthy"}
Run the API server:
uvicorn api:app --reload --port 8000
You can now send questions via HTTP:
curl -X POST http://localhost:8000/ask \
-H "Content-Type: application/json" \
-d '{"query": "What is the refund policy?"}'
Best Practices
To get the most out of your CrewAI knowledge base chatbot, follow these best practices:
Document Preparation
- Clean your data: Remove boilerplate, headers, footers, and navigation text before indexing. Noisy data leads to poor retrieval results.
- Use consistent formatting: Structure documents with clear headings and sections. This helps the text splitter produce coherent chunks.
- Keep documents updated: Schedule regular rebuilds of the vector store when source documents change.
Chunking Strategy
- Choose appropriate chunk sizes: 500-1000 characters with 100-200 character overlap works well for most documents. Adjust based on your content density.
- Use semantic splitting: The RecursiveCharacterTextSplitter tries to split on paragraph boundaries first, which preserves context better than fixed-size splitting.
- Add metadata: Include source filenames, page numbers, and document titles as metadata to improve traceability.
Agent Configuration
- Keep agent roles focused: Each agent should have one clear responsibility. Avoid creating agents that try to do everything.
- Tune temperature carefully: Use low temperature (0.1-0.3) for factual tasks like research and review. Higher temperatures may introduce creativity but also hallucinations.
- Write detailed backstories: The backstory shapes agent behavior. Be specific about expertise, standards, and working style.
Performance Optimization
- Cache embeddings: Persist the ChromaDB index so you do not re-embed documents on every run.
- Limit retrieval results: Returning too many chunks can overwhelm the context window. Start with k=5 and adjust based on answer quality.
- Use cost-effective models: GPT-4o-mini provides excellent performance for most knowledge base tasks at a fraction of the cost of larger models.
Handling Edge Cases
- Empty results: Always handle the case where no relevant documents are found. Return a clear message rather than a hallucinated answer.
- Ambiguous questions: Consider adding a clarification agent that asks follow-up questions when the user query is unclear.
- Rate limiting: If deploying as an API, implement rate limiting to prevent abuse and control costs.
Advanced: Adding Conversation Memory
By default, each question is processed independently. To support follow-up questions, you can add conversation memory by maintaining chat history and passing it as context.
Update crew/crew.py with memory support:
from crewai import Crew, Process
from crew.agents import researcher, synthesizer, reviewer
from crew.tasks import (
create_research_task,
create_synthesis_task,
create_review_task
)
from crew.tools import KnowledgeBaseTool
conversation_history = []
def create_kb_crew(question: str, history: list = None) -> Crew:
kb_tool = KnowledgeBaseTool()
researcher.tools = [kb_tool]
# Include conversation context in the question
context = ""
if history:
recent = history[-4:] # Keep last 4 exchanges
context = "\n\nPrevious conversation:\n"
for exchange in recent:
context += f"User: {exchange['question']}\n"
context += f"Bot: {exchange['answer']}\n"
full_question = f"{question}{context}" if context else question
research_task = create_research_task(full_question)
research_task.agent = researcher
synthesis_task = create_synthesis_task(full_question)
synthesis_task.agent = synthesizer
review_task = create_review_task(full_question)
review_task.agent = reviewer
crew = Crew(
agents=[researcher, synthesizer, reviewer],
tasks=[research_task, synthesis_task, review_task],
process=Process.sequential,
verbose=True
)
return crew
def ask_question(question: str) -> str:
"""Ask a question with conversation memory."""
crew = create_kb_crew(question, conversation_history)
result = crew.kickoff()
answer = str(result)
# Store in history
conversation_history.append({
"question": question,
"answer": answer
})
return answer
def clear_history():
"""Clear conversation history."""
conversation_history.clear()
Monitoring and Debugging
CrewAI provides verbose logging that shows each agent's thought process, tool usage, and output. Enable verbose mode by setting verbose=True on both agents and the crew. For deeper observability, you can integrate LangSmith or Phoenix to trace the full execution pipeline.
Here is a simple logging wrapper you can add to track performance:
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def ask_question_with_logging(question: str) -> str:
start_time = time.time()
logger.info(f"Processing question: {question}")
try:
answer = ask_question(question)
elapsed = time.time() - start_time
logger.info(f"Answer generated in {elapsed:.2f}s")
logger.info(f"Answer length: {len(answer)} characters")
return answer
except Exception as e:
logger.error(f"Error processing question: {e}")
raise
Conclusion
Building a knowledge base chatbot with CrewAI gives you a powerful, modular, and extensible system that goes beyond simple retrieval. By leveraging multiple specialized agents, you can ensure that answers are well-researched, clearly synthesized, and quality-checked before reaching the user. The combination of ChromaDB for vector storage, custom CrewAI tools for retrieval, and a sequential agent workflow creates a robust foundation that you can adapt to any domain. Start with the basic setup described in this guide, then iterate on your document collection, chunking strategy, and agent configurations to fine-tune performance for your specific use case. As your needs grow, consider adding hierarchical processes, additional agents for specialized tasks, and integration with external APIs to create an even more capable knowledge base assistant.