Spaces:
Running
Running
File size: 9,792 Bytes
c0f79cc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | # TASKS β Conversational Analytics Assistant (chat-service)
Step-by-step build checklist, derived from the implementation plan. Check items off as you go. Order matters β each milestone assumes the previous one works.
---
## Milestone 0 β Repo & environment setup
- [x] Create `chat-service/` folder structure
- [x] `git init`, set `origin` remote
- [x] Create virtual environment (`python -m venv venv`)
- [x] Activate venv
- [x] Create `.env.example` with all vars from plan Β§10 (placeholders)
- [x] Create local `.env` (real values, gitignored)
- [x] Add `.gitignore` (venv, `.env`, `__pycache__`, `*.pyc`)
- [x] Initial commit ("chore: project skeleton")
---
## Milestone 1 β Service skeleton (boots + Groq round-trip)
- [x] Add core deps to `requirements.txt`: `fastapi`, `uvicorn[standard]`, `pydantic-settings`, `groq`, `python-dotenv`
- [x] `pip install -r requirements.txt`
- [x] `app/config.py` β `Settings(BaseSettings)` class reading env vars
- [x] `app/agent/llm.py` β thin Groq client wrapper (single `ask(prompt: str) -> str` function)
- [x] `app/main.py`:
- [x] `GET /health` β `{"status": "ok"}`
- [x] Temporary `POST /chat` β calls Groq wrapper directly, no LangGraph yet, just to prove the chain works
- [x] Run locally: `uvicorn app.main:app --reload --port 8002`
- [x] Test `/health` with curl/Postman
- [x] Test `/chat` with curl/Postman β confirm real Groq response comes back
- [x] Write `Dockerfile` (Python 3.12 base, matches existing services)
- [x] Build + run container locally, re-test `/health` and `/chat` inside Docker
- [x] Commit ("feat: service skeleton with Groq round-trip")
**Done when:** `/health` and `/chat` both work locally and in Docker.
---
## Milestone 2 β First real tool + LangGraph agent (`sql_query_tool`)
- [x] Add deps: `langgraph`, `langchain-groq`, `sqlalchemy`, `psycopg[binary]` (or `asyncpg`)
- [x] Create **read-only Postgres role** (`chat_ro_user`) β DB side, not app code
- [x] `app/db/postgres.py` β read-only connection pool using `POSTGRES_READONLY_URL`
- [x] Confirm exact schema fields needed (resolve plan Β§12.1: `ArticleFakeNews`, `ArticleTopic`, etc. field names)
- [x] `app/tools/sql_tool.py`:
- [x] Define `metric` enum: `fake_news_count`, `articles_by_topic`, `sentiment_breakdown`, `propaganda_count`, `hate_speech_count`, `article_lookup`, `dialect_breakdown`
- [x] Write one parametrized SQL query per metric
- [x] Return shape: `{"rows": [...], "sql_used": "...", "source_refs": [...]}`
- [x] Write unit tests for each metric in `tests/test_sql_tool.py` (against a test/staging DB)
- [x] `app/agent/state.py` β `AgentState` TypedDict (`messages`, `sources`, `session_id`, `user_id`)
- [x] `app/agent/prompts.py` β system prompt (identity, tool-preference rule, citation rule, language-matching rule, no-fabrication rule)
- [x] `app/agent/graph.py` β LangGraph ReAct-style agent wired to `sql_query_tool` only
- [x] Wire `/chat` in `main.py` to call the LangGraph agent instead of the raw Groq wrapper
- [x] `app/schemas/request.py` / `app/schemas/response.py` β formalize `ChatRequest` / `ChatResponse` models per plan Β§4
- [x] Manually test: "How many fake news articles were detected last month?" β correct tool call + correct answer
- [x] Commit ("feat: sql_query_tool + LangGraph agent")
**Done when:** agent correctly answers at least 3 different stat questions using real DB data, citing article IDs.
---
## Milestone 3 β Django proxy endpoint
- [x] In Django: add `CHAT_SERVICE_URL` and `CHAT_SERVICE_INTERNAL_TOKEN` to settings/env
- [x] New view: `POST /api/v1/chat/`
- [x] Verify JWT (reuse existing auth)
- [ ] Apply DRF throttling/rate limit (no need for now)
- [x] Forward `{session_id, message, user_id}` to chat service with internal token header
- [x] Return chat service's JSON response unchanged
- [x] Confirm chat service rejects requests without the internal token
- [x] Test end-to-end: frontend-style request β Django β chat service β Groq β back
- [x] Commit on Django repo ("feat: chat proxy endpoint")
**Done when:** a JWT-authenticated request through Django reaches the chat service and gets a real answer.
---
## Milestone 4 β `graph_query_tool` (Neo4j)
- [x] Confirm current Neo4j label/relationship conventions used by `kg_sync` (resolve plan Β§12.2)
- [x] ~~Create read-only Neo4j role/user~~ β N/A, Aura Free has no RBAC; enforcement moved to app-level (read-only transactions + no write Cypher in tool code)
- [x] `app/db/neo4j.py` β driver wrapper using `NEO4J_*` vars, enforcing read-only via explicit read transactions
- [x] `app/tools/graph_tool.py`:
- [x] Define `query_type` enum (7 total): `entity_connections`, `entity_mentions`, `shared_entities_between_articles`, `most_connected_entities`, `article_verdict`, `claims_for_article`, `analysis_for_article`
- [x] Write one parametrized Cypher template per query_type
- [x] Return shape: `{"rows": [...], "source_refs": [...]}`
- [x] Add `graph_query_tool` to the LangGraph agent's tool list
- [x] Unit tests in `tests/test_graph_tool.py`
- [x] Manually test: "Who is connected to [entity]?" β correct Cypher template used, correct answer
- [x] Commit ("feat: graph_query_tool")
**Done when:** agent correctly answers at least 2 relationship questions using real graph data.
---
## Milestone 5 β Memory (multi-turn)
- [x] Add dep: Redis client (`redis` / `redis[hiredis]`)
- [x] `app/memory/redis_checkpointer.py` β LangGraph checkpointer backed by Redis, key prefix `chat:checkpoint:{session_id}`
- [x] Wire checkpointer into `app/agent/graph.py` compilation
- [x] Set `CHAT_SESSION_TTL_SECONDS` TTL on session keys
- [x] Implement history trimming: keep last `CHAT_HISTORY_MAX_TURNS` verbatim, summarize older turns beyond that
- [x] Manual test: ask a question, then a follow-up ("and what about last month?") using the same `session_id` β confirm context carries over
- [x] Manual test: new `session_id` β confirm no memory leaks across sessions
- [x] Commit ("feat: Redis-backed multi-turn memory")
**Done when:** follow-up questions correctly resolve using prior turn context, and TTL/trimming work as expected.
---
## Milestone 6 β Citations end-to-end
- [x] `app/tools/article_tool.py` β `get_article_detail(article_id)` for resolving full title/snippet
- [x] Confirm every tool (`sql_query_tool`, `graph_query_tool`) consistently returns `source_refs`
- [x] Agent post-processing step: merge all `source_refs` collected during the turn into final `sources[]` in the response
- [x] Update system prompt to explicitly require citing every ID it used
- [x] Manual test: ask a question, verify `sources[]` in the JSON response matches what was actually used
- [x] Commit ("feat: citation resolution")
**Done when:** every factual answer includes a non-empty, accurate `sources[]` array.
---
## Milestone 7 β `hybrid_search_tool`
- [x] Confirm reuse path for `_embed` (call existing Django/analysis embedding function - the function is in app/services/nlp_client.py)
- [x] `app/tools/hybrid_tool.py`:
- [x] Step 1: embed query text
- [x] Step 2: pgvector cosine similarity search over `search_vector`
- [x] Step 3: for each top article, pull mentioned entities from Neo4j
- [x] Step 4: merge into single ranked result set with combined `source_refs`
- [x] Add tool to agent's tool list
- [ ] Manual test: "Articles about the election, and who's mentioned" β correct combined results
- [x] Commit ("feat: hybrid_search_tool")
**Done when:** at least one combined semantic+graph question is answered correctly with merged citations.
---
## Milestone 8 β Frontend chat widget
- [ ] Generate/persist `session_id` client-side (e.g. `localStorage`, created on first load)
- [ ] Chat UI component: message list, input box, send button
- [ ] Call `POST /api/v1/chat/` with `{session_id, message}`
- [ ] Render `answer` text
- [ ] Render `sources[]` as clickable links (article/entity)
- [ ] Loading state while waiting for response
- [ ] Basic error state (service down / timeout)
- [ ] Commit ("feat: chat widget")
**Done when:** a real user can open the widget, ask a question, get an answer with clickable citations, and ask a follow-up.
---
## Milestone 9 β Tests, docs, hardening
- [ ] Unit tests for all tools (`sql_tool`, `graph_tool`, `hybrid_tool`) with mocked/test DB data
- [ ] `tests/test_agent_e2e.py` β at least 3 end-to-end scenarios (stat question, relationship question, follow-up question)
- [ ] `chat-service/README.md` β setup instructions, env vars, how to run locally + in Docker
- [ ] Verify `.env.example` is complete and matches plan Β§10
- [ ] Security checklist review (plan Β§8) β confirm every box is actually true in the running system:
- [ ] Read-only DB roles confirmed (test that a write attempt fails)
- [ ] No raw SQL/Cypher ever comes from the LLM β code review confirms only enum params reach tools
- [ ] Internal token required and enforced
- [ ] Rate limiting active on Django proxy
- [ ] Groq key not logged anywhere
- [ ] Tool calls logged (name + params, not raw rows)
- [ ] Add `chat_service` to `docker-compose.yml` (no published host port in prod config)
- [ ] Final walkthrough / demo run-through for defense
- [ ] Tag/commit ("chore: v1 complete")
**Done when:** everything above is checked and you can demo the full flow live without surprises.
---
## Open items to resolve before/while coding (carry over from plan Β§12)
- [ ] Confirm exact Postgres field names per metric
- [ ] Confirm Neo4j label/relationship naming conventions
- [ ] Decide: authenticated-only sessions, or also support anonymous/demo sessions for defense
- [ ] Decide: citation links deep-link into real frontend routes, or raw IDs for now
|