--- layout: "raw" --- VIOLETS Architecture
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