Zero-infrastructure KB Agent for Banking Voice Agents
Pickle everything at build time. Load into memory at startup. Stateless workers for K8s/Docker scale-out. No Redis, no MongoDB, no vector DB.
# Like arxiv-sanity pipeline: # fetch_papers.py → parse_kb.py # analyze.py → build_bm25_index() # make_cache.py → build_all() python build_kb.py --kb-dir ./kb_files --output ./data # Output: # data/kb.p ← Structured KB (product:field → value) # data/chunks.p ← KB chunks for RAG # data/embeddings.npy ← Precomputed vectors # data/bm25.p ← BM25 inverted index # data/sim.p ← Precomputed similarities # data/meta.p ← Vocab, IDF, config
┌─────────────────────────────────────────────────────────────────┐ │ Python Process Memory │ ├─────────────────────────────────────────────────────────────────┤ │ GLOBALS (loaded once at startup) │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ KB: dict[str, KBEntry] ~1 KB │ │ │ │ "be_cake:interest_rate" → KBEntry(display="44%/năm")│ │ │ └──────────────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ CHUNKS: list[Chunk] ~10-100 KB │ │ │ └──────────────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ EMBEDDINGS: np.ndarray ~10-50 MB │ │ │ │ shape: (num_chunks, 384 or 1536) │ │ │ └──────────────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ BM25_INDEX: dict[str, list] ~1-5 MB │ │ │ │ "lãi" → [(idx=0, score=2.3), ...] │ │ │ │ (inverted index, like SEARCH_DICT but inverted) │ │ │ └──────────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────────┤ │ Total: ~50-100 MB per worker │ │ ✓ Fits in memory ✓ No DB calls ✓ Workers independent │ └─────────────────────────────────────────────────────────────────┘
def papers_search(qraw):
for pid,p in db.items():
score = sum(
SEARCH_DICT[pid].get(q,0)
for q in qparts
)
# O(num_docs) per query
def search_bm25(query):
for token in tokens:
for idx, score in BM25_INDEX[token]:
scores[idx] += score
# O(terms × posting_len)
# Build & Deploy docker build -t kb-agent . kubectl apply -f deployment.yaml kubectl scale deployment kb-agent --replicas=10 # Each pod independent: # - Same pickles from image # - No shared state # - SQLite ephemeral # - Scale by replicas
Karpathy's arxiv-sanity works because he accepted constraints: single server, papers fit in memory, batch updates daily. Same philosophy adapted for stateless scale-out.