Skip to content
This repository was archived by the owner on May 25, 2026. It is now read-only.

Commit 4bcb833

Browse files
Add AGENTS.md for AI agent guidance
Co-Authored-By: AdaL <adal@sylph.ai>
1 parent bbebf9b commit 4bcb833

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to agents (i.e., ADAL) when working with code in this repository.
4+
5+
## Essential Commands
6+
7+
```bash
8+
# Setup (one-time)
9+
python3.12 -m venv venv
10+
source venv/bin/activate
11+
pip install -r requirements.txt
12+
cp .env.example .env # then add GROQ_API_KEY
13+
14+
# Ingest documents into ChromaDB (required before first run or after doc changes)
15+
python -c "from src.ingest import ingest_documents; print(ingest_documents(force=True))"
16+
17+
# Run the app locally
18+
python -m src.app # serves on http://localhost:5000
19+
20+
# Run all tests (30 tests, ~5s, no API key needed)
21+
python -m pytest tests/ -v
22+
23+
# Run specific test files
24+
python -m pytest tests/test_health.py -v
25+
python -m pytest tests/test_guardrails.py -v
26+
python -m pytest tests/test_app.py -v
27+
python -m pytest tests/test_ingest.py -v
28+
29+
# Run evaluation (REQUIRES GROQ_API_KEY in .env, ~3-5 min, hits Groq API)
30+
python evaluation/run_evaluation.py
31+
```
32+
33+
## Critical Gotchas
34+
35+
- **GROQ_API_KEY required for /chat and evaluation** — tests in `test_app.py` and `test_guardrails.py` do NOT hit the LLM; they test guardrails and Flask routing only. But `/chat` with a valid policy question and `run_evaluation.py` require a live key.
36+
- **Ingest before first run** — ChromaDB is gitignored (`chroma_db/`). After cloning, you must run the ingest command before the app can answer questions. On Render, the `buildCommand` in `render.yaml` handles this automatically.
37+
- **ChromaDB corpus hash** — Ingestion is idempotent. It hashes all `docs/*.md` files and skips re-ingestion if unchanged. Use `force=True` to override.
38+
- **ChromaDB telemetry warnings** — You'll see `Failed to send telemetry event` messages from ChromaDB. These are harmless and don't affect functionality.
39+
- **Import paths use `src.` prefix** — The app is run as `python -m src.app`, not `python src/app.py`. All imports use `from src.config import ...` style. The evaluation script manually adds the project root to `sys.path`.
40+
- **Templates/static are outside src/** — Flask is configured with `template_folder="../templates"` and `static_folder="../static"` relative to `src/app.py`. If you move `app.py`, these paths break.
41+
- **Groq rate limits on free tier** — The evaluation script has a 2-second sleep between questions to avoid rate limiting. If you hit 429 errors, increase the delay.
42+
43+
## Architecture & Data Flow
44+
45+
### Request Flow
46+
47+
```
48+
User (browser) → GET / → Flask serves templates/index.html
49+
↓ (user types question)
50+
Browser JS → POST /chat {question: "..."}
51+
52+
Flask app.py → guardrails.validate_input(question)
53+
↓ (if valid)
54+
→ rag_chain.ask(question)
55+
56+
1. _get_embeddings() → HuggingFace MiniLM-L6-v2 (local)
57+
2. embed query → ChromaDB.query(top_k=5)
58+
3. _format_context() → builds numbered source blocks
59+
4. ChatPromptTemplate(system + human) → Groq API
60+
5. Parse response → extract sources
61+
62+
→ guardrails.validate_output(answer)
63+
64+
→ JSON {answer, sources[], latency_ms}
65+
```
66+
67+
### Ingestion Flow
68+
69+
```
70+
docs/*.md → MarkdownHeaderTextSplitter (by #/##/###)
71+
→ RecursiveCharacterTextSplitter (1000 chars, 200 overlap)
72+
→ HuggingFace embed_documents()
73+
→ ChromaDB collection "acme_policies" (persistent in chroma_db/)
74+
```
75+
76+
Each chunk carries metadata: `{source: "pto-and-leave-policy", file: "pto-and-leave-policy.md", heading_1: ..., heading_2: ...}`.
77+
78+
### Guardrails (two layers)
79+
80+
1. **Input** (`guardrails.validate_input`): Rejects empty, too-short (<3 chars), too-long (>500 chars), and off-topic questions (regex patterns for code requests, weather, recipes, etc.). Runs BEFORE any LLM call.
81+
2. **Output** (`guardrails.validate_output`): Truncates >4000 chars, checks for `[doc-name]` citation markers, appends a verification note if citations are missing (unless the response is an out-of-scope message).
82+
83+
### LLM Prompt Strategy
84+
85+
The system prompt in `rag_chain.py` enforces 7 rules: answer only from context, cite with `[doc-name]`, refuse off-topic, never fabricate, etc. The human prompt template injects numbered `[Source N: doc-name]` blocks. This makes citation extraction reliable via regex.
86+
87+
## Key Entry Points
88+
89+
| What | File | Notes |
90+
|------|------|-------|
91+
| Flask app | `src/app.py` | Routes: `/`, `/chat`, `/health` |
92+
| RAG pipeline | `src/rag_chain.py` | `ask(question)` is the main entry |
93+
| Ingestion | `src/ingest.py` | `ingest_documents(force=bool)` |
94+
| Config | `src/config.py` | All env vars with defaults |
95+
| Guardrails | `src/guardrails.py` | `validate_input()`, `validate_output()` |
96+
| Chat UI | `templates/index.html` + `static/chat.js` | Vanilla JS, no build step |
97+
| Evaluation | `evaluation/run_evaluation.py` | Runs 25 questions, writes `eval_results.json` |
98+
| CI/CD | `.github/workflows/ci-cd.yml` | install → import check → pytest → deploy webhook |
99+
| Keep-warm | `.github/workflows/keep-warm.yml` | Cron every 10 min, pings `/health` |
100+
| Render config | `render.yaml` | Build: pip install + ingest; Start: gunicorn |
101+
102+
## Config & Environment
103+
104+
All config lives in `src/config.py`, loaded from `.env` via `python-dotenv`:
105+
106+
| Variable | Default | Required |
107+
|----------|---------|----------|
108+
| `GROQ_API_KEY` | *(none)* | Yes (for /chat and eval) |
109+
| `GROQ_MODEL` | `llama-3.3-70b-versatile` | No |
110+
| `EMBEDDING_MODEL` | `sentence-transformers/all-MiniLM-L6-v2` | No |
111+
| `CHROMA_PERSIST_DIR` | `chroma_db` | No |
112+
| `CHUNK_SIZE` | `1000` | No |
113+
| `CHUNK_OVERLAP` | `200` | No |
114+
| `RETRIEVAL_K` | `5` | No |
115+
116+
## Corpus Details
117+
118+
10 markdown files in `docs/`, all for fictional "Acme Corp". Each has a `Policy ID:` header (e.g., `ACME-HR-001`). Documents cross-reference each other by policy ID. The evaluation questions map to specific documents via `expected_source` in `eval_questions.json`.
119+
120+
Key cross-references:
121+
- PTO policy → Holiday Schedule (`ACME-HR-006`)
122+
- Remote Work → Expense Policy (`ACME-FIN-001`) for travel reimbursement
123+
- Remote Work → Info Security (`ACME-IT-001`) for device/VPN requirements
124+
- Acceptable Use → Info Security (`ACME-IT-001`) and Code of Conduct (`ACME-HR-005`)
125+
- Onboarding → references nearly all other policies
126+
127+
## Deployment (Render)
128+
129+
Follows the same pattern as `malware-detection-ml`:
130+
- `render.yaml`: buildCommand runs `pip install` then ingestion; startCommand runs gunicorn
131+
- GitHub secrets needed: `RENDER_DEPLOY_HOOK_URL`, `APP_HEALTH_URL`, `GROQ_API_KEY`
132+
- Keep-warm pings `/health` every 10 min via GitHub Actions cron
133+
- Single worker + 2 threads (free tier memory constraint)
134+
- Embedding model downloads on first build (~80MB), cached in Render's build layer
135+
136+
## Testing Notes
137+
138+
- All 30 tests run without `GROQ_API_KEY` — they test guardrails, Flask routing, and ingestion only
139+
- `test_ingest.py::test_load_and_chunk` downloads the embedding model on first run (~80MB), takes ~20s; subsequent runs ~5s
140+
- No mocking of the LLM — integration tests for RAG quality are in `evaluation/run_evaluation.py`, not pytest
141+
- CI runs `python -c "from src.app import app"` as an import smoke test before pytest

0 commit comments

Comments
 (0)