07. FAQ Bot
Chapter 7 of 18 · 20 min
An FAQ bot answers common questions by retrieving relevant knowledge and generating natural responses. The architecture is retrieval-augmented generation: find the relevant information, then have the model synthesize an answer.
Start by structuring your knowledge base:
import json
from pathlib import Path
def build_knowledge_base(knowledge_files):
"""Convert documents into a searchable knowledge base."""
documents = []
for file_path in knowledge_files:
path = Path(file_path)
if path.suffix == '.md':
content = path.read_text()
documents.append({
'source': str(path),
'type': 'markdown',
'content': content
})
elif path.suffix == '.txt':
content = path.read_text()
documents.append({
'source': str(path),
'type': 'text',
'content': content
})
elif path.suffix == '.json':
data = json.loads(path.read_text())
for item in data:
documents.append({
'source': str(path),
'type': 'qa_pair',
'question': item.get('question', ''),
'answer': item.get('answer', '')
})
return documents
def chunk_documents(documents, chunk_size=500):
"""Split documents into manageable chunks."""
chunks = []
for doc in documents:
content = doc['content']
if 'question' in doc:
# Q&A pairs stay together
chunks.append({
'content': f"Q: {doc['question']}\nA: {doc['answer']}",
'source': doc['source']
})
else:
# Split longer documents
words = content.split()
for i in range(0, len(words), chunk_size):
chunk_text = ' '.join(words[i:i+chunk_size])
chunks.append({
'content': chunk_text,
'source': doc['source']
})
return chunks
The simple retrieval approach for a FAQ bot uses keyword matching:
import re
def find_relevant_chunks(query, chunks, max_chunks=3):
"""Find chunks relevant to user query."""
query_words = set(re.findall(r'\w+', query.lower()))
scored_chunks = []
for chunk in chunks:
chunk_words = set(re.findall(r'\w+', chunk['content'].lower()))
overlap = len(query_words & chunk_words)
if overlap > 0:
scored_chunks.append((overlap, chunk))
scored_chunks.sort(reverse=True)
return [chunk for _, chunk in scored_chunks[:max_chunks]]
More advanced retrieval uses embeddings, but for FAQ bots with hundreds of documents, keyword matching with TF-IDF scoring often suffices and avoids the complexity of embedding model management.
The answer generation combines the user question with retrieved context:
FAQ_PROMPT = """Answer the user's question based only on the provided context.
If the context does not contain the answer, say "I don't have information about that in my knowledge base."
CONTEXT:
{context}
USER QUESTION: {question}
Provide a helpful, accurate answer:
"""
def answer_faq(question, chunks):
"""Generate FAQ answer using retrieved context."""
relevant = find_relevant_chunks(question, chunks)
if not relevant:
return {
'answer': "I don't have information about that in my knowledge base.",
'sources': []
}
context = "\n---\n".join(chunk['content'] for chunk in relevant)
sources = [chunk['source'] for chunk in relevant]
prompt = FAQ_PROMPT.format(question=question, context=context)
response = chat(model='llama3.2:3b', messages=[
{'role': 'user', 'content': prompt}
])
return {
'answer': response['message']['content'],
'sources': sources
}
EXERCISE
Compile your top twenty FAQ topics into a structured knowledge base file. Deploy the FAQ bot and test with real user questions.