Spaces:
Running
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
- Create
chat-service/folder structure -
git init, setoriginremote - Create virtual environment (
python -m venv venv) - Activate venv
- Create
.env.examplewith all vars from plan Β§10 (placeholders) - Create local
.env(real values, gitignored) - Add
.gitignore(venv,.env,__pycache__,*.pyc) - Initial commit ("chore: project skeleton")
Milestone 1 β Service skeleton (boots + Groq round-trip)
- Add core deps to
requirements.txt:fastapi,uvicorn[standard],pydantic-settings,groq,python-dotenv -
pip install -r requirements.txt -
app/config.pyβSettings(BaseSettings)class reading env vars -
app/agent/llm.pyβ thin Groq client wrapper (singleask(prompt: str) -> strfunction) -
app/main.py:-
GET /healthβ{"status": "ok"} - Temporary
POST /chatβ calls Groq wrapper directly, no LangGraph yet, just to prove the chain works
-
- Run locally:
uvicorn app.main:app --reload --port 8002 - Test
/healthwith curl/Postman - Test
/chatwith curl/Postman β confirm real Groq response comes back - Write
Dockerfile(Python 3.12 base, matches existing services) - Build + run container locally, re-test
/healthand/chatinside Docker - 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)
- Add deps:
langgraph,langchain-groq,sqlalchemy,psycopg[binary](orasyncpg) - Create read-only Postgres role (
chat_ro_user) β DB side, not app code -
app/db/postgres.pyβ read-only connection pool usingPOSTGRES_READONLY_URL - Confirm exact schema fields needed (resolve plan Β§12.1:
ArticleFakeNews,ArticleTopic, etc. field names) -
app/tools/sql_tool.py:- Define
metricenum:fake_news_count,articles_by_topic,sentiment_breakdown,propaganda_count,hate_speech_count,article_lookup,dialect_breakdown - Write one parametrized SQL query per metric
- Return shape:
{"rows": [...], "sql_used": "...", "source_refs": [...]}
- Define
- Write unit tests for each metric in
tests/test_sql_tool.py(against a test/staging DB) -
app/agent/state.pyβAgentStateTypedDict (messages,sources,session_id,user_id) -
app/agent/prompts.pyβ system prompt (identity, tool-preference rule, citation rule, language-matching rule, no-fabrication rule) -
app/agent/graph.pyβ LangGraph ReAct-style agent wired tosql_query_toolonly - Wire
/chatinmain.pyto call the LangGraph agent instead of the raw Groq wrapper -
app/schemas/request.py/app/schemas/response.pyβ formalizeChatRequest/ChatResponsemodels per plan Β§4 - Manually test: "How many fake news articles were detected last month?" β correct tool call + correct answer
- 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
- In Django: add
CHAT_SERVICE_URLandCHAT_SERVICE_INTERNAL_TOKENto settings/env - New view:
POST /api/v1/chat/- Verify JWT (reuse existing auth)
- Apply DRF throttling/rate limit (no need for now)
- Forward
{session_id, message, user_id}to chat service with internal token header - Return chat service's JSON response unchanged
- Confirm chat service rejects requests without the internal token
- Test end-to-end: frontend-style request β Django β chat service β Groq β back
- 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)
- Confirm current Neo4j label/relationship conventions used by
kg_sync(resolve plan Β§12.2) -
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) -
app/db/neo4j.pyβ driver wrapper usingNEO4J_*vars, enforcing read-only via explicit read transactions -
app/tools/graph_tool.py:- Define
query_typeenum (7 total):entity_connections,entity_mentions,shared_entities_between_articles,most_connected_entities,article_verdict,claims_for_article,analysis_for_article - Write one parametrized Cypher template per query_type
- Return shape:
{"rows": [...], "source_refs": [...]}
- Define
- Add
graph_query_toolto the LangGraph agent's tool list - Unit tests in
tests/test_graph_tool.py - Manually test: "Who is connected to [entity]?" β correct Cypher template used, correct answer
- Commit ("feat: graph_query_tool")
Done when: agent correctly answers at least 2 relationship questions using real graph data.
Milestone 5 β Memory (multi-turn)
- Add dep: Redis client (
redis/redis[hiredis]) -
app/memory/redis_checkpointer.pyβ LangGraph checkpointer backed by Redis, key prefixchat:checkpoint:{session_id} - Wire checkpointer into
app/agent/graph.pycompilation - Set
CHAT_SESSION_TTL_SECONDSTTL on session keys - Implement history trimming: keep last
CHAT_HISTORY_MAX_TURNSverbatim, summarize older turns beyond that - Manual test: ask a question, then a follow-up ("and what about last month?") using the same
session_idβ confirm context carries over - Manual test: new
session_idβ confirm no memory leaks across sessions - 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
-
app/tools/article_tool.pyβget_article_detail(article_id)for resolving full title/snippet - Confirm every tool (
sql_query_tool,graph_query_tool) consistently returnssource_refs - Agent post-processing step: merge all
source_refscollected during the turn into finalsources[]in the response - Update system prompt to explicitly require citing every ID it used
- Manual test: ask a question, verify
sources[]in the JSON response matches what was actually used - Commit ("feat: citation resolution")
Done when: every factual answer includes a non-empty, accurate sources[] array.
Milestone 7 β hybrid_search_tool
- Confirm reuse path for
_embed(call existing Django/analysis embedding function - the function is in app/services/nlp_client.py) -
app/tools/hybrid_tool.py:- Step 1: embed query text
- Step 2: pgvector cosine similarity search over
search_vector - Step 3: for each top article, pull mentioned entities from Neo4j
- Step 4: merge into single ranked result set with combined
source_refs
- Add tool to agent's tool list
- Manual test: "Articles about the election, and who's mentioned" β correct combined results
- 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_idclient-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
answertext - 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.exampleis 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_servicetodocker-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