Priyansh Saxena commited on
Commit
e977d87
Β·
1 Parent(s): fa95b8a

init: project foundation

Browse files
Files changed (4) hide show
  1. .gitignore +31 -0
  2. README.md +265 -0
  3. pytest.ini +3 -0
  4. requirements.txt +7 -0
.gitignore ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```
2
+ # Python
3
+ __pycache__/
4
+ *.pyc
5
+ *.pyo
6
+ *.pyd
7
+
8
+ # Logs and temp files
9
+ *.log
10
+ *.tmp
11
+ *.swp
12
+
13
+ # Environment
14
+ .env
15
+ .env.local
16
+ *.env.*
17
+
18
+ # Editors
19
+ .vscode/
20
+ .idea/
21
+
22
+ # Dependencies
23
+ .venv/
24
+ venv/
25
+ node_modules/
26
+
27
+ # Build artifacts
28
+ dist/
29
+ build/
30
+ target/
31
+ ```
README.md ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Clinical Intake Agent
3
+ emoji: πŸ₯
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # Clinical Intake Agent
11
+
12
+ A LangGraph-based conversational agent for conducting pre-visit clinical intakes with simulated patients. The agent generates a structured ClinicalBrief (Chief Complaint, HPI, ROS) at the end of the conversation.
13
+
14
+ ## Features
15
+
16
+ - **Multi-turn conversation** with stateful memory using LangGraph checkpointing
17
+ - **Structured clinical data collection**: Chief Complaint, HPI (OPQRST), and ROS
18
+ - **Conditional ROS scoping**: Adapts review of systems based on chief complaint
19
+ - **Vague answer handling**: Gracefully re-prompts when patient responses are unclear
20
+ - **Dual mode**: Runs as FastAPI web app OR CLI tool
21
+ - **Mock/Real LLM**: Switch between mock responses and real local LLM via environment variable
22
+
23
+ ## Architecture
24
+
25
+ ```
26
+ intake β†’ hpi β†’ ros β†’ brief_generation β†’ done
27
+ ```
28
+
29
+ ### State Graph (LangGraph TypedDict)
30
+
31
+ ```python
32
+ class IntakeState(TypedDict):
33
+ messages: list[dict] # conversation history
34
+ chief_complaint: str
35
+ hpi: dict # onset, location, duration, character, severity, aggravating, relieving
36
+ ros: dict[str, list[str]] # system -> [positive findings, negative findings]
37
+ current_node: str
38
+ clinical_brief: Optional[ClinicalBrief]
39
+ ros_systems: list[str]
40
+ ros_current_index: int
41
+ ros_pending_system: Optional[str]
42
+ last_processed_message_index: int
43
+ vague_retry_field: Optional[str]
44
+ ```
45
+
46
+ ### Nodes
47
+
48
+ 1. **intake_node**: Greets patient, extracts chief complaint. Moves to hpi when CC is clear.
49
+ 2. **hpi_node**: Asks OPQRST questions one at a time. Re-prompts gracefully on vague answers.
50
+ 3. **ros_node**: CONDITIONAL - scopes ROS systems based on CC (e.g., chest pain β†’ cardiac, respiratory, GI).
51
+ 4. **brief_generator_node**: Generates Pydantic ClinicalBrief from state (no LLM call).
52
+
53
+ ## Installation
54
+
55
+ ### Local Development
56
+
57
+ ```bash
58
+ # Clone repository
59
+ git clone <repo-url>
60
+ cd clinical-intake-agent
61
+
62
+ # Install dependencies
63
+ pip install -r requirements.txt
64
+
65
+ # Run with Mock LLM (default)
66
+ export MOCK_LLM=true
67
+ uvicorn app.main:app --reload
68
+
69
+ # Run with Real LLM (requires model download)
70
+ export MOCK_LLM=false
71
+ uvicorn app.main:app --reload
72
+ ```
73
+
74
+ ### Docker (HuggingFace Spaces)
75
+
76
+ ```bash
77
+ # Build and run locally
78
+ docker build -t clinical-intake-agent .
79
+ docker run -p 7860:7860 -e MOCK_LLM=true clinical-intake-agent
80
+ ```
81
+
82
+ ## Usage
83
+
84
+ ### FastAPI Web App
85
+
86
+ #### Health Check
87
+ ```bash
88
+ curl http://localhost:7860/health
89
+ # Response: {"status": "ok", "mock_mode": true}
90
+ ```
91
+
92
+ #### Chat Endpoint
93
+ ```bash
94
+ # Start conversation
95
+ curl -X POST http://localhost:7860/chat \
96
+ -H "Content-Type: application/json" \
97
+ -d '{"session_id": "patient123", "message": "hello"}'
98
+
99
+ # Continue conversation
100
+ curl -X POST http://localhost:7860/chat \
101
+ -H "Content-Type: application/json" \
102
+ -d '{"session_id": "patient123", "message": "I have chest pain"}'
103
+
104
+ # Final response includes clinical_brief when state == "done"
105
+ ```
106
+
107
+ ### CLI Mode
108
+
109
+ ```bash
110
+ # Run interactive CLI
111
+ python app/main.py --cli
112
+
113
+ # Example session:
114
+ # Agent: Hello! I'm here to help you with your pre-visit intake. What brings you in today?
115
+ # You: I have chest pain since this morning
116
+ # Agent: I understand you're experiencing chest pain. When did it first start?
117
+ # ... (continues through HPI and ROS) ...
118
+ # Agent: Your clinical intake is complete. Here is your summary:
119
+ # {
120
+ # "chief_complaint": "chest pain",
121
+ # "hpi": {...},
122
+ # "ros": {...},
123
+ # "generated_at": "2024-01-15T10:30:00Z"
124
+ # }
125
+ ```
126
+
127
+ ## API Reference
128
+
129
+ ### POST /chat
130
+
131
+ **Request:**
132
+ ```json
133
+ {
134
+ "session_id": "string",
135
+ "message": "string"
136
+ }
137
+ ```
138
+
139
+ **Response:**
140
+ ```json
141
+ {
142
+ "reply": "string",
143
+ "state": "intake|hpi|ros|brief_generation|done",
144
+ "brief": {
145
+ "chief_complaint": "string",
146
+ "hpi": {
147
+ "onset": "string",
148
+ "location": "string",
149
+ "duration": "string",
150
+ "character": "string",
151
+ "severity": "string",
152
+ "aggravating": "string",
153
+ "relieving": "string"
154
+ },
155
+ "ros": {
156
+ "system_name": ["finding1", "finding2"]
157
+ },
158
+ "generated_at": "ISO8601 timestamp"
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### GET /health
164
+
165
+ **Response:**
166
+ ```json
167
+ {
168
+ "status": "ok",
169
+ "mock_mode": true
170
+ }
171
+ ```
172
+
173
+ ## Configuration
174
+
175
+ | Environment Variable | Description | Default |
176
+ |---------------------|-------------|---------|
177
+ | `MOCK_LLM` | Use mock LLM responses (`true`) or real local LLM (`false`) | `true` |
178
+ | `MODEL_PATH` | Path to GGUF model file (used when `MOCK_LLM=false`) | `/models/qwen2.5-0.5b-instruct-q4_k_m.gguf` |
179
+
180
+ ## Testing
181
+
182
+ ```bash
183
+ # Run all tests (uses MockLLM automatically)
184
+ pytest tests/
185
+
186
+ # Run specific test
187
+ pytest tests/test_e2e.py::test_full_intake_flow -v
188
+
189
+ # Run with coverage
190
+ pytest --cov=app tests/
191
+ ```
192
+
193
+ ### Test Coverage
194
+
195
+ - βœ… `test_health_endpoint`: Verifies health check returns mock_mode status
196
+ - βœ… `test_full_intake_flow`: Complete conversation flow from greeting to ClinicalBrief
197
+ - βœ… `test_hpi_reprompt`: Validates vague answer re-prompting behavior
198
+ - βœ… `test_ros_scoping`: Confirms ROS systems are scoped based on chief complaint
199
+ - βœ… `test_brief_structure`: Validates ClinicalBrief Pydantic schema compliance
200
+
201
+ ## Project Structure
202
+
203
+ ```
204
+ clinical-intake-agent/
205
+ β”œβ”€β”€ app/
206
+ β”‚ β”œβ”€β”€ __init__.py
207
+ β”‚ β”œβ”€β”€ main.py # FastAPI app + CLI entry point
208
+ β”‚ β”œβ”€β”€ graph.py # LangGraph state graph and nodes
209
+ β”‚ β”œβ”€β”€ state.py # TypedDict state definitions
210
+ β”‚ β”œβ”€β”€ schemas.py # Pydantic models (HPI, ClinicalBrief)
211
+ β”‚ └── llm.py # LLM provider (MockLLM, RealLLM)
212
+ β”œβ”€β”€ tests/
213
+ β”‚ β”œβ”€β”€ __init__.py
214
+ β”‚ └── test_e2e.py # End-to-end tests
215
+ β”œβ”€β”€ requirements.txt
216
+ β”œβ”€β”€ Dockerfile
217
+ β”œβ”€β”€ README.md
218
+ ```
219
+
220
+ ## Dependencies
221
+
222
+ Minimal dependencies (no heavy ML libraries unless `MOCK_LLM=false`):
223
+
224
+ - `langgraph` - State graph orchestration
225
+ - `fastapi` - Web framework
226
+ - `uvicorn` - ASGI server
227
+ - `pydantic` - Data validation
228
+ - `pytest` + `pytest-asyncio` - Testing
229
+ - `httpx` - Async HTTP client for tests
230
+ - `llama-cpp-python` - Only in Docker prod layer for real LLM mode
231
+
232
+ ## License
233
+
234
+ MIT
235
+
236
+ ## Contributing
237
+
238
+ 1. Fork the repository
239
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
240
+ 3. Commit changes (`git commit -m 'Add amazing feature'`)
241
+ 4. Push to branch (`git push origin feature/amazing-feature`)
242
+ 5. Open a Pull Request
243
+
244
+ ## Troubleshooting
245
+
246
+ ### Model Download Fails
247
+
248
+ If running with `MOCK_LLM=false` and the model fails to download:
249
+
250
+ ```bash
251
+ # Manually download the model
252
+ python -c "from huggingface_hub import hf_hub_download; hf_hub_download('bartowski/Qwen2.5-0.5B-Instruct-GGUF', 'Qwen2.5-0.5B-Instruct-Q4_K_M.gguf', local_dir='/models')"
253
+ ```
254
+
255
+ ### Session State Not Persisting
256
+
257
+ Ensure you're using the same `session_id` across multiple `/chat` calls. Sessions are stored in-memory per process.
258
+
259
+ ### Docker Build Fails
260
+
261
+ The Dockerfile skips model download if `MOCK_LLM=true`. To force model download in Docker:
262
+
263
+ ```bash
264
+ docker build --build-arg MOCK_LLM=false -t clinical-intake-agent .
265
+ ```
pytest.ini ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [pytest]
2
+ asyncio_mode = auto
3
+ asyncio_default_fixture_loop_scope = function
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ langgraph>=0.2.0
2
+ fastapi>=0.115.0
3
+ uvicorn>=0.32.0
4
+ pydantic>=2.9.0
5
+ pytest>=8.3.0
6
+ httpx>=0.27.0
7
+ pytest-asyncio>=0.24.0