Knowledge Base¶
LocalKB is a production-ready, built-in knowledge base with FAISS vector search, BM25 keyword search, hybrid search, SQLite persistence, and full CRUD operations. No external infrastructure required.
New in 0.3.0 — Pluggable backends. The vector, keyword, and metadata stores behind
LocalKBare now swappable. Default behavior (FAISS + BM25 + SQLite) is unchanged. Point at a remote Qdrant or Chroma instance with a single kwarg. See Backends.Hosted KBs. For KBs uploaded and managed on the FastAIAgent platform, use
PlatformKB— samesearch()surface, retrieval runs on the platform.
Quick Start¶
from fastaiagent.kb import LocalKB
kb = LocalKB(name="product-docs")
# Add content (text, files, or directories)
kb.add("Refund policy: Returns accepted within 30 days of purchase.")
kb.add("/path/to/faq.md")
kb.add("docs/") # recursively ingests .txt, .md, .pdf
# Search (hybrid by default — combines FAISS + BM25)
results = kb.search("How do I return an item?", top_k=3)
for r in results:
print(f"[{r.score:.3f}] {r.chunk.content[:80]}...")
Restart the process — your data is still there. No re-embedding.
Adding Content¶
Raw Text¶
Files¶
Supported formats: .txt, .md, .pdf (requires pip install fastaiagent[kb])
Directories¶
Recursively ingests all supported files:
Multiple Documents¶
from fastaiagent.kb.document import Document
docs = [
Document(content="First document content", source="doc1.md", metadata={"type": "faq"}),
Document(content="Second document content", source="doc2.md", metadata={"type": "guide"}),
]
count = kb.add_documents(docs)
print(f"Added {count} chunks")
Each file is:
1. Read and extracted to text (PDF pages extracted individually)
2. Split into chunks (default 512 characters with 50-character overlap)
3. Embedded into vectors (skipped for search_type="keyword")
4. Stored in SQLite (if persist=True) and indexed for search
Searching¶
Search Types¶
LocalKB supports three search modes. Choose based on your query patterns:
| Search Type | How It Works | Best For |
|---|---|---|
"vector" |
FAISS semantic similarity | Natural language queries ("how do I get a refund") |
"keyword" |
BM25 term matching | Exact terms, codes, IDs ("ERR-4012", "TXN-88421") |
"hybrid" (default) |
Vector + BM25 combined | Real-world queries mixing both ("ERR-4012 payment not working") |
# Hybrid (default) — best of both worlds
kb = LocalKB(name="support-docs")
# Vector only — semantic search
kb = LocalKB(name="docs", search_type="vector")
# Keyword only — no embedder needed, zero embedding cost
kb = LocalKB(name="logs", search_type="keyword")
Keyword mode is especially useful when you don't need semantic search — it skips embedding entirely, meaning no embedder is initialized, no API calls, and instant ingestion.
Hybrid Search and Alpha Tuning¶
In hybrid mode, results from FAISS and BM25 are normalized and combined:
# Semantic-heavy (default) — good for most cases
kb = LocalKB(name="docs", alpha=0.7)
# Equal weight — queries mix codes + natural language
kb = LocalKB(name="docs", alpha=0.5)
# Keyword-heavy — technical docs with lots of IDs/codes
kb = LocalKB(name="docs", alpha=0.3)
Searching¶
results = kb.search("refund policy", top_k=5)
for r in results:
print(f"Score: {r.score:.3f}")
print(f"Content: {r.chunk.content}")
print(f"Source: {r.chunk.metadata.get('source', 'unknown')}")
print()
Empty KB¶
Searching an empty KB returns an empty list — no error:
FAISS Index Types¶
LocalKB uses FAISS for vector search. Three index types are available:
| Index Type | Algorithm | Accuracy | Speed | When to Use |
|---|---|---|---|---|
"flat" (default) |
Brute-force inner product | Exact (100%) | O(N) per query | Up to ~100K chunks. No tuning needed. Start here. |
"ivf" |
Inverted file index | ~95-99% (approximate) | Sublinear | 100K-1M chunks. Trades small accuracy for faster search. |
"hnsw" |
Hierarchical Navigable Small World graph | ~99% (approximate) | Very fast | Large KBs needing both speed and high recall. Uses more memory. |
# Default — exact search, no config needed
kb = LocalKB(name="docs")
# Large KB — use IVF for faster approximate search
kb = LocalKB(name="big-docs", index_type="ivf")
# Speed-critical — HNSW for fastest recall
kb = LocalKB(name="realtime", index_type="hnsw")
Start with "flat" (the default). You only need "ivf" or "hnsw" if search latency becomes noticeable — typically above 100K chunks.
Persistence¶
By default, LocalKB persists all data to SQLite. Chunks and embeddings survive process restarts — no re-embedding needed.
# Persistent (default)
kb = LocalKB(name="docs")
kb.add("important content")
# Data saved to .fastaiagent/kb/docs/kb.sqlite
# After restart:
kb = LocalKB(name="docs")
print(kb.status()["chunk_count"]) # Still there!
Temporary KB¶
For throwaway use cases (single agent run, dynamic API content, testing):
kb = LocalKB(name="scratch", persist=False)
kb.add("temporary content from an API call")
results = kb.search("keyword")
# No files created, data gone when process ends
Context Manager¶
with LocalKB(name="docs") as kb:
kb.add("content")
results = kb.search("query")
# Database connection closed automatically
Update and Delete¶
All CRUD operations persist to SQLite and update search indexes.
Delete by Chunk ID¶
Delete by Source File¶
kb.add("docs/faq.md")
deleted = kb.delete_by_source("docs/faq.md")
print(f"Removed {deleted} chunks")
Update a Chunk¶
kb.add("Original content")
chunk_id = kb._chunks[0].id
kb.update(chunk_id, "Updated content") # Re-embeds automatically
Clear Entire KB¶
Using KB as an Agent Tool¶
The most common pattern — give your agent access to a knowledge base:
from fastaiagent import Agent, LLMClient
from fastaiagent.kb import LocalKB
# Build the KB (persisted — only need to add once)
kb = LocalKB(name="product-docs")
kb.add("Refund policy: Returns within 30 days. Items must be in original condition.")
kb.add("Shipping: 3-5 business days domestic. Express shipping available.")
kb.add("Support hours: Monday-Friday 9am-5pm EST.")
# Create a search tool from the KB
search_tool = kb.as_tool()
# Creates a FunctionTool named "search_product-docs" that wraps kb.search()
# Give it to an agent
agent = Agent(
name="support-bot",
system_prompt="Use the search tool to find information before answering.",
llm=LLMClient(provider="openai", model="gpt-4.1"),
tools=[search_tool],
)
result = agent.run("What is the refund policy?")
# Agent calls search_product-docs → gets relevant chunks → answers from them
The tool returns formatted search results:
[Score: 0.825] Refund policy: Returns within 30 days. Items must be in original condition.
[Score: 0.412] Support hours: Monday-Friday 9am-5pm EST.
Multi-KB Agent Pattern¶
Use domain-sharded KBs with an agent that routes queries to the right KB:
kb_billing = LocalKB(name="billing")
kb_billing.add("billing-docs/")
kb_shipping = LocalKB(name="shipping")
kb_shipping.add("shipping-docs/")
kb_returns = LocalKB(name="returns")
kb_returns.add("return-policy-docs/")
agent = Agent(
name="support-agent",
system_prompt="Search the relevant KB based on the customer's question.",
llm=LLMClient(provider="openai", model="gpt-4.1"),
tools=[kb_billing.as_tool(), kb_shipping.as_tool(), kb_returns.as_tool()],
)
Each KB is searched independently — search stays fast even with thousands of documents across all domains.
Embedding Providers¶
Three embedders are available, selected automatically based on what's installed:
OpenAI Embeddings (recommended for production)¶
from fastaiagent.kb.embedding import OpenAIEmbedder
kb = LocalKB(name="docs", embedder=OpenAIEmbedder(model="text-embedding-3-small"))
Requires: pip install fastaiagent[openai] and OPENAI_API_KEY env var.
Produces 1536-dimensional vectors with strong semantic understanding — "return an item" matches "refund policy" even without shared keywords.
FastEmbed (local, no API calls)¶
from fastaiagent.kb.embedding import FastEmbedEmbedder
kb = LocalKB(name="docs", embedder=FastEmbedEmbedder(model_name="BAAI/bge-small-en-v1.5"))
Requires: pip install fastaiagent[kb]
Runs entirely on your machine. Good balance of quality and speed for development and privacy-sensitive use cases.
SimpleEmbedder (fallback, no dependencies)¶
from fastaiagent.kb.embedding import SimpleEmbedder
kb = LocalKB(name="docs", embedder=SimpleEmbedder(dimensions=128))
Character-frequency based — no external dependencies, no API calls. Works for testing but not suitable for production (no semantic understanding).
Auto-Selection¶
If you don't specify an embedder, the best available one is chosen:
| Priority | Embedder | Requires | Quality |
|---|---|---|---|
| 1st | FastEmbedEmbedder | pip install fastaiagent[kb] |
Good |
| 2nd | OpenAIEmbedder | pip install fastaiagent[openai] + API key |
Best |
| 3rd | SimpleEmbedder | Nothing | Testing only |
Chunking¶
Documents are split into chunks before embedding. The recursive chunker tries progressively finer separators:
\n\n(paragraphs)\n(lines).(sentences)(words)- Hard character split (last resort)
Configuration¶
kb = LocalKB(
name="docs",
chunk_size=512, # Max characters per chunk (default: 512)
chunk_overlap=50, # Character overlap between chunks (default: 50)
)
Chunk size guidelines:
| Chunk Size | Best For |
|---|---|
| 256 | Short, precise answers (FAQ) |
| 512 | General purpose (default) |
| 1024 | Longer context, fewer chunks |
Direct Chunking API¶
from fastaiagent.kb.chunking import chunk_text
chunks = chunk_text(
text="Your long document text...",
chunk_size=512,
overlap=50,
metadata={"source": "readme.md"},
)
for c in chunks:
print(f"[{c.id}] {c.start_char}-{c.end_char}: {c.content[:40]}...")
Search Result¶
| Field | Type | Description |
|---|---|---|
chunk |
Chunk |
The matched chunk |
score |
float |
Similarity score (higher is better) |
Chunk¶
| Field | Type | Description |
|---|---|---|
id |
str |
Unique UUID for the chunk |
content |
str |
Chunk text |
metadata |
dict |
Source info, custom metadata |
index |
int |
Position in the original document |
start_char |
int |
Start character offset |
end_char |
int |
End character offset |
KB Status¶
status = kb.status()
print(status)
# {
# "name": "product-docs",
# "chunk_count": 15,
# "path": ".fastaiagent/kb/product-docs",
# "persist": True,
# "search_type": "hybrid",
# "index_type": "flat"
# }
Full Configuration Reference¶
kb = LocalKB(
name="docs", # KB name (used in path and tool name)
path=".fastaiagent/kb/", # Base storage directory
embedder=FastEmbedEmbedder(), # Embedding provider (auto-selected if omitted)
chunk_size=512, # Max characters per chunk
chunk_overlap=50, # Character overlap between chunks
persist=True, # Save to SQLite (False for in-memory only)
search_type="hybrid", # "vector" | "keyword" | "hybrid"
index_type="flat", # "flat" | "ivf" | "hnsw"
alpha=0.7, # Vector vs BM25 weight in hybrid mode
)
CLI Commands¶
# Check KB status
fastaiagent kb status --name product-docs
# Add a file or directory
fastaiagent kb add ./docs/readme.md --name product-docs
fastaiagent kb add ./docs/ --name product-docs
# Delete chunks from a source file
fastaiagent kb delete ./docs/old-faq.md --name product-docs
# Clear all data
fastaiagent kb clear --name product-docs
Error Handling¶
# Nonexistent path — treated as raw text, NOT an error
kb.add("/nonexistent/file.txt")
# Adds the string "/nonexistent/file.txt" as a text chunk
# To ingest a file, ensure the path exists first
# Explicit file ingestion (raises FileNotFoundError)
from fastaiagent.kb.document import ingest_file
try:
ingest_file("/nonexistent/file.txt")
except FileNotFoundError:
print("File not found")
# PDF without pymupdf
try:
kb.add("document.pdf") # Only works if document.pdf exists
except ImportError:
print("Install pymupdf: pip install fastaiagent[kb]")
# Embedding dimension mismatch on reload
try:
kb = LocalKB(name="docs", embedder=SimpleEmbedder(dimensions=64))
# Fails if KB was created with a different dimension embedder
except ValueError as e:
print(f"Dimension mismatch: {e}")
Complete Example¶
from fastaiagent import Agent, LLMClient
from fastaiagent.kb import LocalKB
from fastaiagent.kb.embedding import OpenAIEmbedder
# 1. Build a KB with OpenAI embeddings (persisted — run once)
kb = LocalKB(
name="company-docs",
embedder=OpenAIEmbedder(),
chunk_size=512,
)
# 2. Ingest files and directories
kb.add("docs/refund-policy.md")
kb.add("docs/shipping-guide.md")
kb.add("docs/faq/") # Recursive directory ingestion
print(f"KB ready: {kb.status()['chunk_count']} chunks")
# 3. Create agent with KB tool
agent = Agent(
name="support-bot",
system_prompt=(
"You are a customer support agent. Always search the knowledge base "
"before answering. Cite the relevant information in your response."
),
llm=LLMClient(provider="openai", model="gpt-4.1"),
tools=[kb.as_tool()],
)
# 4. Agent uses KB to answer
result = agent.run("Can I return a digital product?")
print(result.output)
Next Steps¶
- Backends — Pluggable vector, keyword, and metadata storage (Qdrant, Chroma, custom)
- Agents — Build agents that use knowledge bases
- Tools — Learn about tool types
- Evaluation — Test knowledge base accuracy