An AI-first CRM screen for pharmaceutical field representatives. Reps log Healthcare Professional (HCP) interactions by talking or typing — a LangGraph agent extracts the details, fills the form, summarizes voice notes, suggests follow-ups, and writes to the database. No manual form-filling.
Built by Alok Deep · Round 1 technical assignment — AI-First CRM HCP Module.
Field reps in pharma waste selling time on data entry — typing up who they met, what was discussed, what samples they left, and what to do next. This module flips that: the rep describes the visit in natural language (typed or spoken) and an AI agent does the paperwork.
It is deliberately built the way a real internal tool would be: a LangGraph agent orchestrating six typed tools, a FastAPI backend, a React + Redux UI, Groq LLM + Whisper speech-to-text, and a PostgreSQL database — not a hard-coded demo.
The one rule (from the brief): the rep must not fill the left-hand form manually. Every field is populated by the AI assistant on the right, through LangGraph tools driven by an LLM.
- 🗣️ Log by talking or typing — "Today I called Dr. Smith, discussed Product X efficacy, sentiment positive, shared brochures" → the whole form fills itself.
- ✏️ Correct conversationally — "Actually it was Dr. John, negative, and a meeting not a call" → only those fields change.
- 🎙️ Voice notes (with consent) — record a note → Groq Whisper transcribes → the agent summarizes it into Topics Discussed.
- 💡 Proactive next steps — the agent suggests follow-up actions and offers to save.
- 🔎 HCP lookup — searches the CRM database and auto-selects a match.
- 💾 Persist to the database — saves the completed interaction to PostgreSQL.
| Requirement | Where it lives in the project | Status |
|---|---|---|
| Log Interaction screen (form and chat) | frontend/src/components/ — split-screen InteractionForm + ChatPanel |
✅ |
| Frontend: React + Redux | frontend/src/store/ — Redux Toolkit slices (interaction, chat) |
✅ |
| Backend: Python + FastAPI | backend/app/main.py + routers |
✅ |
| AI framework: LangGraph | backend/app/agent/graph.py — StateGraph (agent ↔ ToolNode) |
✅ |
| LLM: Groq | llama-3.3-70b-versatile (see LLM note) |
✅ |
| Database: MySQL/Postgres | PostgreSQL 16 via SQLAlchemy + docker-compose.yml |
✅ |
| Font: Google Inter | loaded in frontend/index.html |
✅ |
| Describe the agent's role | below | ✅ |
| ≥ 5 tools incl. Log + Edit | 6 tools in backend/app/agent/tools.py |
✅ |
| README + one repo | this file | ✅ |
The LangGraph agent is the orchestration brain of the screen. It sits between the rep's natural language (typed or spoken) and the structured CRM form. On every message it runs a StateGraph in an agent → tools → agent loop:
- The agent node (Groq LLM with all tools bound) interprets intent and decides which tool(s) to call and with what arguments.
- The ToolNode executes those tools; each returns a
Commandthat patches shared state — the form snapshot and the field-levelform_updatessent back to the UI. - Control returns to the agent to produce a short, natural confirmation.
The frontend is the source of truth for the form: each request sends the current form snapshot, and the backend returns only the changed fields, which Redux applies (with a flash-highlight on updated fields).
| # | Tool | Mandatory | What it does | Uses LLM |
|---|---|---|---|---|
| 1 | log_interaction |
✅ | Entity-extracts HCP, date, time, type, sentiment, materials & samples from free text, and writes an elaborated professional Topics Discussed note. | ✅ |
| 2 | edit_interaction |
✅ | Modifies only the specific field(s) the rep corrects, leaving the rest intact. | ✅ |
| 3 | summarize_notes |
— | Condenses a raw voice-note transcript / long dictation into clean Topics Discussed bullets. | ✅ |
| 4 | suggest_followups |
— | Generates 3 actionable next-step recommendations from the current interaction. | ✅ |
| 5 | search_hcp |
— | Looks up an HCP in the PostgreSQL database and auto-fills a unique match. | — |
| 6 | save_interaction |
— | Persists the completed interaction to the interactions table. |
— |
Bonus AI feature: recorded voice notes are transcribed by Groq Whisper (
whisper-large-v3-turbo) viaPOST /api/transcribe, then handed to thesummarize_notestool — a third Groq model working alongside the chat LLM.
+-------------------------------------------------------------+
| FRONTEND (React + Redux) |
| |
| InteractionForm <----- Redux store -----> ChatPanel |
| (left: form) interaction + chat (right: chat) |
| ^ slices | |
| | form_updates | message + form + history
+--------|-------------------------------------------|--------+
| v
+-------------------------------------------------------------+
| BACKEND (FastAPI) |
| /api/chat /api/transcribe /api/interactions |
| | | | /api/hcps |
| v v v |
| +---------------------------+ +----------------------+ |
| | LangGraph Agent | | Groq Whisper | |
| | StateGraph: | | speech -> text | |
| | agent <--> ToolNode | +----------------------+ |
| | | | | |
| | Groq LLM 6 tools ------+---> patch form / query DB |
| +---------------------------+ |
+----------------------------|--------------------------------+
v
+-----------------------+
| PostgreSQL 16 |
| hcps · interactions |
+-----------------------+
Design choices worth calling out:
- Stateless agent, frontend owns the form. Each request carries the current form snapshot, so tools like
edit_interactionknow exactly what exists — no server-side session state to drift. - Tools return
Commandobjects. Every tool patches shared graph state cleanly (form +form_updates+ a tool message), so multiple tools can run in one turn and the reducer merges their deltas. - Robust to weaker models. List fields accept arrays or strings or
null; a server-side cleaner drops"null"-ish junk — so tool-call validation never fails on quirky LLM output. - Model-agnostic via env.
GROQ_MODELandGROQ_WHISPER_MODELswap models without code changes.
| Layer | Choice | Why |
|---|---|---|
| Agent | LangGraph | StateGraph + ToolNode give explicit, debuggable tool orchestration |
| LLM | Groq — Llama 3.3 70B | Fast, reliable multi-tool function calling (see LLM note) |
| Speech | Groq Whisper | On-stack voice-note transcription, no extra vendor |
| Backend | FastAPI + Pydantic | Async-ready, auto Swagger docs, typed schemas |
| Frontend | React + Redux Toolkit (Vite) | Redux for shared form/chat state; Vite for fast HMR |
| Database | PostgreSQL (SQLAlchemy) | Meets the SQL requirement; swappable to MySQL/SQLite via DATABASE_URL |
| Font | Google Inter | Clean, modern UI type |
Prerequisites: Python 3.10+ · Node.js 18+ · Docker (for Postgres) · a free Groq API key
# 1. Start PostgreSQL
docker compose up -d
# 2. Backend
cd backend
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
cp .env.example .env # then paste your GROQ_API_KEY into .env
# DATABASE_URL is already set for the docker Postgres above
uvicorn app.main:app --reload --port 8000
# 3. Frontend (new terminal)
cd frontend
npm install
npm run dev| Service | URL |
|---|---|
| Frontend (React) | http://localhost:5173 |
| Backend (FastAPI) | http://localhost:8000 |
| API docs (Swagger) | http://localhost:8000/docs |
| Health check | http://localhost:8000/api/health |
No Docker? Leave
DATABASE_URLas the default SQLite line in.envand skip step 1 — the app runs with zero database setup. Postgres is recommended to match the assignment's requirement.
- Type: "Today I met Dr. Smith, discussed Product X efficacy, positive sentiment, shared brochures." →
log_interaction - Type: "Actually the name was Dr. John and the sentiment was negative." →
edit_interaction - Click 🎙 Summarize from Voice Note, speak a quick note → Whisper +
summarize_notes - Type: "Suggest follow-up actions for this visit." →
suggest_followups - Type: "Find Dr. Sharma in the system." →
search_hcp - Type: "Save this interaction." →
save_interaction(then show the row in Postgres)
AI_CRM/
├── backend/ # FastAPI + LangGraph
│ ├── app/
│ │ ├── main.py # FastAPI app, CORS, startup seed
│ │ ├── config.py # env settings (Groq key/model, DB url)
│ │ ├── database.py # SQLAlchemy engine/session
│ │ ├── models.py # HCP, Interaction tables
│ │ ├── schemas.py # Pydantic request/response models
│ │ ├── seed.py # sample HCPs
│ │ ├── agent/
│ │ │ ├── graph.py # StateGraph wiring + run_agent()
│ │ │ ├── tools.py # the 6 LangGraph tools
│ │ │ ├── state.py # AgentState + reducers
│ │ │ └── llm.py # ChatGroq factory
│ │ └── routers/
│ │ ├── chat.py # /api/chat + /api/transcribe (Whisper)
│ │ └── interactions.py # CRUD + /api/hcps
│ ├── requirements.txt
│ └── .env.example
├── frontend/ # React + Redux (Vite)
│ ├── src/
│ │ ├── store/ # Redux Toolkit slices
│ │ ├── api/client.js
│ │ ├── hooks/useRecorder.js # MediaRecorder voice capture
│ │ └── components/ # InteractionForm, ChatPanel, ChipList
│ ├── public/
│ │ ├── logo.png # ← add your logo
│ │ └── screenshots/ # ← add screenshots + video thumbnail
│ └── package.json
├── docker-compose.yml # PostgreSQL 16
└── README.md
The assignment specified gemma2-9b-it, but Groq decommissioned that model on 2025-10-08 (deprecations) — it no longer serves requests. The same brief explicitly permits llama-3.3-70b-versatile, which is active and far more reliable at multi-tool function calling, so this project uses it by default. The model is fully configurable via GROQ_MODEL in backend/.env (e.g. llama-3.1-8b-instant, Groq's official gemma2 replacement).
Alok Deep — Full-stack developer building toward AI / data roles.
LinkedIn · Portfolio · alokdeep9925@gmail.com
Educational / assignment project. Not affiliated with any pharmaceutical company. Sample HCP data is synthetic.