BroncoBot

Technical Architecture

How BroncoBot retrieves and grounds answers from 72,499 chunks of official CPP content.

Architecture Overview

1

User Question

Natural language query

2

Claude Sonnet

Tool-calling LLM

3

search_corpus

Single retrieval tool

4

BM25 Keyword

In-memory, 72K chunks

5

Gemini Semantic

pgvector, 768d vectors

6

Hybrid Merge

70% semantic + 30% BM25

7

Top 8 Results

With source URLs

8

Grounded Answer

Cited response

Key Technical Decisions

DecisionWhat we choseWhy
Search methodHybrid BM25 + semanticBM25 catches exact matches (course codes, names); semantic catches paraphrased intent
Embedding modelGemini text-embedding-004 (768d)Free tier, RETRIEVAL_QUERY task type optimizes for search
Vector storageSupabase pgvector (HNSW index)72K vectors exceed Vercel's 250MB bundle limit; HNSW gives ~10-50ms query time
BM25 locationIn-memory (serverless)70MB of chunk data fits in Vercel's limit; no DB round-trip for keyword search
Hybrid weights70% semantic / 30% BM25Validated via blind evaluation (0.95 MRR)
Result count8 per queryA/B tested against 15 — higher MRR at 8 due to candidate pool distortion
Tool architectureSingle tool (search_corpus)Tested 5-tool approach; it routed queries to sparse structured data and degraded answers
LLM integrationTool-calling, not RAG injectionLLM decides when to search; avoids context bloat and hallucination from injected chunks

Corpus Statistics

8,042

Total pages indexed

72,499

Total chunks

72,499 (100%)

Chunks embedded

768

Embedding dimensions

~110K

Unique BM25 terms

~600 chars

Avg chunk length

Retrieval Evaluation — Blind A/B Test (10 queries)

We validated our search pipeline with objective metrics, not subjective judgment.

Metriclimit=8limit=15
Mean Top-1 Score0.73880.7472
Mean Reciprocal Rank (MRR)0.95000.8833
Queries with correct answer in top-39/108/10

Key finding: Expanding the semantic candidate pool from 24 to 45 (via match_count: limit * 3) pulled in tangentially related chunks that distorted hybrid score normalization. 6/10 queries returned different top-3 rankings. We chose limit=8 for higher MRR despite marginally lower top-1 scores — correct ranking matters more than marginal relevance gains.