why-agent / README.md
MapoTofu9's picture
deploy: HF Spaces
5d30bdc
|
Raw
History Blame Contribute Delete
29.8 kB
---
title: why-agent
emoji: πŸ”
colorFrom: indigo
colorTo: purple
sdk: docker
app_port: 7860
pinned: false
license: mit
---
# why-agent β€” internal working doc
Owners: Mapo, Isa
This is **our** working doc. It's where we record what we agreed on,
why we agreed on it, and who owns what. Update it when decisions change.
---
## 1. What we're building
An autonomous root-cause agent for data. User asks **"why did metric X
move?"** β€” agent investigates and returns a structured report with an
evidence chain. Works against any user-provided DuckDB/Parquet dataset.
Built on AMD MI300X, deployed to Streamlit Community Cloud.
Working name: **Why Agent**. Repo name: `why-agent`.
---
## 2. Why this, not something else
We considered three other directions and rejected them. The reasoning
matters β€” it's our defense if we're tempted to scope-creep later.
| Considered | Why we rejected it |
|---|---|
| Multi-agent research over arXiv | Crowded space (Undermind, Elicit, Consensus). No clear users beyond PhD niche. |
| Conflict-aware research agent | Intellectually interesting, no real buyer. "Neat" β‰  "needed." |
| Generic NL-to-SQL agent | Saturated. Every cloud and BI vendor ships one. We can't out-product Snowflake. |
| Multi-agent diagnostic system | One capable agent teaches us more about agent fundamentals than orchestrating several shallow ones. |
What makes *why-agent* the right pick:
- **Real users**: any data team at any company > a few hundred people.
- **Real gap**: only Tellius does autonomous RCA, and it's closed enterprise. No OSS equivalent.
- **Plays to us**: Isa lives this problem at PayPal; Mapo has shipped RAG + agentic systems.
- **Beyond simple RAG**: required by the hackathon brief, naturally true here.
---
## 3. Business insight (the thing this fixes)
Every company runs on metrics. When a metric moves unexpectedly, an
analyst spends 30–90 minutes doing *mechanical* work:
Confirm the drop is real
Decompose by every dimension they can think of
Find the anomalous slice
Drill in further
Cross-check related signals
Write up the conclusion
This is expert-level but repetitive. It's exactly the shape of work
agents do well. But today's tools answer **"what is X?"**, not
**"why did X change?"**.
The market split:
- **Descriptive layer** (NL-to-SQL): saturated. Snowflake Cortex,
Databricks Genie, Hex, Julius, Looker+Gemini, etc.
- **Diagnostic layer** (autonomous RCA): one closed-source vendor
(Tellius). Nothing in OSS. **This is where we play.**
Our wedge in one sentence:
> *What Looker shows you, why-agent investigates for you.*
---
## 4. Architecture overview
```
Judge / user Cost
β”‚ ────
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” $0
β”‚ Streamlit Cloud β”‚
β”‚ https://why-agent... β”‚
β”‚ β”‚
β”‚ UI + agent + tools + data β”‚
β”‚ all in one Python process β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ HTTPS, OpenAI-compat
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” $1.99/hr
β”‚ AMD MI300X droplet β”‚ (when ON)
β”‚ vLLM + Llama-3.3-70B BF16 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
![Architecture diagram](docs/why-agent-architecture.png)
**Three logical pieces:**
1. **The model** β€” vLLM serving Llama 70B on MI300X. Heavy, expensive.
2. **The agent** β€” Python code (LangGraph). Light, runs anywhere.
3. **The data** β€” DuckDB on Parquet + a YAML semantic layer. Tiny.
Everything except the model lives inside one Streamlit app on
Streamlit Cloud. The model is reached via HTTPS to the AMD droplet.
### Why this split
- The model needs a GPU. Nothing else does.
- Streamlit Cloud is free; GPU droplets are $1.99/hr.
- The agent can use Anthropic API as a fallback when the GPU is off.
- Iteration speed: code changes don't redeploy the GPU.
### Three model backends, env-switchable
MODEL_BACKEND=minimax β†’ MiniMax API (MiniMax-M2.7). Use Days 1-2 (no GPU).
MODEL_BACKEND=vllm β†’ MI300X. Use Day 3+ for integration & demo.
MODEL_BACKEND=replay β†’ Pre-recorded traces. For demo when GPU off.
This is critical infrastructure β€” implement on Day 1.
---
## 5. The agent
### Loop
```
plan β†’ decompose β†’ drill β†’ cross-check β†’ critique β†’ report
↑ β”‚
└────── if evidence weak, loop back β”€β”€β”€β”€β”€β”€β”€β”˜
```
A LangGraph state machine. Each step is an explicit node. State persists
across the whole investigation.
When critique returns **VERDICT: weak**, its justification text is stored
in `state.critique_feedback` and injected into the next phase's system
prompt as a targeted directive β€” so the agent retries the *specific* gap
identified, rather than re-exploring from scratch and burning retry budget.
### The four tools
We deliberately have only four. Fewer integrations = fewer demo
failure modes. Hypothesis tracking lives in agent prompt + memory,
not in a tool.
| Tool | Purpose |
|---|---|
| `inspect_schema(table)` | Returns columns, types, sample values, business descriptions from semantic layer |
| `run_sql(query)` | Executes a read-only DuckDB query, returns rows |
| `decompose_metric(metric, dims)` | Slices metric by each dim, ranks slices by anomaly magnitude |
| `compare_periods(metric, before, after, segment)` | Quantifies how a slice changed between two windows |
Tools were cut from six. **Resist re-adding them.** The agent gets
smarter by reasoning better, not by having more tools.
### What "beyond simple RAG" means here
| Simple RAG | why-agent |
|---|---|
| 1 retrieval | Many SQL queries, planned |
| 1 generation | Multi-step loop |
| Static | Hypothesis-driven branching |
| No self-eval | Self-critique before reporting |
| Document-bound | Operates on live structured data |
---
## 6. Data + semantic layer
### Dataset
why-agent works against **any user-provided data** β€” drop Parquet files
into `data/parquet/` and point the semantic layer at them. There is no
fixed dataset baked into the project.
The current demo dataset is a marketing/CRM extract:
| File | Contents |
|---|---|
| `campaigns.parquet` | Campaign metadata and performance metrics |
| `client_first_purchase_date.parquet` | Customer acquisition timeline |
| `holidays.parquet` | Holiday calendar for seasonality context |
| `messages.parquet` | Message-level send/open/click events |
**DuckDB view registration:** All `*.parquet` files in `PARQUET_DIR` are registered as independent DuckDB views named after their file stem. There are no implicit JOINs between views β€” cross-table queries require explicit `INNER JOIN` / `LEFT JOIN` clauses. The agent learns which tables need joining from the `joins` section returned by `inspect_schema`, and writes SQL with explicit joins. This keeps the system generic: any set of parquet files is registered as-is, and the semantic layer YAML defines the relationships.
### The semantic layer
A single `data/semantic_layer.yml` file. Defines:
- **Tables**: column names, types, business meaning, and primary keys (supports composite PKs via a list)
- **Metrics**: named measures the agent can compute (`open_rate`, `messages_sent`, etc.)
- **Dimensions**: slicing axes for decomposition (`channel`, `topic`, `eventually_converted`, etc.)
- **Relationships**: join keys between tables
- **Filters**: globally applied rules (e.g., exclude test campaigns)
- **Value labels**: what enum values mean (e.g., `bulk` vs `trigger` vs `transactional`)
- **Gotchas**: dataset-specific analysis pitfalls surfaced to the agent at plan time
This artifact is **the contract between Isa and Mapo**. Isa produces
it; Mapo consumes it. Once it stabilizes, both of us build forward
without blocking each other.
---
## 7. Tech stack (locked)
| Layer | Choice |
|---|---|
| Hosting | Streamlit Community Cloud (free, public URL) |
| Frontend | Streamlit |
| Backend | LangGraph agent + tools (pure Python, in-process) |
| Orchestration | LangGraph |
| Model | Llama-3.3-70B-Instruct, BF16 |
| Inference | vLLM on ROCm |
| Hardware | AMD MI300X (1 GPU) |
| Data engine | DuckDB |
| Data format | Parquet |
| Semantic layer | YAML |
| Tracing | LangSmith (free tier) |
| Validation | Pydantic |
| Deps | uv |
| Lint | Ruff |
| Containers | Docker |
## 8. Demo strategy
### Two modes the public URL supports
- 🟒 **Live MI300X mode** β€” full live agent against the GPU.
Available during scheduled windows we post.
- πŸ“Ό **Replay mode** β€” pre-recorded canonical investigations,
played back from saved JSON traces. Always available.
- πŸ’¬ **Anthropic fallback** β€” for ad-hoc judge questions when GPU is off.
### Demo scenarios (rehearsed, deterministic)
1. **Why did message open rate drop in the most recent campaign?**
Expected: specific campaign/segment underperformance, tied to send-time or audience slice.
2. **Why did new customer acquisition spike in a particular month?**
Expected: campaign concentration or holiday effect in the acquisition data.
3. **Why is weekend engagement consistently lower than weekday?**
Expected: structural pattern, agent should distinguish from anomaly.
Scenario 1 is our hero demo.
### One thing to verify early
Run scenario 1 end-to-end on Day 3. If the agent can't reach the
conclusion from the data alone, we need a different question. **Don't
wait until Day 5 to find out.**
---
## 9. Cost & GPU budget
We have $100 in AMD Developer Cloud credits = ~50 GPU-hours.
| Phase | GPU hrs | $ |
|---|---|---|
| Day 0 β€” validate vLLM + Llama 70B serves | 3 | $6 |
| Days 1–2 β€” local build (no GPU) | 0 | $0 |
| Day 3 β€” first integration | 4 | $8 |
| Day 4 β€” iteration & prompt tuning | 6 | $12 |
| Day 5 β€” record replays + polish | 8 | $16 |
| Day 6 β€” demo day | 12 | $24 |
| **Reserve** | **17** | **$34** |
| **Total** | **50** | **$100** |
### Rules
- **DESTROY the droplet, don't stop it.** Stopped droplets still bill.
- Credits expire 30 days after activation. Activate close to use.
- One GPU only β€” never the 8x ($15.92/hr will burn through credits in 6 hours).
- ~$5 of our own money for a 200GB block volume to cache model weights
across droplet recreations is a sensible add-on. Worth confirming.
---
## 10. Team split & ownership
The split mirrors the architecture: Isa owns *data + meaning*,
Mapo owns *agent + interface*. The handoff is `semantic_layer.yml`.
### Isa owns
- Demo dataset β€” Parquet files in `data/parquet/`
- Semantic layer YAML
- Ground-truth validation: "is the agent's answer actually right?"
- Build-in-public post about the data layer
### Mapo owns
- LangGraph state machine + agent loop
- The four tools (Pydantic schemas, implementations)
- LLM client (multi-backend switching)
- Streamlit UI (chat, reasoning trace, replay picker)
- vLLM-on-MI300X setup scripts
- Streamlit Cloud deployment
- Build-in-public post about the agent design
### Shared
- Demo script & live narration
- README, submission video, final pitch
- Day-end sync (15 min) to surface blockers
---
## 11. Day-by-day plan (rough)
| Day | Mapo | Isa | Joint |
|---|---|---|---|
| **0** (pre) | Validate vLLM on MI300X, write `start_vllm.sh`, destroy droplet | Prepare demo Parquet files, drop into `data/parquet/` | Repo scaffolded, README locked |
| **1** | Agent loop + tools against Anthropic API | Draft `semantic_layer.yml` v1 | Agree on tool I/O signatures |
| **2** | First end-to-end agent investigation working | Refine semantic layer, prep demo scenarios | Smoke-test with provided dataset |
| **3** | Switch to vLLM backend, run scenario 1 live | Validate scenario 1 ground truth | First real demo run; identify gaps |
| **4** | Prompt tuning, self-critique node | Scenario 2 + 3 ground truth | Build-in-public posts |
| **5** | Streamlit polish, record replays, deploy to Cloud | Final dataset cleanup, write evaluation notes | Rehearse demo |
| **6** | Final fixes, submission prep | Final fixes, submission prep | Submit + pitch |
### What's allowed to slip
- Scenario 3 (the structural-pattern one)
- Streamlit polish beyond functional
- Build-in-public posts after the first two
### What is NOT allowed to slip
- Scenario 1 working end-to-end by Day 3
- Public URL up and demoable by Day 5
- Replay mode working by Day 5
---
## 12. Risks & open questions
### Risks we can name today
1. **vLLM-on-MI300X setup stalls Day 1.** Mitigation: validate on Day 0,
keep Anthropic API fallback always wired.
2. **The "right answer" for our chosen scenario isn't reachable from
the data alone.** Mitigation: pick scenario on Day 0; smoke-test
end-to-end by Day 3.
3. **Streamlit Cloud RAM limit exceeded by data + agent + LangGraph
process overhead.** Mitigation: keep Parquet files under 300 MB total; profile
on Day 2.
4. **Live demo timing variance β€” agent takes 90s instead of 60s.**
Mitigation: don't claim "60 seconds"; frame as "minutes vs hours."
5. **One of us gets sick.** Mitigation: README + repo are clear enough
the other can demo solo.
### Open questions to resolve early
- [ ] Which specific question is our hero demo?
- [ ] Is the semantic layer accurate enough for the demo dataset?
- [ ] Do we pay $5 for the model-weights block volume?
- [ ] Do we want a custom domain, or is `*.streamlit.app` fine? *(default: streamlit.app fine)*
---
## 13. Working agreements
- **Decisions go in this doc.** If we changed our minds, edit it. No
re-litigating in chat.
- **Day-end sync, 15 min.** Just blockers and tomorrow's priority.
- **Push to main freely until Day 4.** From Day 5: PRs only.
- **No new tools, libraries, or scope past Day 3** without explicit
agreement from both of us.
- **The semantic layer is a contract.** Once stable, breaking changes
require a heads-up.
---
## 14. Implementation status
| Component | Status |
|---|---|
| LLM client β€” 3 backends (`minimax`, `vllm`, `replay`) | βœ… done |
| Pydantic state model (`InvestigationState`) | βœ… done |
| LangGraph state machine (6-phase loop) | βœ… done |
| `inspect_schema` tool | βœ… done β€” derived dimensions surface SQL expression |
| `run_sql` tool | βœ… done |
| `compare_periods` tool | βœ… done |
| `decompose_metric` tool | βœ… done |
| System + critique prompts | βœ… done |
| REPL for local testing | βœ… done |
| Streamlit UI | βœ… done |
| Demo dataset in `data/parquet/` | βœ… done |
| Replay recording script | ⬜ pending |
| vLLM Docker + MI300X scripts | ⬜ pending |
---
## 15. Getting started (dev setup)
```bash
# Prerequisites: Python 3.12+, uv installed
git clone https://github.com/Isa-Mapo-Hackathon/why-agent
cd why-agent
uv sync
# Copy and fill in .env
cp .env.example .env
# Set MINIMAX_API_KEY (get from MiniMax dashboard)
# PARQUET_DIR defaults to data/parquet β€” point at data/dev for the toy dataset
# Run the test suite (120 tests, no network required)
uv run pytest
# Interactive REPL against the real MiniMax API
uv run python scripts/repl_graph.py
# > Q: Why did PR activity drop on Oct 21 2018?
# Lint + format (must be clean before any commit)
uv run ruff check --fix && uv run ruff format
# Run the app β€” FastAPI backend + Next.js frontend (default)
uv run uvicorn client.backend.main:app --reload --port 8000 # Terminal 1
cd client/frontend && npm run dev # Terminal 2
# Alternative: Streamlit (single terminal)
uv run streamlit run streamlit_app.py
```
### Environment variables
| Variable | Required | Description |
|---|---|---|
| `MODEL_BACKEND` | Yes | `minimax` / `vllm` / `replay` |
| `MINIMAX_API_KEY` | When `MODEL_BACKEND=minimax` | MiniMax API key |
| `VLLM_ENDPOINT` | When `MODEL_BACKEND=vllm` | e.g. `http://host:8000/v1` |
| `REPLAY_SCENARIO_ID` | When `MODEL_BACKEND=replay` | Scenario JSON filename (without `.json`) |
| `PARQUET_DIR` | No | Path to Parquet files (default: `data/parquet`) |
| `SEMANTIC_LAYER_PATH` | No | Default: `data/semantic_layer.yml` |
---
## 16. Running Locally (Full Stack)
### Default: FastAPI + Next.js
**Terminal 1 β€” FastAPI backend:**
```bash
uv run uvicorn client.backend.main:app --reload --port 8000
```
Backend runs at `http://localhost:8000`. Check health at `http://localhost:8000/api/health`.
**Terminal 2 β€” Next.js frontend:**
```bash
cd client/frontend
npm install # first time only
npm run dev
```
Frontend runs at `http://localhost:3000`.
### Alternative: Streamlit UI
Single terminal, no frontend setup needed. Use this for quick iteration on the agent loop.
```bash
uv run streamlit run streamlit_app.py
```
Opens at `http://localhost:8501`.
### Common development commands
| Task | Command |
|------|---------|
| Install/sync deps | `uv sync` |
| Add dependency | `uv add <package>` (runtime) or `uv add --dev <package>` (dev) |
| Run all tests | `uv run pytest -v` |
| Run one test file | `uv run pytest tests/test_agent_smoke.py -v` |
| Lint & auto-fix | `uv run ruff check --fix` |
| Format code | `uv run ruff format` |
| Type check (optional) | `uv run pyright` |
| Run FastAPI backend | `uv run uvicorn client.backend.main:app --reload --port 8000` |
| Run Next.js frontend | `cd client/frontend && npm run dev` |
| Run Streamlit (alt) | `uv run streamlit run streamlit_app.py` |
| Build Next.js | `cd client/frontend && npm run build` |
| Build Docker image | `docker build -t why-agent:latest .` |
---
## 17. Development & Testing
### Running tests
```bash
# All tests
uv run pytest
# Single file
uv run pytest tests/test_tools.py -v
# Single test
uv run pytest tests/test_tools.py::test_inspect_schema -v
# With print output
uv run pytest -s
```
Tests are **smoke tests** β€” we verify that tools run without crashing and return the expected JSON shape. Mocking is minimal.
### Code quality gates (required before commit)
```bash
uv run ruff check --fix # Fix lint issues
uv run ruff format # Format code
```
Both must pass before committing. Set up a pre-commit hook to automate:
```bash
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
uv run ruff check --fix && uv run ruff format || exit 1
EOF
chmod +x .git/hooks/pre-commit
```
### Using the REPL (interactive testing)
```bash
# Against MiniMax API (requires MINIMAX_API_KEY)
export MODEL_BACKEND=minimax
uv run python scripts/repl_graph.py
# > Q: Why did message open rate drop?
# > Q: Why does weekend engagement differ?
# Against replay (no API key needed)
export MODEL_BACKEND=replay
export REPLAY_SCENARIO_ID=scenario_1
uv run python scripts/repl_graph.py
```
---
## 18. Deployment to HF Spaces
why-agent is designed to deploy to Hugging Face Spaces via Docker. The included `Dockerfile` is multi-stage and includes everything: Python agent, FastAPI backend, Next.js frontend, and nginx reverse proxy.
### Quick deploy (3 steps)
**1. Push code to HF Spaces:**
```bash
# Set up remote once (replace with your Spaces URL)
git remote add space https://huggingface.co/spaces/YOUR_USERNAME/why-agent.git
# Then push (HF Spaces auto-detects Dockerfile and builds)
git push space main
```
**2. Set environment variables in HF Spaces Settings:**
Go to Space Settings > Variables and add:
| Variable | Value |
|----------|-------|
| `MODEL_BACKEND` | `replay` (recommended) or `vllm` if you have a GPU endpoint |
| `MINIMAX_API_KEY` | Only if using `MODEL_BACKEND=minimax` |
| `VLLM_ENDPOINT` | Only if using `MODEL_BACKEND=vllm` (e.g. `http://vllm-api.example.com:8000/v1`) |
| `HF_DATASET_ID` | Optional: e.g. `username/why-agent-data` (auto-downloads at boot) |
**3. Verify:**
```bash
curl https://YOUR_SPACE_URL/api/health
```
Should return: `{"ok": true}`
### Model backends explained
| Backend | Use case | Cost | Setup |
|---------|----------|------|-------|
| **replay** | Demo when GPU offline, pre-recorded scenarios | Free | Set `REPLAY_SCENARIO_ID=scenario_1` |
| **minimax** | Fallback LLM for ad-hoc questions | ~$0.01/query | Set `MINIMAX_API_KEY` |
| **vllm** | High-quality, fast inference on GPU | $1.99/hr (AMD MI300X) | Set `VLLM_ENDPOINT` |
### Recording demo scenarios for offline playback
When a scenario works end-to-end, record it:
```bash
export MODEL_BACKEND=minimax
export MINIMAX_API_KEY=your-key
uv run python scripts/record_replay.py --scenario scenario_1
```
This saves `replays/scenario_1.json`. Commit it and deploy with `MODEL_BACKEND=replay`.
### Docker: build and run locally
```bash
# Build
docker build -t why-agent:latest .
# Run
docker run -p 7860:7860 -e MODEL_BACKEND=replay why-agent:latest
```
Then visit `http://localhost:7860`.
### Environment variables reference (complete)
| Variable | Default | Description |
|----------|---------|-------------|
| `MODEL_BACKEND` | β€” | LLM backend: `minimax`, `vllm`, or `replay` |
| `MINIMAX_API_KEY` | β€” | MiniMax API key (if using minimax backend) |
| `VLLM_ENDPOINT` | β€” | vLLM server URL (if using vllm backend; include `/v1`) |
| `REPLAY_SCENARIO_ID` | β€” | Scenario ID for replay mode (filename without `.json`) |
| `PARQUET_DIR` | `/app/data/parquet` | Path to Parquet dataset directory |
| `SEMANTIC_LAYER_PATH` | `/app/data/semantic_layer.yml` | Path to semantic layer YAML |
| `HF_DATASET_ID` | β€” | HF Dataset ID to auto-download at boot (optional) |
| `LANGSMITH_API_KEY` | β€” | LangSmith API key for tracing (optional) |
### Health check endpoints
```bash
# Health check
curl http://localhost:7860/api/health
# Returns: {"ok": true}
# Demo questions
curl http://localhost:7860/api/demo-questions
# Returns: {"questions": [...]}
# Investigate (POST)
curl -X POST http://localhost:7860/api/investigate \
-H "Content-Type: application/json" \
-d '{"question": "Why did open rate drop?"}'
# Streams Server-Sent Events (SSE)
```
### Troubleshooting deployment
| Issue | Solution |
|-------|----------|
| Build fails with npm error | Ensure Node 20+ installed; run `npm install --legacy-peer-deps` locally |
| API returns 500 | Check HF Spaces logs; verify `PARQUET_DIR` and `SEMANTIC_LAYER_PATH` exist |
| vLLM endpoint unreachable | Verify `VLLM_ENDPOINT` includes `/v1`; check GPU server is running |
| Data not loading | Set `HF_DATASET_ID` to auto-download, or manually COPY Parquet files into Dockerfile |
| Replay scenario not found | Verify `REPLAY_SCENARIO_ID` matches filename in `replays/` (without `.json`) |
---
## 19. Architecture & Project Structure
```
why-agent/
β”œβ”€β”€ agent/ # Core agent logic
β”‚ β”œβ”€β”€ graph.py # LangGraph state machine (6-phase loop)
β”‚ β”œβ”€β”€ state.py # Pydantic InvestigationState model
β”‚ β”œβ”€β”€ client.py # Multi-backend LLM client
β”‚ β”œβ”€β”€ constants.py # Named constants (backends, tools, demo questions)
β”‚ β”œβ”€β”€ tools/ # The four tools
β”‚ β”‚ β”œβ”€β”€ inspect_schema.py # Returns table metadata + business context
β”‚ β”‚ β”œβ”€β”€ run_sql.py # Execute read-only DuckDB queries
β”‚ β”‚ β”œβ”€β”€ compare_periods.py # Quantify metric change between windows
β”‚ β”‚ └── decompose_metric.py # Slice metric by dimensions, rank anomalies
β”‚ └── prompts/ # System + critique prompts (markdown)
β”‚ β”œβ”€β”€ system.md
β”‚ └── critique.md
β”‚
β”œβ”€β”€ client/
β”‚ β”œβ”€β”€ backend/ # FastAPI server
β”‚ β”‚ β”œβ”€β”€ main.py # GET /health, POST /api/investigate
β”‚ β”‚ β”œβ”€β”€ deps.py # Dependency injection
β”‚ β”‚ β”œβ”€β”€ sse.py # Server-Sent Events formatting
β”‚ β”‚ └── tests/
β”‚ └── frontend/ # Next.js app (React + TypeScript)
β”‚ β”œβ”€β”€ src/app/page.tsx # Main UI page
β”‚ β”œβ”€β”€ src/app/api/investigate # Next.js API route (optional)
β”‚ └── package.json
β”‚
β”œβ”€β”€ data/
β”‚ β”œβ”€β”€ parquet/ # Dataset files (user-provided; gitignored)
β”‚ β”œβ”€β”€ semantic_layer.yml # Business metadata, metrics, dimensions, joins
β”‚ └── root_cause/ # Ground-truth documentation
β”‚
β”œβ”€β”€ replays/ # Pre-recorded investigation traces (JSON)
β”‚ └── scenario_1.json
β”‚
β”œβ”€β”€ tests/ # Python smoke tests
β”‚ β”œβ”€β”€ test_tools.py # Tool execution and output shape
β”‚ β”œβ”€β”€ test_client_backends.py # Verify 3 backends (minimax, vllm, replay)
β”‚ └── test_agent_smoke.py # End-to-end agent smoke test
β”‚
β”œβ”€β”€ docker/ # Container config
β”‚ β”œβ”€β”€ Dockerfile # Multi-stage: Next.js + Python + nginx
β”‚ β”œβ”€β”€ entrypoint.sh # Boot script (handles HF Dataset download)
β”‚ β”œβ”€β”€ nginx.conf # Reverse proxy (routes / to Next.js, /api/* to FastAPI)
β”‚ └── supervisord.conf # Process management (nginx, FastAPI, Next.js)
β”‚
β”œβ”€β”€ scripts/ # Utilities
β”‚ β”œβ”€β”€ repl_graph.py # Interactive REPL for testing the agent
β”‚ └── record_replay.py # Save a scenario as replay JSON
β”‚
β”œβ”€β”€ streamlit_app.py # Streamlit UI (standalone, no backend needed)
β”œβ”€β”€ pyproject.toml # Dependencies + test config
β”œβ”€β”€ Dockerfile # Deployment image
β”œβ”€β”€ .env.example # Environment template
β”œβ”€β”€ CLAUDE.md # Implementation decisions & constraints
β”œβ”€β”€ README.md # This file (project overview + business context)
└── docs/
└── why-agent-architecture.png # Diagram
```
---
## 20. Coding conventions
Per CLAUDE.md, follow these when writing code:
1. **Sync by default** β€” DuckDB and Streamlit are sync. Use `async def` only at the LLM boundary.
2. **Pydantic v2** β€” All structured data (tool inputs/outputs, state, semantic layer).
3. **Type annotations** β€” Required on public functions (args + return type).
4. **No print()** β€” Use `logger = logging.getLogger(__name__)` in agent code.
5. **No magic strings** β€” Backend names, tool names, scenario IDs go in `agent/constants.py`.
6. **Tool docstrings for the LLM** β€” Write them as if the model will read them (be descriptive about what it does and when to use it).
### Example tool implementation
```python
from pydantic import BaseModel, Field
import logging
logger = logging.getLogger(__name__)
class MyToolInput(BaseModel):
query: str = Field(description="A human-readable query or metric name.")
class MyToolOutput(BaseModel):
result: dict
error: str | None = None
def my_tool(args: MyToolInput) -> dict:
"""Use this tool to analyze X. Returns a dict with 'result' (the data) and optional 'error'."""
try:
result = ...
return {"result": result}
except Exception as exc:
logger.exception("Tool failed for query: %s", args.query)
return {"error": str(exc), "hint": "Try phrasing the query differently"}
```
---
## 21. Locked decisions (do not change without explicit approval)
These decisions are locked per CLAUDE.md. Changing any requires discussion:
| Decision | Value | Why |
|----------|-------|-----|
| Architecture | Single agent (not multi-agent) | Simpler to debug, easier to understand agentic fundamentals |
| Tool count | 4 tools (fixed) | Fewer integrations = fewer demo failure modes |
| Orchestration | LangGraph (not CrewAI, AutoGen, etc.) | Explicit state machine, good tracing, community support |
| Model (prod) | Llama-3.3-70B (vLLM) | Open-source, fast on MI300X, no licensing |
| Model (dev) | MiniMax-M2.7 (API fallback) | No GPU required, quick iteration |
| Data engine | DuckDB on Parquet | Embedded, column-oriented, single query engine |
| Semantic layer | Single YAML file (hand-written) | Simple, no tooling overhead, easy to version |
| UI | Streamlit (primary) + Next.js (secondary) | Free hosting, rapid iteration, good for demos |
| Hosting | HF Spaces (primary) + Streamlit Cloud (backup) | Free, simple, community-friendly |
| License | MIT | Open-source, permissive |
If a task seems to require changing one of these, pause and ask before proceeding.
---
## 22. Risks & known limitations
1. **Parquet size** β€” Keep total Parquet data under 500 MB to fit in HF Spaces' memory limit. Profile on Day 2.
2. **Investigation latency** β€” Agent might take 60–120 seconds on a fallback model. Frame demos as "minutes vs hours," not "60 seconds."
3. **GPU availability** β€” The MI300X droplet costs $1.99/hr. Use `MODEL_BACKEND=replay` when the GPU is off.
4. **Concurrent requests** β€” HF Spaces free tier queues additional requests (no parallelism). For production, use a dedicated server.
5. **Replay maintenance** β€” Scenarios must be re-recorded if the agent loop changes significantly.
---
## 23. Resources & links
- AMD Developer Hackathon: https://lablab.ai/ai-hackathons/amd-developer
- AMD Developer Cloud docs: https://www.amd.com/en/developer/resources/cloud-access/amd-developer-cloud.html
- LangGraph docs: https://langchain-ai.github.io/langgraph/
- vLLM on ROCm: https://docs.vllm.ai/en/latest/getting_started/amd-installation.html
- Streamlit Cloud: https://share.streamlit.io
- MiniMax API: https://platform.minimaxi.chat/
- Hugging Face Spaces: https://huggingface.co/spaces