VIOLETS System Architecture β€” Treatment Condition (Chat) Voter Info from Official Local Election Trusted Sources
What this is: VIOLETS is a RAG (retrieval-augmented generation) chatbot that answers Maryland voters' questions using text pulled from official state and county election sources. It's embedded as the chat widget inside a Qualtrics survey. Offline, a Python pipeline crawls and chunks official web pages plus a curated set of Box-hosted documents, embeds every chunk, and stores the vectors in Postgres. Online, a guarded FastAPI service turns each question into a search over those vectors and asks an LLM to answer from what it retrieved, citing sources β€” with guardrails that block PII, reroute sensitive question types to official pages, and re-check every answer for partisan endorsement before it's sent back.
crawl official sites + Box docs→ chunk→ embed→ Postgres + pgvector→ FastAPI + guardrails→ cited answer in chat
β‘  Qualtrics Frontend
VIOLETS β€” Election Info Chat
When is the registration deadline for the 2026 election?
πŸ€–
For the 2026 Maryland general election (Nov 3), the voter registration deadline is Tuesday, Oct 13, 2026.
πŸ“Ž elections.maryland.gov
Ask about voting in Montgomery County…
➀
JS β†’ Backend (HTTPS)
// Send message to FastAPI
fetch("/chat", {
  headers: { "X-API-Key": apiKey },
  body: JSON.stringify({
    user_id, query
  })
})
β‘‘ Python + FastAPI Backend (VIOLETS Engine)
πŸ€–
VIOLETS
Voter Information from Official Local Election Trusted Sources
FastAPI + LangChain + OpenAI
/chat Request Pipeline
1
Auth + Rate Limit
X-API-Key check β†’ 20 req/min per user_id
hmac compare Β· sliding window
2
PII Guard
Presidio scan β†’ hard block on SSN/card/email/phone/IP/ID
Presidio Β· score β‰₯ 0.5
3
Classify Query
8 categories Β· 5 exit early with a hardcoded response
gpt-5-nano Β· structured output
4
Retrieve
Embed query β†’ pgvector cosine search β†’ top-k=5 chunks
text-embedding-3-small Β· pgvector
5
Generate Response
gpt-5-nano Β· prompt varies by category (QA / Concerns / Conversational)
gpt-5-nano Β· cited answer
6
Partisan Check & Respond
Retry up to 2Γ— if flagged (fail-open) β†’ log β†’ return response + sources
gpt-5-nano Β· fail-open
Guardrails & Operating Limits
X-API-Key auth
20 req/min limit
PII detection
8-way classify
partisan retry Γ—2
30min / 20-turn TTL
CORS allowlist
fail-open on guard error
β‘’ Knowledge Base
🐘 Postgres + pgvector Vector DB
chunk_1
chunk_2
chunk_3
chunk_n
1536
dims/vector
k=5
top retrieved
cos
similarity
Offline KB Construction
1
Discovery (Pass 1)
  • Web Crawl β€” allowlisted BFS over SBE + MoCo, exclusion-filtered, rule-classified into a SQLite manifest
  • Box Hub β€” Playwright + Box API discover & download 2026-folder files, filename-filtered into include / exclude / manual-review
Both run in parallel to build the corpus before extraction
2
Extraction & Chunking (Pass 2)
Type-specific splitting (FAQ pairs, ~300w prose, table rows, ~250w paragraphs, single-chunk pages) for both web and Box content, into one unified schema
3
Embedding & Upload (Pass 3)
text-embedding-3-small β†’ unified Postgres + pgvector table, retrieved by cosine similarity across all sources
↻ Incremental & resumable at every stage β€” reruns only touch what actually changed.
Stack FastAPI LangChain OpenAI API gpt-5-nano text-embedding-3-small PostgreSQL + pgvector Presidio + spaCy trafilatura Β· pdfplumber Β· PyMuPDF