|
| 1 | +# Design and Evaluation — Acme Corp Policy RAG Application |
| 2 | + |
| 3 | +## 1. Design and Architecture Decisions |
| 4 | + |
| 5 | +### 1.1 Corpus Design |
| 6 | + |
| 7 | +**Decision**: Generate 10 synthetic policy documents for a fictional company ("Acme Corp") rather than using real company policies. |
| 8 | + |
| 9 | +**Why**: The rubric explicitly allows AI-authored synthetic policies. This approach gives us full control over document quality, topic coverage, and cross-referencing consistency — essential for achieving high evaluation scores. Each document has a unique Policy ID, cross-references related policies, and covers specific evaluation domains. |
| 10 | + |
| 11 | +**Corpus coverage**: PTO & Leave, Remote Work, Expenses, Information Security, Code of Conduct, Holidays, Benefits, Onboarding, Performance Reviews, Acceptable Use — totaling ~2,500 lines across 10 markdown files. |
| 12 | + |
| 13 | +### 1.2 Embedding Model |
| 14 | + |
| 15 | +**Decision**: `sentence-transformers/all-MiniLM-L6-v2` (local, free) |
| 16 | + |
| 17 | +**Why**: This model is lightweight (80MB), runs locally without API keys, produces 384-dimensional embeddings, and has strong performance on semantic similarity benchmarks. It loads fast on Render's free tier and avoids external API dependency for embeddings. |
| 18 | + |
| 19 | +**Alternatives considered**: |
| 20 | +- Cohere Embed v3 (free tier): Better quality but adds API dependency and rate limits |
| 21 | +- all-mpnet-base-v2: Higher quality but 2x model size, slower on free tier |
| 22 | + |
| 23 | +### 1.3 Chunking Strategy |
| 24 | + |
| 25 | +**Decision**: Two-pass chunking — Markdown header splitting first, then recursive character splitting (1000 chars, 200 overlap). |
| 26 | + |
| 27 | +**Why**: Policy documents have natural heading structure. Splitting by headers preserves semantic coherence of sections (e.g., "Section 4.1 Air Travel" stays together). The second pass handles oversized sections while the 200-char overlap ensures context continuity at chunk boundaries. This produced 267 chunks from 10 documents. |
| 28 | + |
| 29 | +**Alternatives considered**: |
| 30 | +- Token-based splitting only: Loses heading context and may split mid-section |
| 31 | +- Smaller chunks (500 chars): More chunks = noisier retrieval results |
| 32 | +- Larger chunks (2000 chars): Risk exceeding LLM context with 5 retrieved chunks |
| 33 | + |
| 34 | +### 1.4 Vector Store |
| 35 | + |
| 36 | +**Decision**: ChromaDB (persistent, local) |
| 37 | + |
| 38 | +**Why**: Zero cost, no API key, native LangChain support, persistent storage (survives restarts on Render), and the corpus is small enough (~267 chunks) that local vector search is instant. The corpus hash mechanism avoids re-ingestion on every deployment. |
| 39 | + |
| 40 | +**Alternatives considered**: |
| 41 | +- Pinecone: Better for large-scale, but adds API dependency and free tier limits |
| 42 | +- FAISS: Fast but no built-in persistence; requires manual serialization |
| 43 | + |
| 44 | +### 1.5 LLM |
| 45 | + |
| 46 | +**Decision**: Groq `llama-3.3-70b-versatile` (free tier) |
| 47 | + |
| 48 | +**Why**: Groq provides extremely fast inference (~150 tokens/sec) on the free tier. The 70B parameter Llama 3.3 model has strong instruction following, handles structured citation prompts well, and supports the ~8K context window needed for our prompt + 5 retrieved chunks. |
| 49 | + |
| 50 | +**Alternatives considered**: |
| 51 | +- OpenRouter free models: Unreliable availability, rate limit changes |
| 52 | +- Local Ollama: Not feasible on Render free tier (insufficient RAM) |
| 53 | +- Groq Llama 4 Scout: Newer but less tested for instruction-following RAG tasks |
| 54 | + |
| 55 | +### 1.6 Retrieval Strategy |
| 56 | + |
| 57 | +**Decision**: Top-k=5 with embedding similarity search. |
| 58 | + |
| 59 | +**Why**: k=5 provides good coverage without overwhelming the LLM context. Our testing shows that relevant answers typically appear in the top 3 results, with positions 4-5 providing supporting context from related policies. The 384-dim MiniLM embeddings provide effective semantic matching for policy questions. |
| 60 | + |
| 61 | +### 1.7 Prompt Engineering |
| 62 | + |
| 63 | +**Decision**: System prompt with explicit rules for citation format, out-of-scope handling, and factual grounding. |
| 64 | + |
| 65 | +**Why**: The system prompt enforces 7 rules that directly map to evaluation criteria: |
| 66 | +1. Answer only from context (→ groundedness) |
| 67 | +2. Explicit fallback message for missing info (→ graceful degradation) |
| 68 | +3. Always cite sources in `[doc-name]` format (→ citation accuracy) |
| 69 | +4. Concise, structured answers (→ readability) |
| 70 | +5. Refuse off-topic questions (→ guardrails) |
| 71 | +6. Never fabricate (→ groundedness) |
| 72 | +7. Cite numbers explicitly (→ citation accuracy) |
| 73 | + |
| 74 | +### 1.8 Guardrails |
| 75 | + |
| 76 | +**Decision**: Two-layer guardrails — input validation (regex patterns for off-topic detection, length limits) and output validation (citation checking, length truncation). |
| 77 | + |
| 78 | +**Why**: Input guardrails catch obvious off-topic questions before they hit the LLM (saving API calls). Output guardrails ensure every response either contains citations or is a recognized out-of-scope message. This prevents the LLM from occasionally "forgetting" to cite sources. |
| 79 | + |
| 80 | +### 1.9 Web Application |
| 81 | + |
| 82 | +**Decision**: Flask with a clean chat UI (vanilla HTML/CSS/JS). |
| 83 | + |
| 84 | +**Why**: Flask is lightweight and meets the endpoint requirements (/, /chat, /health). The chat UI uses vanilla JavaScript without React/Vue to minimize bundle size and deployment complexity on the free tier. The interface shows source documents and response latency for transparency. |
| 85 | + |
| 86 | +### 1.10 Deployment |
| 87 | + |
| 88 | +**Decision**: Render free tier with gunicorn, following the same pattern as the malware-detection-ml project. |
| 89 | + |
| 90 | +**Why**: Proven deployment pattern, zero cost, easy CI/CD integration. The keep-warm cron job prevents the Render free tier from spinning down. |
| 91 | + |
| 92 | +--- |
| 93 | + |
| 94 | +## 2. Evaluation Approach |
| 95 | + |
| 96 | +### 2.1 Evaluation Set |
| 97 | + |
| 98 | +25 questions across 6 domains: |
| 99 | +- **PTO & Leave** (5 questions): Accrual rates, carryover, parental leave, sick days, advance notice |
| 100 | +- **Security** (4 questions): Password requirements, MFA, device reporting, encryption standards |
| 101 | +- **Expenses** (4 questions): Meal limits, hotel rates, submission deadlines, professional development |
| 102 | +- **Remote Work** (4 questions): Core hours, equipment, internet stipend, co-working space policy |
| 103 | +- **Holidays** (4 questions): Juneteenth, floating holidays, year-end closure, holiday pay |
| 104 | +- **Cross-domain** (4 questions): 401(k) match, onboarding-to-remote timeline, gift limits, approved AI tools |
| 105 | + |
| 106 | +Each question has an expected answer and expected source document for automated evaluation. |
| 107 | + |
| 108 | +### 2.2 Metrics |
| 109 | + |
| 110 | +| Metric | Definition | Method | |
| 111 | +|--------|-----------|--------| |
| 112 | +| **Groundedness** | % of answers whose content is factually supported by retrieved context | LLM-as-judge (Groq llama-3.3-70b) evaluates each answer against its retrieved context chunks | |
| 113 | +| **Citation Accuracy** | % of answers that correctly cite the expected source document | Automated regex check for `[expected-source]` in the answer | |
| 114 | +| **Latency p50** | Median end-to-end response time | Timed from question submission to answer return | |
| 115 | +| **Latency p95** | 95th percentile response time | Sorted latency array, index at 95% | |
| 116 | + |
| 117 | +### 2.3 Evaluation Results |
| 118 | + |
| 119 | +*(To be populated after running `python evaluation/run_evaluation.py`)* |
| 120 | + |
| 121 | +| Metric | Target | Actual | |
| 122 | +|--------|--------|--------| |
| 123 | +| Groundedness | ≥ 90% | TBD | |
| 124 | +| Citation Accuracy | ≥ 85% | TBD | |
| 125 | +| Latency p50 | < 3s | TBD | |
| 126 | +| Latency p95 | < 8s | TBD | |
| 127 | + |
| 128 | +### Per-Domain Breakdown |
| 129 | + |
| 130 | +| Domain | Questions | Groundedness | Citation Accuracy | |
| 131 | +|--------|-----------|-------------|------------------| |
| 132 | +| PTO & Leave | 5 | TBD | TBD | |
| 133 | +| Security | 4 | TBD | TBD | |
| 134 | +| Expenses | 4 | TBD | TBD | |
| 135 | +| Remote Work | 4 | TBD | TBD | |
| 136 | +| Holidays | 4 | TBD | TBD | |
| 137 | +| Cross-domain | 4 | TBD | TBD | |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## 3. Technology Stack Summary |
| 142 | + |
| 143 | +| Component | Choice | Rationale | |
| 144 | +|-----------|--------|-----------| |
| 145 | +| LLM | Groq llama-3.3-70b-versatile | Free, fast, strong instruction following | |
| 146 | +| Embeddings | all-MiniLM-L6-v2 | Free, local, lightweight, good quality | |
| 147 | +| Vector Store | ChromaDB | Free, persistent, LangChain native | |
| 148 | +| Orchestration | LangChain | Required by spec; handles retrieval chain | |
| 149 | +| Web Framework | Flask | Lightweight, meets endpoint requirements | |
| 150 | +| Chunking | Markdown headers + recursive (1000/200) | Preserves document structure | |
| 151 | +| Retrieval | Top-k=5 similarity | Good coverage-to-noise balance | |
| 152 | +| Deployment | Render free tier + gunicorn | Proven pattern, zero cost | |
| 153 | +| CI/CD | GitHub Actions | Build + test + deploy on push | |
0 commit comments