refactor: Simplified and streamlined the core demo and overall codebase
Browse files- .claude/README.md +0 -47
- .claude/commands/conversation-task.md +0 -48
- .claude/commands/llm-task.md +0 -53
- .claude/commands/websocket-task.md +0 -44
- .claude/settings.local.json +0 -14
- .env.example +10 -81
- .gitignore +1 -0
- AXIOM_ARCHITECTURE.md +0 -206
- AXIOM_CLAUDE_INTEGRATION.md +0 -171
- AXIOM_IMPLEMENTATION_HISTORY.md +0 -159
- AXIOM_README.md +0 -316
- AXIOM_SETUP.md +0 -208
- AXIOM_WEBSOCKET_ARCHITECTURE.md +0 -285
- PROJECT_STATE.md +0 -246
- README.md +156 -0
- backend/api/{services/conversation_service.py → conversation_service.py} +27 -14
- backend/api/{websockets/conversation_ws.py → conversation_ws.py} +5 -5
- backend/api/main.py +23 -9
- backend/api/{routes/conversations.py → routes.py} +4 -10
- backend/core/conversation_manager.py +1 -1
- backend/core/llm_client.py +36 -5
- backend/core/persona_system.py +3 -3
- config/default_config.yaml +1 -1
- config/settings.py +74 -0
- data/{personas/patient_personas.yaml → patient_personas.yaml} +0 -0
- data/{personas/surveyor_personas.yaml → surveyor_personas.yaml} +0 -0
- docs/README.md +9 -0
- docs/development.md +61 -0
- docs/overview.md +56 -0
- docs/roadmap.md +37 -0
- frontend/gradio_app.py +43 -26
- run_local.sh +105 -0
- scripts/check_setup.py +0 -234
- scripts/run_conversation_demo.py +0 -233
- scripts/test_integration.py +0 -387
- scripts/test_llm_connection.py +0 -317
- scripts/test_websocket.py +0 -250
.claude/README.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
| 1 |
-
# 🤖 .claude Directory - Claude Code Integration
|
| 2 |
-
|
| 3 |
-
This directory contains Claude Code-specific customizations for AI agent development workflow.
|
| 4 |
-
|
| 5 |
-
## Structure
|
| 6 |
-
|
| 7 |
-
```
|
| 8 |
-
.claude/
|
| 9 |
-
├── commands/ # Custom slash commands
|
| 10 |
-
│ ├── websocket-task.md # /websocket-task command
|
| 11 |
-
│ ├── llm-task.md # /llm-task command
|
| 12 |
-
│ └── conversation-task.md # /conversation-task command
|
| 13 |
-
└── settings.local.json # Local Claude Code settings
|
| 14 |
-
```
|
| 15 |
-
|
| 16 |
-
## Usage
|
| 17 |
-
|
| 18 |
-
### ✨ Custom Slash Commands (Primary Method)
|
| 19 |
-
```bash
|
| 20 |
-
# Load complete task context with automatic file loading:
|
| 21 |
-
/websocket-task # WebSocket implementation
|
| 22 |
-
/llm-task # LLM integration
|
| 23 |
-
/conversation-task # Conversation orchestration
|
| 24 |
-
```
|
| 25 |
-
|
| 26 |
-
### 📂 Individual File References
|
| 27 |
-
```bash
|
| 28 |
-
# Load specific files when needed:
|
| 29 |
-
@backend/core/llm_client.py @data/personas/patient_personas.yaml
|
| 30 |
-
|
| 31 |
-
# Load entire directories:
|
| 32 |
-
@backend/models/ @data/personas/
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
## Workflow for AI Agents
|
| 36 |
-
|
| 37 |
-
**Streamlined 3-step process:**
|
| 38 |
-
1. **Context**: `@CLAUDE.md` for project overview
|
| 39 |
-
2. **Task**: `/websocket-task`, `/llm-task`, or `/conversation-task`
|
| 40 |
-
3. **Code**: Begin implementation with full context loaded
|
| 41 |
-
|
| 42 |
-
## Benefits
|
| 43 |
-
|
| 44 |
-
- **All-in-one**: Each slash command provides context + guidance + file loading
|
| 45 |
-
- **Native integration**: Leverages Claude Code's built-in features
|
| 46 |
-
- **No maintenance**: Single source of truth per task
|
| 47 |
-
- **Automatic**: Files load instantly with `@` syntax
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.claude/commands/conversation-task.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
| 1 |
-
# Conversation Orchestration Task
|
| 2 |
-
|
| 3 |
-
Load the necessary context for implementing conversation orchestration logic.
|
| 4 |
-
|
| 5 |
-
## Context to Load
|
| 6 |
-
|
| 7 |
-
@backend/core/conversation_manager.py - Main orchestration logic
|
| 8 |
-
@backend/core/llm_client.py - LLM interface to use
|
| 9 |
-
@data/personas/ - All persona definitions
|
| 10 |
-
@config/personas_config.yaml - Persona structure reference
|
| 11 |
-
@TODO_CONTEXT.md - Full task breakdown
|
| 12 |
-
|
| 13 |
-
## Key Implementation Areas
|
| 14 |
-
|
| 15 |
-
### Files to Create
|
| 16 |
-
1. **backend/core/persona_system.py** - Persona prompt builder
|
| 17 |
-
2. **backend/models/conversation.py** - Conversation data models
|
| 18 |
-
3. **backend/models/message.py** - Message data models
|
| 19 |
-
|
| 20 |
-
### PersonaSystem Class Structure
|
| 21 |
-
```python
|
| 22 |
-
class PersonaSystem:
|
| 23 |
-
def build_prompt(self, persona: dict, history: List[dict], context: dict) -> str:
|
| 24 |
-
"""Build complete prompt including persona, history, and context."""
|
| 25 |
-
|
| 26 |
-
def extract_persona_traits(self, persona_id: str) -> dict:
|
| 27 |
-
"""Extract behavioral traits from persona definition."""
|
| 28 |
-
|
| 29 |
-
def format_conversation_history(self, messages: List[dict]) -> str:
|
| 30 |
-
"""Format message history for inclusion in prompt."""
|
| 31 |
-
```
|
| 32 |
-
|
| 33 |
-
### Conversation Flow Logic
|
| 34 |
-
1. Surveyor initiates with greeting
|
| 35 |
-
2. Patient responds based on persona
|
| 36 |
-
3. Surveyor asks follow-up questions
|
| 37 |
-
4. Continue until end conditions met
|
| 38 |
-
|
| 39 |
-
### Persona Behavior Implementation
|
| 40 |
-
- **cooperative_senior**: Add delays, ask for clarification
|
| 41 |
-
- **anxious_parent**: Question motives, seek reassurance
|
| 42 |
-
- **busy_professional**: Keep responses brief, mention time
|
| 43 |
-
|
| 44 |
-
## Success Criteria
|
| 45 |
-
- Two AIs can have coherent conversation
|
| 46 |
-
- Personas behave according to definitions
|
| 47 |
-
- Conversation flows naturally
|
| 48 |
-
- History is maintained correctly
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.claude/commands/llm-task.md
DELETED
|
@@ -1,53 +0,0 @@
|
|
| 1 |
-
# LLM Integration Task
|
| 2 |
-
|
| 3 |
-
Load the necessary context for implementing Ollama LLM integration in the AI Survey Simulator.
|
| 4 |
-
|
| 5 |
-
## Context to Load
|
| 6 |
-
|
| 7 |
-
@backend/core/llm_client.py - Implement OllamaClient.generate()
|
| 8 |
-
@config/personas_config.yaml - System prompts for personas
|
| 9 |
-
@.env.example - LLM configuration examples
|
| 10 |
-
@data/personas/ - All persona definitions
|
| 11 |
-
@TODO_CONTEXT.md - Full task breakdown
|
| 12 |
-
|
| 13 |
-
## Implementation Focus
|
| 14 |
-
|
| 15 |
-
### Complete OllamaClient.generate() Method
|
| 16 |
-
```python
|
| 17 |
-
async def generate(self, prompt: str, system_prompt: Optional[str] = None, **kwargs) -> str:
|
| 18 |
-
messages = []
|
| 19 |
-
if system_prompt:
|
| 20 |
-
messages.append({"role": "system", "content": system_prompt})
|
| 21 |
-
messages.append({"role": "user", "content": prompt})
|
| 22 |
-
|
| 23 |
-
payload = {
|
| 24 |
-
"model": self.model,
|
| 25 |
-
"messages": messages,
|
| 26 |
-
"stream": False,
|
| 27 |
-
**kwargs
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
response = await self.client.post(f"{self.host}/api/chat", json=payload)
|
| 31 |
-
# Handle response and errors
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
## Test Setup Commands
|
| 35 |
-
```bash
|
| 36 |
-
# Install Ollama
|
| 37 |
-
curl -fsSL https://ollama.ai/install.sh | sh
|
| 38 |
-
|
| 39 |
-
# Pull model
|
| 40 |
-
ollama pull llama2:13b
|
| 41 |
-
|
| 42 |
-
# Test connection
|
| 43 |
-
curl http://localhost:11434/api/tags
|
| 44 |
-
```
|
| 45 |
-
|
| 46 |
-
## Files to Create
|
| 47 |
-
1. **scripts/test_llm_connection.py** - Test script for LLM connectivity
|
| 48 |
-
|
| 49 |
-
## Success Criteria
|
| 50 |
-
- Can call Ollama and get responses
|
| 51 |
-
- System prompts correctly formatted
|
| 52 |
-
- Errors handled gracefully
|
| 53 |
-
- Test script validates connection
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.claude/commands/websocket-task.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
| 1 |
-
# WebSocket Implementation Task
|
| 2 |
-
|
| 3 |
-
Load the necessary context for implementing WebSocket functionality in the AI Survey Simulator.
|
| 4 |
-
|
| 5 |
-
## Context to Load
|
| 6 |
-
|
| 7 |
-
Please load these files to understand the task:
|
| 8 |
-
|
| 9 |
-
@backend/api/main.py - Add WebSocket endpoint here
|
| 10 |
-
@config/default_config.yaml - WebSocket configuration section
|
| 11 |
-
@frontend/streamlit_app.py - Frontend that needs WebSocket client
|
| 12 |
-
@TODO_CONTEXT.md - Full task breakdown
|
| 13 |
-
|
| 14 |
-
## Files to Create
|
| 15 |
-
|
| 16 |
-
1. **backend/api/websockets/conversation_ws.py** - WebSocket endpoint implementation
|
| 17 |
-
2. **frontend/utils/websocket_client.py** - Frontend WebSocket client
|
| 18 |
-
|
| 19 |
-
## Implementation Notes
|
| 20 |
-
|
| 21 |
-
### WebSocket Endpoint Pattern
|
| 22 |
-
```python
|
| 23 |
-
@app.websocket("/ws/conversation/{conversation_id}")
|
| 24 |
-
async def websocket_endpoint(websocket: WebSocket, conversation_id: str):
|
| 25 |
-
await websocket.accept()
|
| 26 |
-
# Handle messages
|
| 27 |
-
```
|
| 28 |
-
|
| 29 |
-
### Message Format
|
| 30 |
-
```json
|
| 31 |
-
{
|
| 32 |
-
"type": "conversation_message",
|
| 33 |
-
"role": "surveyor" | "patient",
|
| 34 |
-
"content": "message text",
|
| 35 |
-
"timestamp": "2024-01-01T12:00:00Z",
|
| 36 |
-
"conversation_id": "uuid"
|
| 37 |
-
}
|
| 38 |
-
```
|
| 39 |
-
|
| 40 |
-
## Success Criteria
|
| 41 |
-
- Frontend connects to backend WebSocket
|
| 42 |
-
- Messages flow bidirectionally
|
| 43 |
-
- Connection recovers from disconnects
|
| 44 |
-
- Status indicator shows connection state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.claude/settings.local.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"permissions": {
|
| 3 |
-
"allow": [
|
| 4 |
-
"Bash(mkdir:*)",
|
| 5 |
-
"Bash(ls:*)",
|
| 6 |
-
"Bash(mv:*)",
|
| 7 |
-
"Bash(true)",
|
| 8 |
-
"Bash(cp:*)",
|
| 9 |
-
"Bash(find:*)",
|
| 10 |
-
"WebFetch(domain:docs.anthropic.com)"
|
| 11 |
-
],
|
| 12 |
-
"deny": []
|
| 13 |
-
}
|
| 14 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.env.example
CHANGED
|
@@ -1,87 +1,16 @@
|
|
| 1 |
-
#
|
| 2 |
-
# Copy this file to .env and update with your settings
|
| 3 |
-
|
| 4 |
-
# Application Settings
|
| 5 |
-
APP_NAME="AI Survey Simulator"
|
| 6 |
-
APP_ENV=development
|
| 7 |
-
DEBUG=true
|
| 8 |
-
LOG_LEVEL=INFO
|
| 9 |
-
|
| 10 |
-
# API Server Configuration
|
| 11 |
API_HOST=0.0.0.0
|
| 12 |
API_PORT=8000
|
| 13 |
-
API_WORKERS=1
|
| 14 |
-
API_RELOAD=true
|
| 15 |
-
|
| 16 |
-
# Frontend Configuration
|
| 17 |
-
STREAMLIT_SERVER_PORT=8501
|
| 18 |
-
STREAMLIT_SERVER_ADDRESS=localhost
|
| 19 |
|
| 20 |
-
# LLM
|
| 21 |
-
# Choose one: ollama, vllm, openai
|
| 22 |
LLM_BACKEND=ollama
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
OLLAMA_TIMEOUT=120
|
| 28 |
-
|
| 29 |
-
# vLLM Configuration (alternative to Ollama)
|
| 30 |
-
# VLLM_HOST=http://localhost:8000
|
| 31 |
-
# VLLM_MODEL=meta-llama/Llama-2-13b-chat-hf
|
| 32 |
-
# VLLM_TIMEOUT=120
|
| 33 |
-
|
| 34 |
-
# OpenAI Configuration (for testing/comparison)
|
| 35 |
-
# OPENAI_API_KEY=your-api-key-here
|
| 36 |
-
# OPENAI_MODEL=gpt-3.5-turbo
|
| 37 |
-
|
| 38 |
-
# Model Parameters
|
| 39 |
-
LLM_TEMPERATURE=0.7
|
| 40 |
-
LLM_MAX_TOKENS=2048
|
| 41 |
-
LLM_TOP_P=0.9
|
| 42 |
-
|
| 43 |
-
# Database Configuration
|
| 44 |
-
DATABASE_TYPE=sqlite
|
| 45 |
-
DATABASE_URL=sqlite:///./data/conversations.db
|
| 46 |
|
| 47 |
-
#
|
| 48 |
-
|
| 49 |
-
# POSTGRES_HOST=localhost
|
| 50 |
-
# POSTGRES_PORT=5432
|
| 51 |
-
# POSTGRES_DB=ai_survey_simulator
|
| 52 |
-
# POSTGRES_USER=postgres
|
| 53 |
-
# POSTGRES_PASSWORD=your-password
|
| 54 |
-
|
| 55 |
-
# WebSocket Configuration
|
| 56 |
-
WS_PING_INTERVAL=10
|
| 57 |
-
WS_PING_TIMEOUT=5
|
| 58 |
-
WS_MAX_MESSAGE_SIZE=1048576
|
| 59 |
-
|
| 60 |
-
# Security Settings
|
| 61 |
-
# Set to true in production
|
| 62 |
-
ENABLE_AUTH=false
|
| 63 |
-
API_KEY=your-secure-api-key-here
|
| 64 |
-
SECRET_KEY=your-secret-key-for-sessions
|
| 65 |
-
ALLOWED_HOSTS=localhost,127.0.0.1
|
| 66 |
-
|
| 67 |
-
# CORS Settings
|
| 68 |
-
CORS_ORIGINS=http://localhost:8501,http://localhost:3000
|
| 69 |
-
|
| 70 |
-
# Performance Settings
|
| 71 |
-
CACHE_ENABLED=true
|
| 72 |
-
CACHE_TTL=3600
|
| 73 |
-
MAX_CONCURRENT_CONVERSATIONS=10
|
| 74 |
-
CONVERSATION_QUEUE_SIZE=100
|
| 75 |
-
|
| 76 |
-
# File Storage
|
| 77 |
-
LOG_FILE_PATH=./logs/app.log
|
| 78 |
-
LOG_FILE_MAX_SIZE=10MB
|
| 79 |
-
LOG_FILE_BACKUP_COUNT=5
|
| 80 |
-
|
| 81 |
-
# Monitoring (Optional)
|
| 82 |
-
ENABLE_METRICS=false
|
| 83 |
-
METRICS_PORT=9090
|
| 84 |
-
|
| 85 |
-
# Development Settings
|
| 86 |
-
AUTO_RELOAD_PERSONAS=true
|
| 87 |
-
MOCK_LLM_RESPONSES=false
|
|
|
|
| 1 |
+
# API configuration
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
API_HOST=0.0.0.0
|
| 3 |
API_PORT=8000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
# LLM backend configuration
|
|
|
|
| 6 |
LLM_BACKEND=ollama
|
| 7 |
+
LLM_HOST=http://localhost:11434
|
| 8 |
+
LLM_MODEL=llama3.2:latest
|
| 9 |
+
LLM_TIMEOUT=120
|
| 10 |
|
| 11 |
+
# Frontend configuration
|
| 12 |
+
FRONTEND_BACKEND_BASE_URL=http://localhost:8000
|
| 13 |
+
FRONTEND_WEBSOCKET_URL=ws://localhost:8000/ws/conversation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
# Logging
|
| 16 |
+
LOG_LEVEL=INFO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
*.pyc
|
|
|
|
|
|
| 1 |
*.pyc
|
| 2 |
+
.env
|
AXIOM_ARCHITECTURE.md
DELETED
|
@@ -1,206 +0,0 @@
|
|
| 1 |
-
# 🏗️ Architecture Decision Records (ADRs)
|
| 2 |
-
|
| 3 |
-
> **Purpose**: Document significant architectural decisions for AI agents and developers.
|
| 4 |
-
> Each decision includes context, options considered, decision made, and consequences.
|
| 5 |
-
|
| 6 |
-
## ADR Template
|
| 7 |
-
```markdown
|
| 8 |
-
### ADR-XXX: [Decision Title]
|
| 9 |
-
**Date**: YYYY-MM-DD
|
| 10 |
-
**Status**: Accepted/Deprecated/Superseded
|
| 11 |
-
**Context**: Why this decision was needed
|
| 12 |
-
**Decision**: What we decided
|
| 13 |
-
**Consequences**: What happens as a result
|
| 14 |
-
**Alternatives**: What else was considered
|
| 15 |
-
```
|
| 16 |
-
|
| 17 |
-
---
|
| 18 |
-
|
| 19 |
-
## ADR-001: Use Ollama for MVP LLM Backend
|
| 20 |
-
**Date**: 2025-08-22
|
| 21 |
-
**Status**: Accepted
|
| 22 |
-
**Context**: Need local LLM hosting for privacy and cost control in research environment
|
| 23 |
-
**Decision**: Use Ollama as primary LLM backend for MVP
|
| 24 |
-
**Consequences**:
|
| 25 |
-
- ✅ Simple setup with one-line install
|
| 26 |
-
- ✅ No API costs
|
| 27 |
-
- ✅ Full data privacy
|
| 28 |
-
- ❌ Limited to models Ollama supports
|
| 29 |
-
- ❌ Requires local GPU resources
|
| 30 |
-
|
| 31 |
-
**Alternatives Considered**:
|
| 32 |
-
- vLLM: More performant but complex setup
|
| 33 |
-
- OpenAI API: Easy but costs and privacy concerns
|
| 34 |
-
- Hugging Face local: More setup complexity
|
| 35 |
-
|
| 36 |
-
---
|
| 37 |
-
|
| 38 |
-
## ADR-002: FastAPI + Gradio Architecture
|
| 39 |
-
**Date**: 2025-09-18 (Updated from original Streamlit decision)
|
| 40 |
-
**Status**: Accepted
|
| 41 |
-
**Context**: Need rapid development with real-time chat features and WebSocket integration
|
| 42 |
-
**Decision**: FastAPI backend with Gradio frontend
|
| 43 |
-
**Consequences**:
|
| 44 |
-
- ✅ Fast development velocity
|
| 45 |
-
- ✅ Built-in WebSocket support
|
| 46 |
-
- ✅ Auto-generated API docs
|
| 47 |
-
- ✅ Native chat components (`gr.Chatbot()`)
|
| 48 |
-
- ✅ Better real-time streaming support
|
| 49 |
-
- ✅ Cleaner async/await integration
|
| 50 |
-
- ✅ No full page reruns on updates
|
| 51 |
-
- ❌ Different component ecosystem than Streamlit
|
| 52 |
-
- ❌ Not ideal for production scaling
|
| 53 |
-
|
| 54 |
-
**Alternatives Considered**:
|
| 55 |
-
- Streamlit: Good for research but limitations for real-time chat
|
| 56 |
-
- Flask + React: More control but slower development
|
| 57 |
-
- Django + templates: Too heavyweight for MVP
|
| 58 |
-
- Pure FastAPI with Jinja: Limited interactivity
|
| 59 |
-
|
| 60 |
-
---
|
| 61 |
-
|
| 62 |
-
## ADR-003: SQLite for MVP Database
|
| 63 |
-
**Date**: 2025-08-22
|
| 64 |
-
**Status**: Accepted
|
| 65 |
-
**Context**: Need simple persistence without setup complexity
|
| 66 |
-
**Decision**: Use SQLite for MVP, design for PostgreSQL migration
|
| 67 |
-
**Consequences**:
|
| 68 |
-
- ✅ Zero configuration required
|
| 69 |
-
- ✅ Single file database
|
| 70 |
-
- ✅ Good enough for research scale
|
| 71 |
-
- ✅ Easy to backup/share
|
| 72 |
-
- ❌ Limited concurrent writes
|
| 73 |
-
- ❌ No native JSON queries
|
| 74 |
-
|
| 75 |
-
**Migration Path**:
|
| 76 |
-
- Use SQLAlchemy ORM for abstraction
|
| 77 |
-
- Keep database logic isolated
|
| 78 |
-
- Document PostgreSQL migration steps
|
| 79 |
-
|
| 80 |
-
---
|
| 81 |
-
|
| 82 |
-
## ADR-004: YAML for Persona Configuration
|
| 83 |
-
**Date**: 2025-08-22
|
| 84 |
-
**Status**: Accepted
|
| 85 |
-
**Context**: Researchers need to edit personas without coding
|
| 86 |
-
**Decision**: Store personas as YAML files
|
| 87 |
-
**Consequences**:
|
| 88 |
-
- ✅ Human-readable and editable
|
| 89 |
-
- ✅ Git-friendly for tracking changes
|
| 90 |
-
- ✅ Comments for documentation
|
| 91 |
-
- ✅ Hierarchical structure support
|
| 92 |
-
- ❌ No schema validation (without extras)
|
| 93 |
-
- ❌ Potential for syntax errors
|
| 94 |
-
|
| 95 |
-
**Alternatives Considered**:
|
| 96 |
-
- JSON: Less readable, no comments
|
| 97 |
-
- Python files: Too technical for researchers
|
| 98 |
-
- Database: Harder to version control
|
| 99 |
-
|
| 100 |
-
---
|
| 101 |
-
|
| 102 |
-
## ADR-005: WebSocket for Real-time Communication
|
| 103 |
-
**Date**: 2025-08-22
|
| 104 |
-
**Status**: Accepted
|
| 105 |
-
**Context**: Need real-time conversation streaming
|
| 106 |
-
**Decision**: Use WebSocket protocol for live updates
|
| 107 |
-
**Consequences**:
|
| 108 |
-
- ✅ True real-time bidirectional communication
|
| 109 |
-
- ✅ Lower latency than polling
|
| 110 |
-
- ✅ Native FastAPI support
|
| 111 |
-
- ❌ More complex than REST
|
| 112 |
-
- ❌ Connection management needed
|
| 113 |
-
- ❌ Reconnection logic required
|
| 114 |
-
|
| 115 |
-
**Implementation Notes**:
|
| 116 |
-
- JSON message protocol
|
| 117 |
-
- Heartbeat for connection health
|
| 118 |
-
- Automatic reconnection in frontend
|
| 119 |
-
|
| 120 |
-
---
|
| 121 |
-
|
| 122 |
-
## ADR-006: Monolithic Repository Structure
|
| 123 |
-
**Date**: 2025-08-22
|
| 124 |
-
**Status**: Accepted
|
| 125 |
-
**Context**: MVP development speed and AI agent context
|
| 126 |
-
**Decision**: Keep all code in single repository
|
| 127 |
-
**Consequences**:
|
| 128 |
-
- ✅ Easier for AI agents to understand
|
| 129 |
-
- ✅ Simpler development workflow
|
| 130 |
-
- ✅ All context in one place
|
| 131 |
-
- ❌ Harder to scale teams later
|
| 132 |
-
- ❌ Frontend/backend coupling
|
| 133 |
-
|
| 134 |
-
**Future Considerations**:
|
| 135 |
-
- Clear module boundaries for future split
|
| 136 |
-
- API-first design enables separation
|
| 137 |
-
- Document splitting strategy
|
| 138 |
-
|
| 139 |
-
---
|
| 140 |
-
|
| 141 |
-
## ADR-007: AI-First Documentation
|
| 142 |
-
**Date**: 2025-08-22
|
| 143 |
-
**Status**: Accepted
|
| 144 |
-
**Context**: Development primarily by AI agents
|
| 145 |
-
**Decision**: Optimize all documentation for AI consumption
|
| 146 |
-
**Consequences**:
|
| 147 |
-
- ✅ Faster AI agent onboarding
|
| 148 |
-
- ✅ Better context management
|
| 149 |
-
- ✅ Consistent development patterns
|
| 150 |
-
- ✅ Self-documenting approach
|
| 151 |
-
|
| 152 |
-
**Key Principles**:
|
| 153 |
-
- CLAUDE.md as central entry point
|
| 154 |
-
- Context packages in .claude/
|
| 155 |
-
- Detailed docstrings everywhere
|
| 156 |
-
- Clear file navigation paths
|
| 157 |
-
|
| 158 |
-
---
|
| 159 |
-
|
| 160 |
-
## ADR-008: Configuration-Driven Design
|
| 161 |
-
**Date**: 2025-08-22
|
| 162 |
-
**Status**: Accepted
|
| 163 |
-
**Context**: Research tool needs flexibility
|
| 164 |
-
**Decision**: Externalize all configuration
|
| 165 |
-
**Consequences**:
|
| 166 |
-
- ✅ No code changes for experiments
|
| 167 |
-
- ✅ Easy A/B testing
|
| 168 |
-
- ✅ Shareable configurations
|
| 169 |
-
- ❌ More files to manage
|
| 170 |
-
- ❌ Configuration validation needed
|
| 171 |
-
|
| 172 |
-
**Configuration Hierarchy**:
|
| 173 |
-
1. Environment variables (secrets)
|
| 174 |
-
2. YAML config files (settings)
|
| 175 |
-
3. Default values in code
|
| 176 |
-
|
| 177 |
-
---
|
| 178 |
-
|
| 179 |
-
## Future Decision Points
|
| 180 |
-
|
| 181 |
-
### Pending Decisions:
|
| 182 |
-
1. **Streaming vs Batch LLM Responses**
|
| 183 |
-
- Consider: UX vs simplicity
|
| 184 |
-
- Impact: WebSocket message protocol
|
| 185 |
-
|
| 186 |
-
2. **Multi-conversation Support**
|
| 187 |
-
- Consider: Concurrent conversations
|
| 188 |
-
- Impact: Resource management
|
| 189 |
-
|
| 190 |
-
3. **Persona Fine-tuning**
|
| 191 |
-
- Consider: Learning from conversations
|
| 192 |
-
- Impact: Model management complexity
|
| 193 |
-
|
| 194 |
-
4. **Export Format Standards**
|
| 195 |
-
- Consider: Research tool compatibility
|
| 196 |
-
- Impact: Data pipeline design
|
| 197 |
-
|
| 198 |
-
### Review Triggers:
|
| 199 |
-
- When moving beyond MVP
|
| 200 |
-
- If performance becomes issue
|
| 201 |
-
- When adding team members
|
| 202 |
-
- For production deployment
|
| 203 |
-
|
| 204 |
-
---
|
| 205 |
-
|
| 206 |
-
**Note for AI Agents**: Always check if decisions here conflict with your implementation approach. If so, document why you're deviating or propose an ADR update.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AXIOM_CLAUDE_INTEGRATION.md
DELETED
|
@@ -1,171 +0,0 @@
|
|
| 1 |
-
# 🤖 AI Agent Context Guide - AI Survey Simulator
|
| 2 |
-
|
| 3 |
-
> **Purpose**: Static reference guide for AI agents working on this project.
|
| 4 |
-
> For current project state, see `PROJECT_STATE.md`.
|
| 5 |
-
|
| 6 |
-
## 🎯 Project Overview
|
| 7 |
-
|
| 8 |
-
**What**: AI-to-AI healthcare survey research platform
|
| 9 |
-
**Purpose**: Enable simulated conversations between AI surveyors and patient personas
|
| 10 |
-
**Tech Stack**: FastAPI backend, Streamlit frontend, Ollama/vLLM for LLMs
|
| 11 |
-
|
| 12 |
-
## 🗺️ Critical Files Navigation
|
| 13 |
-
|
| 14 |
-
### For Understanding Architecture
|
| 15 |
-
```bash
|
| 16 |
-
# Read these files in order:
|
| 17 |
-
1. AXIOM_README.md # Developer overview and setup
|
| 18 |
-
2. config/default_config.yaml # System configuration
|
| 19 |
-
3. backend/api/main.py # API entry point
|
| 20 |
-
4. backend/core/conversation_manager.py # Core logic structure
|
| 21 |
-
```
|
| 22 |
-
|
| 23 |
-
### For Persona System
|
| 24 |
-
```bash
|
| 25 |
-
# Essential persona files:
|
| 26 |
-
1. config/personas_config.yaml # Persona template structure
|
| 27 |
-
2. data/personas/patient_personas.yaml # Patient definitions
|
| 28 |
-
3. data/personas/surveyor_personas.yaml # Surveyor definitions
|
| 29 |
-
```
|
| 30 |
-
|
| 31 |
-
### For Implementation Work
|
| 32 |
-
```bash
|
| 33 |
-
# Key implementation files:
|
| 34 |
-
1. backend/core/llm_client.py # LLM integration point
|
| 35 |
-
2. frontend/streamlit_app.py # UI implementation
|
| 36 |
-
3. requirements.txt # Dependencies
|
| 37 |
-
```
|
| 38 |
-
|
| 39 |
-
## 🧩 Native Claude Code Workflow
|
| 40 |
-
|
| 41 |
-
### 📦 Custom Slash Commands
|
| 42 |
-
|
| 43 |
-
Use Claude Code's native slash commands for task-specific context loading:
|
| 44 |
-
|
| 45 |
-
- `/websocket-task` - Load WebSocket implementation context
|
| 46 |
-
- `/llm-task` - Load LLM integration context
|
| 47 |
-
- `/conversation-task` - Load conversation orchestration context
|
| 48 |
-
|
| 49 |
-
These commands automatically load the right files and provide implementation guidance.
|
| 50 |
-
|
| 51 |
-
### 📂 File References
|
| 52 |
-
|
| 53 |
-
Use Claude Code's `@` syntax for quick file loading:
|
| 54 |
-
- `@backend/api/main.py` - Main FastAPI app
|
| 55 |
-
- `@config/default_config.yaml` - System configuration
|
| 56 |
-
- `@data/personas/` - All persona definitions
|
| 57 |
-
- `@PROJECT_STATE.md` - Current project status
|
| 58 |
-
|
| 59 |
-
### 🔗 Context Loading Examples
|
| 60 |
-
```
|
| 61 |
-
# Load all WebSocket-related context:
|
| 62 |
-
/websocket-task
|
| 63 |
-
|
| 64 |
-
# Load specific files:
|
| 65 |
-
@backend/core/llm_client.py @data/personas/patient_personas.yaml
|
| 66 |
-
|
| 67 |
-
# Load directory:
|
| 68 |
-
@backend/models/
|
| 69 |
-
```
|
| 70 |
-
|
| 71 |
-
## 🔧 AI Agent Commands
|
| 72 |
-
|
| 73 |
-
### Quick Context Commands
|
| 74 |
-
```bash
|
| 75 |
-
# View project structure
|
| 76 |
-
find . -type f -name "*.py" | grep -E "(api|core|frontend)" | head -20
|
| 77 |
-
|
| 78 |
-
# Check current TODOs in code
|
| 79 |
-
grep -r "TODO" --include="*.py" .
|
| 80 |
-
|
| 81 |
-
# See configuration structure
|
| 82 |
-
ls -la config/ data/personas/
|
| 83 |
-
|
| 84 |
-
# Understand dependencies
|
| 85 |
-
head -50 requirements.txt
|
| 86 |
-
```
|
| 87 |
-
|
| 88 |
-
### Development Patterns
|
| 89 |
-
1. **Before Adding Features**: Always check existing patterns in similar files
|
| 90 |
-
2. **Configuration First**: Add new settings to config files before hardcoding
|
| 91 |
-
3. **Docstring Discipline**: Every new function needs a docstring
|
| 92 |
-
4. **Type Hints**: Use type hints for all function parameters
|
| 93 |
-
5. **Async by Default**: Backend should be async-first
|
| 94 |
-
|
| 95 |
-
## 📝 Architecture Decisions (Reference)
|
| 96 |
-
|
| 97 |
-
**Major Decisions Made**:
|
| 98 |
-
1. **Ollama for MVP**: Simpler than vLLM for initial development
|
| 99 |
-
2. **SQLite for MVP**: Easy setup, upgradeable to PostgreSQL
|
| 100 |
-
3. **Streamlit UI**: Rapid prototyping, good for research tools
|
| 101 |
-
4. **YAML for Personas**: Human-readable, easy to modify
|
| 102 |
-
|
| 103 |
-
## 🚨 Important Context Rules
|
| 104 |
-
|
| 105 |
-
### Always Remember:
|
| 106 |
-
1. **This is a research tool** - Not for production healthcare
|
| 107 |
-
2. **Privacy conscious** - No real patient data
|
| 108 |
-
3. **Modular design** - Each component should be replaceable
|
| 109 |
-
4. **AI-first documentation** - Write docs for AI agents to understand
|
| 110 |
-
|
| 111 |
-
### Never Do:
|
| 112 |
-
1. Don't hardcode configuration values
|
| 113 |
-
2. Don't create files without docstrings
|
| 114 |
-
3. Don't skip error handling in async code
|
| 115 |
-
4. Don't mix UI logic with business logic
|
| 116 |
-
|
| 117 |
-
## 🔄 AI Agent Resumption Workflow
|
| 118 |
-
|
| 119 |
-
**For new AI agent instances starting work:**
|
| 120 |
-
|
| 121 |
-
### **Step 1: Load Project Context**
|
| 122 |
-
```bash
|
| 123 |
-
@PROJECT_STATE.md # Current status and next steps
|
| 124 |
-
@AXIOM_CLAUDE_INTEGRATION.md # This file - static reference
|
| 125 |
-
```
|
| 126 |
-
|
| 127 |
-
### **Step 2: Verify Environment**
|
| 128 |
-
```bash
|
| 129 |
-
python scripts/check_setup.py # Quick environment verification
|
| 130 |
-
```
|
| 131 |
-
|
| 132 |
-
### **Step 3: Choose Task Based on Status**
|
| 133 |
-
```bash
|
| 134 |
-
/conversation-task # Next priority - conversation orchestration
|
| 135 |
-
/websocket-task # If WebSocket needs work
|
| 136 |
-
/llm-task # If LLM needs work
|
| 137 |
-
```
|
| 138 |
-
|
| 139 |
-
### **Step 4: Test Current State**
|
| 140 |
-
```bash
|
| 141 |
-
python scripts/test_integration.py # Test all working components
|
| 142 |
-
```
|
| 143 |
-
|
| 144 |
-
### **Step 5: Begin Implementation**
|
| 145 |
-
Use task context to guide implementation.
|
| 146 |
-
|
| 147 |
-
## 💡 Meta-Tips for Claude Code AI Agents
|
| 148 |
-
|
| 149 |
-
1. **Leverage native features**: Use `@` references and slash commands
|
| 150 |
-
2. **Use TodoWrite tool** frequently to track progress
|
| 151 |
-
3. **Use `--continue` or `--resume`** to maintain context across sessions
|
| 152 |
-
4. **Read before writing** - Always check existing patterns
|
| 153 |
-
5. **Document decisions** in `PROJECT_STATE.md`
|
| 154 |
-
|
| 155 |
-
## 🚀 Quick Start for Next Session
|
| 156 |
-
|
| 157 |
-
```bash
|
| 158 |
-
# 1. Load current status
|
| 159 |
-
@PROJECT_STATE.md
|
| 160 |
-
|
| 161 |
-
# 2. Choose your task and load context
|
| 162 |
-
/websocket-task # OR
|
| 163 |
-
/llm-task # OR
|
| 164 |
-
/conversation-task
|
| 165 |
-
|
| 166 |
-
# 3. Begin implementation
|
| 167 |
-
```
|
| 168 |
-
|
| 169 |
-
---
|
| 170 |
-
|
| 171 |
-
**Remember**: You're building a tool for other researchers. Keep it simple, well-documented, and extensible.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AXIOM_IMPLEMENTATION_HISTORY.md
DELETED
|
@@ -1,159 +0,0 @@
|
|
| 1 |
-
# 📚 Implementation History Archive
|
| 2 |
-
|
| 3 |
-
> **Purpose**: Historical record of completed implementations and test results.
|
| 4 |
-
> For current active development, see `PROJECT_STATE.md`.
|
| 5 |
-
|
| 6 |
-
---
|
| 7 |
-
|
| 8 |
-
## 🏆 **Foundation Phase Complete** (August-September 2025)
|
| 9 |
-
|
| 10 |
-
### **Integration Test Results** (2025-09-16)
|
| 11 |
-
**Test Command**: `python scripts/test_integration.py`
|
| 12 |
-
**Result**: 🎉 **7/7 tests passed**
|
| 13 |
-
|
| 14 |
-
#### **Components Successfully Verified:**
|
| 15 |
-
- ✅ **Persona System**: 5 personas loaded (cooperative_senior_001, anxious_parent_001, busy_professional_001, + 2 surveyors)
|
| 16 |
-
- ✅ **LLM Client**: Full connectivity to Ollama with llama2:7b model
|
| 17 |
-
- ✅ **Basic Persona Response**: Realistic character responses with personality
|
| 18 |
-
- ✅ **Surveyor Persona**: Dr. Sarah Mitchell professional introduction working
|
| 19 |
-
- ✅ **Patient Persona**: Jennifer Chen health rating responses with character behavior
|
| 20 |
-
- ✅ **Multi-turn Conversation**: History-aware conversation capability
|
| 21 |
-
- ✅ **Configuration Loading**: All YAML and environment config operational
|
| 22 |
-
|
| 23 |
-
#### **Quality Assessment - Sample Responses:**
|
| 24 |
-
```
|
| 25 |
-
Margaret Thompson (patient):
|
| 26 |
-
"Oh, goodness me! *adjusts glasses* Well, you see, as a retired teacher with Type..."
|
| 27 |
-
|
| 28 |
-
Dr. Sarah Mitchell (surveyor):
|
| 29 |
-
"Hello there! My name is Dr. Sarah Mitchell, and I am a senior healthcare survey specialist with over 15 years of experie..."
|
| 30 |
-
|
| 31 |
-
Jennifer Chen (anxious parent):
|
| 32 |
-
"*looks up from her phone* Umm... let me see... *pauses* I would say my child's health is a..."
|
| 33 |
-
```
|
| 34 |
-
**Assessment**: Personas demonstrate authentic personality traits, healthcare context, and realistic behavioral patterns suitable for research use.
|
| 35 |
-
|
| 36 |
-
---
|
| 37 |
-
|
| 38 |
-
## 🔧 **Technical Implementation Details** (Completed)
|
| 39 |
-
|
| 40 |
-
### **LLM Integration System** ✅
|
| 41 |
-
- **Ollama Client**: Retry logic, error handling, performance tracking
|
| 42 |
-
- **Persona System**: YAML-based with prompt building and behavior modifiers
|
| 43 |
-
- **Configuration**: File-based + environment variable support
|
| 44 |
-
- **Health Checks**: Connectivity and model availability testing
|
| 45 |
-
- **Models Verified**: llama2:7b, gemma3:4b, gpt-oss:20b available
|
| 46 |
-
|
| 47 |
-
### **WebSocket Infrastructure** ✅
|
| 48 |
-
- **Backend Endpoint**: Connection management with heartbeat
|
| 49 |
-
- **Frontend Client**: Reconnection logic for production reliability
|
| 50 |
-
- **Message Validation**: Strict format validation prevents errors
|
| 51 |
-
- **Streamlit Integration**: Session state storage for UI updates
|
| 52 |
-
- **Error Handling**: Graceful disconnection and recovery
|
| 53 |
-
|
| 54 |
-
### **Project Foundation** ✅
|
| 55 |
-
- **Directory Structure**: Complete with 11 Python files
|
| 56 |
-
- **Configuration System**: YAML configs + .env support operational
|
| 57 |
-
- **Environment**: Python 3.9.23 in conda environment 'converai'
|
| 58 |
-
- **Hardware**: RTX 3080 GPU verified, adequate for 13B models
|
| 59 |
-
- **Dependencies**: All requirements.txt packages working
|
| 60 |
-
|
| 61 |
-
---
|
| 62 |
-
|
| 63 |
-
## 🧪 **Environment Verification** (Completed)
|
| 64 |
-
|
| 65 |
-
### **System Setup Results:**
|
| 66 |
-
```bash
|
| 67 |
-
# Working Commands (Historical Record):
|
| 68 |
-
python scripts/test_integration.py # 7/7 tests pass
|
| 69 |
-
python scripts/test_llm_connection.py # Full LLM connectivity
|
| 70 |
-
python scripts/test_websocket.py # WebSocket management verified
|
| 71 |
-
python scripts/check_setup.py # Environment verified
|
| 72 |
-
```
|
| 73 |
-
|
| 74 |
-
### **Hardware Configuration:**
|
| 75 |
-
- **GPU**: RTX 3080 Lite Hash Rate (ideal for 13B models)
|
| 76 |
-
- **RAM**: 64GB total, 11GB used
|
| 77 |
-
- **OS**: Ubuntu 22.04.5 LTS
|
| 78 |
-
- **Python**: 3.9.23 via conda
|
| 79 |
-
|
| 80 |
-
---
|
| 81 |
-
|
| 82 |
-
## 📝 **Architecture Decisions Made** (Historical)
|
| 83 |
-
|
| 84 |
-
### **Foundation Decisions (August 2025):**
|
| 85 |
-
1. **Ollama for MVP**: Local hosting, no API costs, full data privacy
|
| 86 |
-
2. **SQLite for MVP**: Simple setup, upgradeable to PostgreSQL later
|
| 87 |
-
3. **Streamlit for UI**: Rapid prototyping, suitable for research tools
|
| 88 |
-
4. **YAML for Personas**: Human-readable format, researchers can modify easily
|
| 89 |
-
5. **FastAPI Backend**: Built-in async support, automatic API documentation
|
| 90 |
-
6. **WebSocket Real-time**: Essential for live conversation monitoring
|
| 91 |
-
|
| 92 |
-
### **Technical Patterns Established:**
|
| 93 |
-
- **Async Everything**: FastAPI endpoints, LLM calls, WebSocket handling
|
| 94 |
-
- **Configuration-Driven**: No hardcoded values, YAML + environment variables
|
| 95 |
-
- **AI-First Documentation**: Every component designed for AI agent consumption
|
| 96 |
-
- **Modular Design**: Clear boundaries for future component replacement
|
| 97 |
-
|
| 98 |
-
---
|
| 99 |
-
|
| 100 |
-
## 🎉 **Key Achievements**
|
| 101 |
-
|
| 102 |
-
### **What Works Right Now:**
|
| 103 |
-
- **Individual Persona Responses**: Each character has distinct personality
|
| 104 |
-
- **Multi-turn Conversations**: Conversations maintain history and context
|
| 105 |
-
- **Professional Healthcare Context**: Responses include medical terminology and situations
|
| 106 |
-
- **Real-time Capability**: WebSocket infrastructure ready for live streaming
|
| 107 |
-
- **Research-Ready Personas**: 5 detailed characters suitable for survey research
|
| 108 |
-
|
| 109 |
-
### **Demo Readiness Assessment:**
|
| 110 |
-
- **Technical Team**: ✅ Ready immediately (working code demonstrated)
|
| 111 |
-
- **Non-Technical Research Team**: 🎯 1-2 weeks to visual interface
|
| 112 |
-
- **Demo Appeal**: HIGH - authentic AI personalities with healthcare context
|
| 113 |
-
|
| 114 |
-
---
|
| 115 |
-
|
| 116 |
-
## 🔄 **Development Methodology Established**
|
| 117 |
-
|
| 118 |
-
### **Testing Approach:**
|
| 119 |
-
- **Integration-First**: Full component interaction testing
|
| 120 |
-
- **Persona Quality**: Response authenticity verification
|
| 121 |
-
- **Environment Verification**: Multi-platform setup validation
|
| 122 |
-
|
| 123 |
-
### **Documentation Strategy:**
|
| 124 |
-
- **AXIOM Files**: Static reference documentation
|
| 125 |
-
- **PROJECT_STATE**: Single evolving status file
|
| 126 |
-
- **Historical Archive**: This file for completed implementations
|
| 127 |
-
|
| 128 |
-
---
|
| 129 |
-
|
| 130 |
-
---
|
| 131 |
-
|
| 132 |
-
## 🚀 **Step 1 Implementation Complete** (2025-09-16)
|
| 133 |
-
|
| 134 |
-
### **Core Conversation Engine** ✅
|
| 135 |
-
- **Files Created**: `backend/core/conversation_manager.py`, `scripts/run_conversation_demo.py`
|
| 136 |
-
- **Functionality**: Full AI-to-AI conversation orchestration with persona management
|
| 137 |
-
- **Features**: Rich terminal display, conversation termination, error handling
|
| 138 |
-
- **Testing**: Live conversations between Dr. Sarah Mitchell and Margaret Thompson
|
| 139 |
-
- **Performance**: Faster than estimated (1 session vs 2-3 days planned)
|
| 140 |
-
|
| 141 |
-
### **Terminal Demo Script**
|
| 142 |
-
```bash
|
| 143 |
-
# Commands now available:
|
| 144 |
-
python scripts/run_conversation_demo.py
|
| 145 |
-
python scripts/run_conversation_demo.py --model gemma3:4b
|
| 146 |
-
```
|
| 147 |
-
|
| 148 |
-
**Demo Features**:
|
| 149 |
-
- Rich formatted terminal output with colored panels
|
| 150 |
-
- Persona selection interface
|
| 151 |
-
- Live conversation streaming
|
| 152 |
-
- Turn-by-turn progress tracking
|
| 153 |
-
- Graceful error handling and cleanup
|
| 154 |
-
|
| 155 |
-
---
|
| 156 |
-
|
| 157 |
-
**Last Updated**: 2025-09-16
|
| 158 |
-
**Status**: Foundation + Step 1 complete, archived for reference
|
| 159 |
-
**Next Phase**: Step 2 - WebSocket Bridge (see PROJECT_STATE.md)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AXIOM_README.md
DELETED
|
@@ -1,316 +0,0 @@
|
|
| 1 |
-
# AI Survey Simulator
|
| 2 |
-
|
| 3 |
-
An AI-to-AI healthcare survey research platform that enables simulated conversations between AI survey interviewers and patient personas for healthcare research purposes.
|
| 4 |
-
|
| 5 |
-
## 🎯 Project Overview
|
| 6 |
-
|
| 7 |
-
The AI Survey Simulator is a research tool designed to:
|
| 8 |
-
- Enable AI-to-AI conversations for healthcare survey testing
|
| 9 |
-
- Simulate various patient personas responding to survey questions
|
| 10 |
-
- Provide real-time monitoring of AI conversations
|
| 11 |
-
- Log conversations for research analysis
|
| 12 |
-
- Support self-hosted deployment using local LLMs
|
| 13 |
-
|
| 14 |
-
## 🏗️ Architecture
|
| 15 |
-
|
| 16 |
-
```
|
| 17 |
-
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
| 18 |
-
│ Streamlit UI │────▶│ FastAPI Server │────▶│ LLM Backend │
|
| 19 |
-
│ │◀────│ (WebSocket) │◀────│ (Ollama/vLLM) │
|
| 20 |
-
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
| 21 |
-
│
|
| 22 |
-
▼
|
| 23 |
-
┌─────────────────┐
|
| 24 |
-
│ SQLite/JSON │
|
| 25 |
-
│ Data Storage │
|
| 26 |
-
└─────────────────┘
|
| 27 |
-
```
|
| 28 |
-
|
| 29 |
-
### Core Components
|
| 30 |
-
|
| 31 |
-
- **Frontend**: Streamlit-based interface for real-time conversation monitoring
|
| 32 |
-
- **Backend API**: FastAPI server handling conversation orchestration
|
| 33 |
-
- **LLM Integration**: Supports Ollama and vLLM for local model deployment
|
| 34 |
-
- **Data Storage**: SQLite for conversation logs, JSON/YAML for personas
|
| 35 |
-
- **Real-time Communication**: WebSocket for live conversation streaming
|
| 36 |
-
|
| 37 |
-
## 🚀 Quick Start
|
| 38 |
-
|
| 39 |
-
### Prerequisites
|
| 40 |
-
|
| 41 |
-
- Python 3.9+
|
| 42 |
-
- NVIDIA GPU with 8GB+ VRAM (for local LLM hosting)
|
| 43 |
-
- Ollama or vLLM installed (see LLM Setup section)
|
| 44 |
-
|
| 45 |
-
### Installation
|
| 46 |
-
|
| 47 |
-
1. Clone the repository:
|
| 48 |
-
```bash
|
| 49 |
-
git clone <repository-url>
|
| 50 |
-
cd ai-survey-simulator
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
2. Create a virtual environment:
|
| 54 |
-
```bash
|
| 55 |
-
python -m venv venv
|
| 56 |
-
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 57 |
-
```
|
| 58 |
-
|
| 59 |
-
3. Install dependencies:
|
| 60 |
-
```bash
|
| 61 |
-
pip install -r requirements.txt
|
| 62 |
-
```
|
| 63 |
-
|
| 64 |
-
4. Copy environment configuration:
|
| 65 |
-
```bash
|
| 66 |
-
cp .env.example .env
|
| 67 |
-
```
|
| 68 |
-
|
| 69 |
-
5. Configure your LLM backend in `.env`:
|
| 70 |
-
```bash
|
| 71 |
-
# For Ollama
|
| 72 |
-
LLM_BACKEND=ollama
|
| 73 |
-
OLLAMA_HOST=http://localhost:11434
|
| 74 |
-
OLLAMA_MODEL=llama2:13b
|
| 75 |
-
|
| 76 |
-
# For vLLM
|
| 77 |
-
# LLM_BACKEND=vllm
|
| 78 |
-
# VLLM_HOST=http://localhost:8000
|
| 79 |
-
# VLLM_MODEL=meta-llama/Llama-2-13b-chat-hf
|
| 80 |
-
```
|
| 81 |
-
|
| 82 |
-
### Running the Application
|
| 83 |
-
|
| 84 |
-
1. Start the backend server:
|
| 85 |
-
```bash
|
| 86 |
-
cd backend
|
| 87 |
-
uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 88 |
-
```
|
| 89 |
-
|
| 90 |
-
2. In a new terminal, start the Streamlit frontend:
|
| 91 |
-
```bash
|
| 92 |
-
cd frontend
|
| 93 |
-
streamlit run streamlit_app.py
|
| 94 |
-
```
|
| 95 |
-
|
| 96 |
-
3. Access the application at `http://localhost:8501`
|
| 97 |
-
|
| 98 |
-
## 🔧 Development Workflow
|
| 99 |
-
|
| 100 |
-
### Project Structure
|
| 101 |
-
|
| 102 |
-
```
|
| 103 |
-
ai-survey-simulator/
|
| 104 |
-
├── backend/ # FastAPI backend service
|
| 105 |
-
│ ├── api/ # API endpoints and WebSocket handlers
|
| 106 |
-
│ ├── core/ # Core business logic
|
| 107 |
-
│ ├── models/ # Data models
|
| 108 |
-
│ └── storage/ # Database and logging
|
| 109 |
-
├── frontend/ # Streamlit UI
|
| 110 |
-
│ ├── components/ # UI components
|
| 111 |
-
│ └── utils/ # Frontend utilities
|
| 112 |
-
├── data/ # Persona definitions and database
|
| 113 |
-
├── config/ # Configuration files
|
| 114 |
-
├── scripts/ # Utility scripts
|
| 115 |
-
└── tests/ # Test suite
|
| 116 |
-
```
|
| 117 |
-
|
| 118 |
-
### Development Guidelines
|
| 119 |
-
|
| 120 |
-
1. **Code Style**: Follow PEP 8 and use type hints
|
| 121 |
-
2. **Testing**: Write tests for new features in the `tests/` directory
|
| 122 |
-
3. **Documentation**: Update docstrings and README for new features
|
| 123 |
-
4. **Git Workflow**: Create feature branches and use descriptive commit messages
|
| 124 |
-
|
| 125 |
-
### Running Tests
|
| 126 |
-
|
| 127 |
-
```bash
|
| 128 |
-
# Run all tests
|
| 129 |
-
pytest
|
| 130 |
-
|
| 131 |
-
# Run with coverage
|
| 132 |
-
pytest --cov=backend --cov=frontend
|
| 133 |
-
|
| 134 |
-
# Run specific test file
|
| 135 |
-
pytest tests/test_conversation_manager.py
|
| 136 |
-
```
|
| 137 |
-
|
| 138 |
-
## 📦 MVP Features
|
| 139 |
-
|
| 140 |
-
The Minimum Viable Product includes:
|
| 141 |
-
|
| 142 |
-
1. **Basic AI-to-AI Conversations**: Two AI agents conducting healthcare surveys
|
| 143 |
-
2. **Patient Personas**: 2-3 pre-configured patient personas
|
| 144 |
-
3. **Real-time Display**: Live conversation monitoring via Streamlit
|
| 145 |
-
4. **Conversation Logging**: SQLite database for conversation storage
|
| 146 |
-
5. **Simple Configuration**: YAML-based configuration system
|
| 147 |
-
|
| 148 |
-
## 🚧 Extending to Full Features
|
| 149 |
-
|
| 150 |
-
### Phase 2: Enhanced Personas
|
| 151 |
-
- Dynamic persona creation/editing interface
|
| 152 |
-
- Persona behavior parameters (cooperation level, anxiety, etc.)
|
| 153 |
-
- Persona template library
|
| 154 |
-
|
| 155 |
-
### Phase 3: Advanced Conversation Control
|
| 156 |
-
- Mid-conversation intervention capabilities
|
| 157 |
-
- Conversation branching and replay
|
| 158 |
-
- Survey script management
|
| 159 |
-
|
| 160 |
-
### Phase 4: Analytics & Export
|
| 161 |
-
- Conversation analysis dashboard
|
| 162 |
-
- Export to common research formats (CSV, JSON, SPSS)
|
| 163 |
-
- Automated report generation
|
| 164 |
-
|
| 165 |
-
### Phase 5: Multi-Model Support
|
| 166 |
-
- Support for multiple LLM providers
|
| 167 |
-
- Model comparison features
|
| 168 |
-
- Fine-tuning integration
|
| 169 |
-
|
| 170 |
-
## ⚙️ Configuration Guide
|
| 171 |
-
|
| 172 |
-
### Environment Variables (.env)
|
| 173 |
-
```bash
|
| 174 |
-
# LLM Configuration
|
| 175 |
-
LLM_BACKEND=ollama # Options: ollama, vllm
|
| 176 |
-
OLLAMA_HOST=http://localhost:11434
|
| 177 |
-
OLLAMA_MODEL=llama2:13b
|
| 178 |
-
|
| 179 |
-
# API Configuration
|
| 180 |
-
API_HOST=0.0.0.0
|
| 181 |
-
API_PORT=8000
|
| 182 |
-
API_WORKERS=1
|
| 183 |
-
|
| 184 |
-
# Database Configuration
|
| 185 |
-
DATABASE_URL=sqlite:///./data/conversations.db
|
| 186 |
-
|
| 187 |
-
# Logging
|
| 188 |
-
LOG_LEVEL=INFO
|
| 189 |
-
LOG_FILE=./logs/app.log
|
| 190 |
-
```
|
| 191 |
-
|
| 192 |
-
### Persona Configuration (config/personas_config.yaml)
|
| 193 |
-
```yaml
|
| 194 |
-
patient_personas:
|
| 195 |
-
cooperative_senior:
|
| 196 |
-
name: "Margaret Thompson"
|
| 197 |
-
age: 72
|
| 198 |
-
personality: "Friendly, cooperative, detail-oriented"
|
| 199 |
-
health_context: "Managing diabetes and hypertension"
|
| 200 |
-
communication_style: "Polite, sometimes needs clarification"
|
| 201 |
-
|
| 202 |
-
anxious_parent:
|
| 203 |
-
name: "Jennifer Chen"
|
| 204 |
-
age: 38
|
| 205 |
-
personality: "Worried, protective, questioning"
|
| 206 |
-
health_context: "Child with recurring asthma"
|
| 207 |
-
communication_style: "Asks many questions, needs reassurance"
|
| 208 |
-
```
|
| 209 |
-
|
| 210 |
-
## 🔌 LLM Setup
|
| 211 |
-
|
| 212 |
-
### Option 1: Ollama (Recommended for MVP)
|
| 213 |
-
|
| 214 |
-
1. Install Ollama:
|
| 215 |
-
```bash
|
| 216 |
-
curl -fsSL https://ollama.ai/install.sh | sh
|
| 217 |
-
```
|
| 218 |
-
|
| 219 |
-
2. Pull a model:
|
| 220 |
-
```bash
|
| 221 |
-
ollama pull llama2:13b
|
| 222 |
-
```
|
| 223 |
-
|
| 224 |
-
3. Verify installation:
|
| 225 |
-
```bash
|
| 226 |
-
ollama list
|
| 227 |
-
```
|
| 228 |
-
|
| 229 |
-
### Option 2: vLLM (For Production)
|
| 230 |
-
|
| 231 |
-
1. Install vLLM:
|
| 232 |
-
```bash
|
| 233 |
-
pip install vllm
|
| 234 |
-
```
|
| 235 |
-
|
| 236 |
-
2. Start vLLM server:
|
| 237 |
-
```bash
|
| 238 |
-
python -m vllm.entrypoints.openai.api_server \
|
| 239 |
-
--model meta-llama/Llama-2-13b-chat-hf \
|
| 240 |
-
--host 0.0.0.0 \
|
| 241 |
-
--port 8000
|
| 242 |
-
```
|
| 243 |
-
|
| 244 |
-
## 🧪 Testing Approach
|
| 245 |
-
|
| 246 |
-
### Unit Tests
|
| 247 |
-
- Test individual components (persona system, conversation manager)
|
| 248 |
-
- Mock LLM responses for predictable testing
|
| 249 |
-
- Located in `tests/test_*.py`
|
| 250 |
-
|
| 251 |
-
### Integration Tests
|
| 252 |
-
- Test API endpoints with real/mock data
|
| 253 |
-
- WebSocket connection testing
|
| 254 |
-
- Database operation verification
|
| 255 |
-
|
| 256 |
-
### End-to-End Tests
|
| 257 |
-
- Full conversation flow testing
|
| 258 |
-
- UI interaction testing with Selenium
|
| 259 |
-
- Performance benchmarking
|
| 260 |
-
|
| 261 |
-
## 📊 Data Export
|
| 262 |
-
|
| 263 |
-
Conversations can be exported via:
|
| 264 |
-
|
| 265 |
-
1. **API Endpoint**: `GET /api/conversations/export?format=csv`
|
| 266 |
-
2. **CLI Script**: `python scripts/export_conversations.py --format json`
|
| 267 |
-
3. **UI Export**: Available in the Streamlit interface
|
| 268 |
-
|
| 269 |
-
## 🤝 Contributing
|
| 270 |
-
|
| 271 |
-
1. Fork the repository
|
| 272 |
-
2. Create a feature branch: `git checkout -b feature/your-feature`
|
| 273 |
-
3. Commit changes: `git commit -m 'Add your feature'`
|
| 274 |
-
4. Push to branch: `git push origin feature/your-feature`
|
| 275 |
-
5. Submit a Pull Request
|
| 276 |
-
|
| 277 |
-
## 📄 License
|
| 278 |
-
|
| 279 |
-
This project is licensed under the MIT License - see LICENSE file for details.
|
| 280 |
-
|
| 281 |
-
## 🆘 Troubleshooting
|
| 282 |
-
|
| 283 |
-
### Common Issues
|
| 284 |
-
|
| 285 |
-
1. **LLM Connection Failed**
|
| 286 |
-
- Verify Ollama/vLLM is running
|
| 287 |
-
- Check LLM_HOST in .env file
|
| 288 |
-
- Test with: `python scripts/test_llm_connection.py`
|
| 289 |
-
|
| 290 |
-
2. **WebSocket Connection Error**
|
| 291 |
-
- Ensure backend is running on correct port
|
| 292 |
-
- Check firewall settings
|
| 293 |
-
- Verify CORS configuration
|
| 294 |
-
|
| 295 |
-
3. **Database Lock Error**
|
| 296 |
-
- Close other connections to SQLite
|
| 297 |
-
- Check file permissions
|
| 298 |
-
- Consider PostgreSQL for production
|
| 299 |
-
|
| 300 |
-
### Debug Mode
|
| 301 |
-
|
| 302 |
-
Enable debug logging:
|
| 303 |
-
```bash
|
| 304 |
-
export LOG_LEVEL=DEBUG
|
| 305 |
-
export STREAMLIT_SERVER_LOG_LEVEL=debug
|
| 306 |
-
```
|
| 307 |
-
|
| 308 |
-
## 📞 Support
|
| 309 |
-
|
| 310 |
-
- Create an issue for bug reports
|
| 311 |
-
- Discussion forum for feature requests
|
| 312 |
-
- Email: [project-email]
|
| 313 |
-
|
| 314 |
-
---
|
| 315 |
-
|
| 316 |
-
**Note**: This is a research tool intended for healthcare survey development and testing. Not for clinical use.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AXIOM_SETUP.md
DELETED
|
@@ -1,208 +0,0 @@
|
|
| 1 |
-
# 🚀 Environment Setup Guide
|
| 2 |
-
|
| 3 |
-
> **Quick Setup**: Complete environment configuration for AI Survey Simulator development.
|
| 4 |
-
|
| 5 |
-
## 📋 **Prerequisites**
|
| 6 |
-
|
| 7 |
-
- Python 3.9+
|
| 8 |
-
- Git
|
| 9 |
-
- NVIDIA GPU recommended (but not required)
|
| 10 |
-
|
| 11 |
-
## ⚡ **Quick Setup (5 minutes)**
|
| 12 |
-
|
| 13 |
-
### **1. Create Conda Environment**
|
| 14 |
-
```bash
|
| 15 |
-
# Create and activate environment
|
| 16 |
-
conda create -n ai-survey-sim python=3.9 -y
|
| 17 |
-
conda activate ai-survey-sim
|
| 18 |
-
|
| 19 |
-
# Install dependencies
|
| 20 |
-
pip install -r requirements.txt
|
| 21 |
-
```
|
| 22 |
-
|
| 23 |
-
### **2. Install Ollama**
|
| 24 |
-
```bash
|
| 25 |
-
# Linux/Mac
|
| 26 |
-
curl -fsSL https://ollama.ai/install.sh | sh
|
| 27 |
-
|
| 28 |
-
# Start Ollama service
|
| 29 |
-
ollama serve &
|
| 30 |
-
|
| 31 |
-
# Pull a model (choose based on your GPU)
|
| 32 |
-
ollama pull llama2:7b # 4GB+ GPU or CPU
|
| 33 |
-
# OR
|
| 34 |
-
ollama pull llama2:13b # 8GB+ GPU (recommended)
|
| 35 |
-
```
|
| 36 |
-
|
| 37 |
-
### **3. Verify Setup**
|
| 38 |
-
```bash
|
| 39 |
-
# Quick verification
|
| 40 |
-
python scripts/check_setup.py
|
| 41 |
-
|
| 42 |
-
# Detailed component tests
|
| 43 |
-
python scripts/test_websocket.py
|
| 44 |
-
python scripts/test_llm_connection.py
|
| 45 |
-
```
|
| 46 |
-
|
| 47 |
-
## 🖥️ **Machine-Specific Setup**
|
| 48 |
-
|
| 49 |
-
### **Laptop (RTX A4000 4GB)**
|
| 50 |
-
```bash
|
| 51 |
-
ollama pull llama2:7b
|
| 52 |
-
export LLM_MODEL=llama2:7b
|
| 53 |
-
```
|
| 54 |
-
|
| 55 |
-
### **Office Desktop (RTX 3080 10GB)**
|
| 56 |
-
```bash
|
| 57 |
-
ollama pull llama2:13b
|
| 58 |
-
export LLM_MODEL=llama2:13b
|
| 59 |
-
```
|
| 60 |
-
|
| 61 |
-
### **Home PC (AMD GPU)**
|
| 62 |
-
```bash
|
| 63 |
-
# CPU-only mode
|
| 64 |
-
ollama pull llama2:7b
|
| 65 |
-
export LLM_MODEL=llama2:7b
|
| 66 |
-
# Performance will be slower but functional
|
| 67 |
-
```
|
| 68 |
-
|
| 69 |
-
### **HPC (Multiple A100s)**
|
| 70 |
-
```bash
|
| 71 |
-
ollama pull llama2:70b
|
| 72 |
-
export LLM_MODEL=llama2:70b
|
| 73 |
-
# Consider vLLM for multi-GPU setups
|
| 74 |
-
```
|
| 75 |
-
|
| 76 |
-
## 🔧 **Configuration**
|
| 77 |
-
|
| 78 |
-
### **Environment Variables**
|
| 79 |
-
```bash
|
| 80 |
-
# Copy example environment file
|
| 81 |
-
cp .env.example .env
|
| 82 |
-
|
| 83 |
-
# Edit .env with your settings
|
| 84 |
-
export LLM_HOST=http://localhost:11434
|
| 85 |
-
export LLM_MODEL=llama2:7b # or llama2:13b
|
| 86 |
-
export API_HOST=0.0.0.0
|
| 87 |
-
export API_PORT=8000
|
| 88 |
-
```
|
| 89 |
-
|
| 90 |
-
### **Test Configuration**
|
| 91 |
-
```bash
|
| 92 |
-
# Test WebSocket
|
| 93 |
-
cd backend && uvicorn api.main:app --reload --host 0.0.0.0 --port 8000 &
|
| 94 |
-
python scripts/test_websocket.py
|
| 95 |
-
|
| 96 |
-
# Test LLM
|
| 97 |
-
python scripts/test_llm_connection.py
|
| 98 |
-
```
|
| 99 |
-
|
| 100 |
-
## 🚨 **Troubleshooting**
|
| 101 |
-
|
| 102 |
-
### **Common Issues**
|
| 103 |
-
|
| 104 |
-
#### **Ollama Connection Failed**
|
| 105 |
-
```bash
|
| 106 |
-
# Check if Ollama is running
|
| 107 |
-
curl http://localhost:11434/api/tags
|
| 108 |
-
|
| 109 |
-
# If not running:
|
| 110 |
-
ollama serve
|
| 111 |
-
|
| 112 |
-
# Check models
|
| 113 |
-
ollama list
|
| 114 |
-
```
|
| 115 |
-
|
| 116 |
-
#### **GPU Not Detected**
|
| 117 |
-
```bash
|
| 118 |
-
# Check GPU status
|
| 119 |
-
nvidia-smi # For NVIDIA
|
| 120 |
-
# AMD GPUs will use CPU automatically
|
| 121 |
-
```
|
| 122 |
-
|
| 123 |
-
#### **Package Import Errors**
|
| 124 |
-
```bash
|
| 125 |
-
# Reinstall requirements
|
| 126 |
-
pip install -r requirements.txt --force-reinstall
|
| 127 |
-
|
| 128 |
-
# Check Python environment
|
| 129 |
-
which python
|
| 130 |
-
python --version
|
| 131 |
-
```
|
| 132 |
-
|
| 133 |
-
#### **Port Already in Use**
|
| 134 |
-
```bash
|
| 135 |
-
# Kill existing processes
|
| 136 |
-
pkill -f uvicorn
|
| 137 |
-
pkill -f streamlit
|
| 138 |
-
|
| 139 |
-
# Or use different ports
|
| 140 |
-
export API_PORT=8001
|
| 141 |
-
export STREAMLIT_SERVER_PORT=8502
|
| 142 |
-
```
|
| 143 |
-
|
| 144 |
-
## 🧪 **Development Workflow**
|
| 145 |
-
|
| 146 |
-
### **Start Development Session**
|
| 147 |
-
```bash
|
| 148 |
-
# 1. Activate environment
|
| 149 |
-
conda activate ai-survey-sim
|
| 150 |
-
|
| 151 |
-
# 2. Verify setup
|
| 152 |
-
python scripts/check_setup.py
|
| 153 |
-
|
| 154 |
-
# 3. Start backend (optional for testing)
|
| 155 |
-
cd backend && uvicorn api.main:app --reload &
|
| 156 |
-
|
| 157 |
-
# 4. Begin development
|
| 158 |
-
# Load context: @CLAUDE.md @STATUS.md
|
| 159 |
-
# Choose task: /conversation-task
|
| 160 |
-
```
|
| 161 |
-
|
| 162 |
-
### **Test Changes**
|
| 163 |
-
```bash
|
| 164 |
-
# Test individual components
|
| 165 |
-
python scripts/test_websocket.py
|
| 166 |
-
python scripts/test_llm_connection.py
|
| 167 |
-
|
| 168 |
-
# Full integration test (when ready)
|
| 169 |
-
python scripts/test_integration.py # Future
|
| 170 |
-
```
|
| 171 |
-
|
| 172 |
-
## 📦 **Docker Alternative (Future)**
|
| 173 |
-
|
| 174 |
-
For consistent environments across machines:
|
| 175 |
-
|
| 176 |
-
```bash
|
| 177 |
-
# Build container
|
| 178 |
-
docker build -t ai-survey-sim .
|
| 179 |
-
|
| 180 |
-
# Run with GPU support
|
| 181 |
-
docker run --gpus all -p 8000:8000 -p 8501:8501 ai-survey-sim
|
| 182 |
-
```
|
| 183 |
-
|
| 184 |
-
## 🔗 **Next Steps**
|
| 185 |
-
|
| 186 |
-
After setup completion:
|
| 187 |
-
|
| 188 |
-
1. **Verify Everything Works**: `python scripts/check_setup.py`
|
| 189 |
-
2. **Load Project Context**: `@CLAUDE.md @STATUS.md`
|
| 190 |
-
3. **Choose Your Task**: `/conversation-task` (current priority)
|
| 191 |
-
4. **Start Coding**: Follow task-specific guidance
|
| 192 |
-
|
| 193 |
-
## 💡 **Performance Tips**
|
| 194 |
-
|
| 195 |
-
### **Memory Optimization**
|
| 196 |
-
- Use 7B models on 4GB GPUs
|
| 197 |
-
- Use 13B models on 8GB+ GPUs
|
| 198 |
-
- Close unused applications
|
| 199 |
-
- Monitor with `nvidia-smi` or `htop`
|
| 200 |
-
|
| 201 |
-
### **Development Speed**
|
| 202 |
-
- Use `--reload` for FastAPI auto-reload
|
| 203 |
-
- Use Streamlit's auto-refresh
|
| 204 |
-
- Keep Ollama server running between sessions
|
| 205 |
-
|
| 206 |
-
---
|
| 207 |
-
|
| 208 |
-
**Need Help?** Check `scripts/check_setup.py` for automated diagnostics!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AXIOM_WEBSOCKET_ARCHITECTURE.md
DELETED
|
@@ -1,285 +0,0 @@
|
|
| 1 |
-
# 🔗 AXIOM: WebSocket Architecture & Implementation History
|
| 2 |
-
|
| 3 |
-
> **Status**: COMPLETE & STABLE
|
| 4 |
-
> **Purpose**: Static reference for completed WebSocket foundation (Steps 1-3)
|
| 5 |
-
> **Date Completed**: 2025-09-18
|
| 6 |
-
|
| 7 |
-
This document captures the complete, finalized WebSocket architecture that enables real-time AI-to-AI conversation streaming. This is stable foundation code that should not require changes.
|
| 8 |
-
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
## 🏗️ **Final Architecture Overview**
|
| 12 |
-
|
| 13 |
-
### **Thread-Safe WebSocket Manager Design**
|
| 14 |
-
**Problem Solved**: Async/sync boundary conflicts between Gradio's synchronous environment and WebSocket's asynchronous nature.
|
| 15 |
-
|
| 16 |
-
**Solution**: Complete separation of concerns - WebSocket remains fully async in dedicated background thread, Gradio stays synchronous, communication via thread-safe message queues.
|
| 17 |
-
|
| 18 |
-
```
|
| 19 |
-
Architecture Flow:
|
| 20 |
-
Gradio Frontend (Sync) ←→ Message Queues ←→ Background Thread (Fully Async WebSocket) ←→ FastAPI Backend
|
| 21 |
-
```
|
| 22 |
-
|
| 23 |
-
**Critical Design Decision**: We did NOT convert async to sync. Instead, we isolated the async WebSocket in its own thread with dedicated event loop, preserving both paradigms while eliminating conflicts.
|
| 24 |
-
|
| 25 |
-
### **Key Components**
|
| 26 |
-
|
| 27 |
-
1. **WebSocketManager** (`frontend/websocket_manager.py`)
|
| 28 |
-
- Runs WebSocket in dedicated background thread
|
| 29 |
-
- Thread-safe message queues for sync/async communication
|
| 30 |
-
- Automatic reconnection with exponential backoff
|
| 31 |
-
- Connection state management
|
| 32 |
-
|
| 33 |
-
2. **ConversationService** (`backend/api/services/conversation_service.py`)
|
| 34 |
-
- Manages active conversation instances
|
| 35 |
-
- Bridges ConversationManager and WebSocket infrastructure
|
| 36 |
-
- Handles conversation lifecycle (start/stop/pause)
|
| 37 |
-
|
| 38 |
-
3. **WebSocket Endpoints** (`backend/api/websockets/conversation_ws.py`)
|
| 39 |
-
- Real-time message broadcasting to connected clients
|
| 40 |
-
- Message validation and protocol handling
|
| 41 |
-
- Connection management with heartbeat
|
| 42 |
-
|
| 43 |
-
---
|
| 44 |
-
|
| 45 |
-
## 📋 **Implementation Steps Completed**
|
| 46 |
-
|
| 47 |
-
### **Step 1: Core Conversation Engine** ✅ (2025-09-16)
|
| 48 |
-
**Goal**: Wire working components into conversation loop
|
| 49 |
-
|
| 50 |
-
**Key Implementation**:
|
| 51 |
-
- `backend/core/conversation_manager.py`: Orchestrates AI-to-AI conversations
|
| 52 |
-
- Async generator pattern for real-time message streaming
|
| 53 |
-
- Proper conversation flow: surveyor → patient → surveyor
|
| 54 |
-
- Termination conditions and error handling
|
| 55 |
-
|
| 56 |
-
**Success**: `python scripts/run_conversation_demo.py` shows live conversations
|
| 57 |
-
|
| 58 |
-
### **Step 2: WebSocket Conversation Bridge** ✅ (2025-09-18)
|
| 59 |
-
**Goal**: Stream conversations to web clients in real-time
|
| 60 |
-
|
| 61 |
-
**Key Implementation**:
|
| 62 |
-
- ConversationService connects ConversationManager to WebSocket system
|
| 63 |
-
- REST API endpoints for conversation control
|
| 64 |
-
- Message broadcasting to all connected clients
|
| 65 |
-
- Start/stop conversation protocol via WebSocket
|
| 66 |
-
|
| 67 |
-
**Success**: 3-terminal pipeline (Ollama + FastAPI + WebSocket test) working
|
| 68 |
-
|
| 69 |
-
### **Step 3: Gradio Chat Interface** ✅ (2025-09-18)
|
| 70 |
-
**Goal**: Visual chat display with reliable WebSocket connectivity
|
| 71 |
-
|
| 72 |
-
**Key Challenge**: Async/sync conflicts caused immediate WebSocket disconnections
|
| 73 |
-
|
| 74 |
-
**Solution Evolution**:
|
| 75 |
-
1. **First Attempt**: Direct WebSocket in Gradio → Failed (JSON schema errors)
|
| 76 |
-
2. **Second Attempt**: Simplified approach → Failed (connection drops)
|
| 77 |
-
3. **Final Solution**: Complete architectural redesign with background threads
|
| 78 |
-
|
| 79 |
-
**Breakthrough**: WebSocketManager with dedicated event loop in background thread
|
| 80 |
-
|
| 81 |
-
**Success**: Real-time AI conversations display in browser with reliable connectivity
|
| 82 |
-
|
| 83 |
-
---
|
| 84 |
-
|
| 85 |
-
## 🔧 **Technical Implementation Details**
|
| 86 |
-
|
| 87 |
-
### **WebSocketManager Architecture**
|
| 88 |
-
|
| 89 |
-
```python
|
| 90 |
-
class WebSocketManager:
|
| 91 |
-
def __init__(self, url: str, conversation_id: str):
|
| 92 |
-
# Thread-safe message queues
|
| 93 |
-
self.outbound_queue = queue.Queue() # Messages to send
|
| 94 |
-
self.inbound_queue = queue.Queue() # Received messages
|
| 95 |
-
|
| 96 |
-
def _run_websocket(self):
|
| 97 |
-
"""Run WebSocket in background thread with dedicated event loop."""
|
| 98 |
-
self.loop = asyncio.new_event_loop()
|
| 99 |
-
asyncio.set_event_loop(self.loop)
|
| 100 |
-
self.loop.run_until_complete(self._websocket_main())
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
**Key Features**:
|
| 104 |
-
- Dedicated event loop in background thread
|
| 105 |
-
- Thread-safe queues for sync/async boundary
|
| 106 |
-
- Automatic reconnection with exponential backoff
|
| 107 |
-
- State management (STOPPED, STARTING, CONNECTED, etc.)
|
| 108 |
-
|
| 109 |
-
### **Message Flow Protocol**
|
| 110 |
-
|
| 111 |
-
1. **Start Conversation**:
|
| 112 |
-
```json
|
| 113 |
-
{
|
| 114 |
-
"type": "start_conversation",
|
| 115 |
-
"content": "start",
|
| 116 |
-
"surveyor_persona_id": "friendly_researcher_001",
|
| 117 |
-
"patient_persona_id": "cooperative_senior_001"
|
| 118 |
-
}
|
| 119 |
-
```
|
| 120 |
-
|
| 121 |
-
2. **Conversation Message**:
|
| 122 |
-
```json
|
| 123 |
-
{
|
| 124 |
-
"type": "conversation_message",
|
| 125 |
-
"role": "surveyor|patient",
|
| 126 |
-
"content": "message content",
|
| 127 |
-
"persona": "persona name",
|
| 128 |
-
"turn": 1
|
| 129 |
-
}
|
| 130 |
-
```
|
| 131 |
-
|
| 132 |
-
3. **Status Updates**:
|
| 133 |
-
```json
|
| 134 |
-
{
|
| 135 |
-
"type": "conversation_status",
|
| 136 |
-
"status": "starting|running|completed"
|
| 137 |
-
}
|
| 138 |
-
```
|
| 139 |
-
|
| 140 |
-
### **Critical Bug Fixes Implemented**
|
| 141 |
-
|
| 142 |
-
1. **"Set changed size during iteration"** - WebSocket connection manager
|
| 143 |
-
- Fixed by creating copy of connections set before iteration
|
| 144 |
-
|
| 145 |
-
2. **Async/Sync Boundary Conflicts** - Gradio + WebSocket
|
| 146 |
-
- Solved with background thread architecture
|
| 147 |
-
|
| 148 |
-
3. **Persona ID Mismatches** - Frontend/Backend coordination
|
| 149 |
-
- Standardized on: "friendly_researcher_001", "cooperative_senior_001"
|
| 150 |
-
|
| 151 |
-
---
|
| 152 |
-
|
| 153 |
-
## 📁 **Final File Structure**
|
| 154 |
-
|
| 155 |
-
### **Frontend Files**
|
| 156 |
-
```
|
| 157 |
-
frontend/
|
| 158 |
-
├── gradio_app.py # Main Gradio application
|
| 159 |
-
├── websocket_manager.py # Thread-safe WebSocket client
|
| 160 |
-
└── __pycache__/ # Python cache
|
| 161 |
-
```
|
| 162 |
-
|
| 163 |
-
### **Backend Files**
|
| 164 |
-
```
|
| 165 |
-
backend/
|
| 166 |
-
├── api/
|
| 167 |
-
│ ├── main.py # FastAPI app with WebSocket endpoint
|
| 168 |
-
│ ├── routes/conversations.py # REST API endpoints
|
| 169 |
-
│ ├── services/conversation_service.py # Conversation management service
|
| 170 |
-
│ └── websockets/conversation_ws.py # WebSocket connection handling
|
| 171 |
-
└── core/
|
| 172 |
-
├── conversation_manager.py # AI-to-AI conversation orchestration
|
| 173 |
-
├── llm_client.py # Ollama integration
|
| 174 |
-
└── persona_system.py # Persona loading and management
|
| 175 |
-
```
|
| 176 |
-
|
| 177 |
-
### **Test Files**
|
| 178 |
-
```
|
| 179 |
-
scripts/
|
| 180 |
-
├── test_websocket.py # Basic WebSocket functionality test
|
| 181 |
-
├── test_integration.py # Foundation component tests (7/7)
|
| 182 |
-
└── run_conversation_demo.py # Terminal conversation demo
|
| 183 |
-
```
|
| 184 |
-
|
| 185 |
-
---
|
| 186 |
-
|
| 187 |
-
## 🚀 **Deployment & Usage**
|
| 188 |
-
|
| 189 |
-
### **Current Working Demo**
|
| 190 |
-
```bash
|
| 191 |
-
# Terminal 1: Start Ollama
|
| 192 |
-
ollama serve
|
| 193 |
-
|
| 194 |
-
# Terminal 2: Start FastAPI backend
|
| 195 |
-
cd backend && uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 196 |
-
|
| 197 |
-
# Terminal 3: Launch Gradio frontend
|
| 198 |
-
python frontend/gradio_app.py
|
| 199 |
-
```
|
| 200 |
-
|
| 201 |
-
**Result**:
|
| 202 |
-
- Browser opens to `http://localhost:7860`
|
| 203 |
-
- Click "Connect to Backend" → "Start Conversation"
|
| 204 |
-
- Real-time AI-to-AI conversation streams live
|
| 205 |
-
- Click "Refresh Messages" to see new responses
|
| 206 |
-
|
| 207 |
-
### **WebSocket Test**
|
| 208 |
-
```bash
|
| 209 |
-
python scripts/test_websocket.py
|
| 210 |
-
```
|
| 211 |
-
**Expected**: All WebSocket functionality tests pass
|
| 212 |
-
|
| 213 |
-
---
|
| 214 |
-
|
| 215 |
-
## 🎯 **What This Foundation Enables**
|
| 216 |
-
|
| 217 |
-
This completed WebSocket architecture provides the foundation for:
|
| 218 |
-
|
| 219 |
-
1. **Real-time conversation streaming** - Messages appear instantly in browser
|
| 220 |
-
2. **Reliable connectivity** - Automatic reconnection, error handling
|
| 221 |
-
3. **Scalable architecture** - Multiple clients can connect to same conversation
|
| 222 |
-
4. **Future UI development** - Solid backend for advanced frontend features
|
| 223 |
-
|
| 224 |
-
---
|
| 225 |
-
|
| 226 |
-
## 📝 **Key Lessons & Design Decisions**
|
| 227 |
-
|
| 228 |
-
### **Framework Choice: Gradio vs Streamlit**
|
| 229 |
-
**Decision**: Gradio
|
| 230 |
-
**Reasoning**:
|
| 231 |
-
- Native chat components (`gr.Chatbot()`)
|
| 232 |
-
- Better WebSocket integration
|
| 233 |
-
- More suitable for real-time applications
|
| 234 |
-
|
| 235 |
-
### **WebSocket Architecture: Direct vs Background Thread**
|
| 236 |
-
**Decision**: Background thread with message queues
|
| 237 |
-
**Reasoning**:
|
| 238 |
-
- Eliminates async/sync conflicts completely
|
| 239 |
-
- Provides reliable, persistent connections
|
| 240 |
-
- Allows Gradio to remain fully synchronous
|
| 241 |
-
|
| 242 |
-
### **Deployment Strategy: Local + ngrok**
|
| 243 |
-
**Decision**: Local development with ngrok tunneling for team access
|
| 244 |
-
**Reasoning**:
|
| 245 |
-
- Leverages full local GPU power
|
| 246 |
-
- Zero hosting costs during development
|
| 247 |
-
- Instant team access when needed
|
| 248 |
-
|
| 249 |
-
---
|
| 250 |
-
|
| 251 |
-
## 🔍 **Architecture Trade-offs & Implications**
|
| 252 |
-
|
| 253 |
-
### **What We Preserved**
|
| 254 |
-
- **Full WebSocket async capabilities**: All async WebSocket features remain available
|
| 255 |
-
- **Gradio simplicity**: No async contamination in UI code
|
| 256 |
-
- **Real-time performance**: Minimal latency impact (queue operations ~microseconds)
|
| 257 |
-
|
| 258 |
-
### **Limitations Introduced**
|
| 259 |
-
1. **Message Buffering**: Messages pass through queues instead of direct handling
|
| 260 |
-
2. **Thread Overhead**: Additional background thread and event loop (minimal resource impact)
|
| 261 |
-
3. **Complexity**: More complex than direct async integration (but necessary for Gradio compatibility)
|
| 262 |
-
|
| 263 |
-
### **Performance Impact Assessment**
|
| 264 |
-
- **Latency**: Negligible for AI conversations (queue ~μs, AI responses ~seconds)
|
| 265 |
-
- **Memory**: Bounded by `max_messages = 100` (~1MB maximum)
|
| 266 |
-
- **Reliability**: Major improvement (eliminated connection drops)
|
| 267 |
-
|
| 268 |
-
### **User Experience Impact**
|
| 269 |
-
- **✅ Positive**: Reliable, persistent connections
|
| 270 |
-
- **✅ Neutral**: No perceptible delay in conversation flow
|
| 271 |
-
- **❌ None**: No negative UX impacts identified
|
| 272 |
-
|
| 273 |
-
---
|
| 274 |
-
|
| 275 |
-
## ⚠️ **Important Notes for Future Development**
|
| 276 |
-
|
| 277 |
-
1. **Do not modify WebSocketManager**: This architecture solved critical async/sync conflicts
|
| 278 |
-
2. **WebSocket stays fully async**: Never attempt to make WebSocket synchronous
|
| 279 |
-
3. **Background thread is essential**: Direct WebSocket in Gradio main thread will fail
|
| 280 |
-
4. **Message queues must remain thread-safe**: Any modifications must preserve thread safety
|
| 281 |
-
5. **Consider implications**: New features should work within queue-based message flow
|
| 282 |
-
|
| 283 |
-
---
|
| 284 |
-
|
| 285 |
-
**This architecture is COMPLETE and STABLE. The trade-offs are acceptable for our use case and no significant limitations were introduced. Use as reference for building additional features on top.**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PROJECT_STATE.md
DELETED
|
@@ -1,246 +0,0 @@
|
|
| 1 |
-
# 🚦 AI Survey Simulator - Current Project State
|
| 2 |
-
|
| 3 |
-
> **Single Source of Truth**: This file tracks all current progress, next steps, and recent changes.
|
| 4 |
-
> Update THIS file when making progress - no other documentation needs updates.
|
| 5 |
-
|
| 6 |
-
**Last Updated**: 2025-09-18
|
| 7 |
-
**Current Phase**: Local Development - Web UI Feature Development
|
| 8 |
-
**Overall Status**: 🟢 **Step 3 Complete - Ready for Step 4 (Persona Selection)**
|
| 9 |
-
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
## 🚀 **QUICK DEMO** - See Current Capabilities
|
| 13 |
-
|
| 14 |
-
**What works RIGHT NOW**: Full web-based AI-to-AI conversation interface with real-time streaming
|
| 15 |
-
|
| 16 |
-
**How to test** (3 terminals required):
|
| 17 |
-
```bash
|
| 18 |
-
# Terminal 1: Start Ollama
|
| 19 |
-
ollama serve
|
| 20 |
-
|
| 21 |
-
# Terminal 2: Start backend API (from backend/ directory)
|
| 22 |
-
cd backend && uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 23 |
-
|
| 24 |
-
# Terminal 3: Launch web interface
|
| 25 |
-
python frontend/gradio_app.py
|
| 26 |
-
```
|
| 27 |
-
|
| 28 |
-
**Expected result**: Browser opens to localhost:7860 with working Gradio interface. Click "Connect to Backend" → "Start Conversation" to see live AI-to-AI conversation streaming in real-time.
|
| 29 |
-
|
| 30 |
-
---
|
| 31 |
-
|
| 32 |
-
## 🎯 **Target Goal & Deployment Strategy**
|
| 33 |
-
|
| 34 |
-
### **Final Application Vision:**
|
| 35 |
-
**Local-First Web-Based Conversation Simulator** with:
|
| 36 |
-
- Persona selection interface (surveyor + patient)
|
| 37 |
-
- System prompt editor for surveyors
|
| 38 |
-
- "Simulate Conversation" button
|
| 39 |
-
- Live chat display (chat bubbles, proper positioning)
|
| 40 |
-
- Real-time AI-to-AI conversation visualization
|
| 41 |
-
|
| 42 |
-
### **Deployment Architecture Decision:**
|
| 43 |
-
**📍 Local Development → ngrok Tunneling for Team Access**
|
| 44 |
-
|
| 45 |
-
**Development Approach:**
|
| 46 |
-
- Build complete application locally (`localhost:7860`)
|
| 47 |
-
- Test and refine using local GPU resources
|
| 48 |
-
- Deploy to research team via **ngrok tunneling** when ready
|
| 49 |
-
|
| 50 |
-
**Why This Architecture:**
|
| 51 |
-
- ✅ **Full GPU Power**: Leverage local compute resources
|
| 52 |
-
- ✅ **Real-time Performance**: No cloud latency limitations
|
| 53 |
-
- ✅ **Zero Hosting Costs**: No monthly cloud bills
|
| 54 |
-
- ✅ **Team Access**: ngrok provides public URLs when needed
|
| 55 |
-
- ✅ **Development Control**: Start/stop access on demand
|
| 56 |
-
|
| 57 |
-
**Post-Development Deployment:**
|
| 58 |
-
1. **Research Team Access**: `ngrok http 7860` → share temporary URL
|
| 59 |
-
2. **Future Options**: Upgrade to ngrok Pro for permanent domain if needed
|
| 60 |
-
3. **Alternative**: Can later deploy to HuggingFace Spaces for portfolio if desired
|
| 61 |
-
|
| 62 |
-
---
|
| 63 |
-
|
| 64 |
-
## ✅ **Foundation Status**
|
| 65 |
-
|
| 66 |
-
**WebSocket Architecture Complete** ✅ (See `AXIOM_WEBSOCKET_ARCHITECTURE.md` for details)
|
| 67 |
-
- Real-time AI-to-AI conversation streaming
|
| 68 |
-
- Thread-safe WebSocket manager (async/sync conflicts resolved)
|
| 69 |
-
- Working Gradio frontend with live message display
|
| 70 |
-
- Complete backend conversation management
|
| 71 |
-
|
| 72 |
-
**Ready for Features**: Core conversation system operational, ready for UI enhancements
|
| 73 |
-
|
| 74 |
-
## 🚀 **Web UI Implementation Roadmap**
|
| 75 |
-
|
| 76 |
-
### **Step 1: Core Conversation Engine** ✅ **COMPLETE** (2025-09-16)
|
| 77 |
-
**Goal**: Wire working components into conversation loop
|
| 78 |
-
- ✅ Working LLM+Persona components (already tested)
|
| 79 |
-
- ✅ Implemented `conversation_manager.py` orchestration
|
| 80 |
-
- ✅ Created conversation loop (surveyor → patient → surveyor)
|
| 81 |
-
- ✅ Added termination conditions
|
| 82 |
-
- ✅ Tested terminal AI-to-AI conversations
|
| 83 |
-
|
| 84 |
-
**Success Criteria**: ✅ `python scripts/run_conversation_demo.py` shows live back-and-forth
|
| 85 |
-
|
| 86 |
-
### **Step 2: WebSocket Conversation Bridge** ✅ **COMPLETE** (2025-09-18)
|
| 87 |
-
**Goal**: Stream conversations to web clients in real-time
|
| 88 |
-
- ✅ WebSocket system infrastructure (already built)
|
| 89 |
-
- ✅ Connected conversation engine to WebSocket system
|
| 90 |
-
- ✅ Implemented message broadcasting to clients
|
| 91 |
-
- ✅ Added conversation state management (start/stop/pause)
|
| 92 |
-
- ✅ Created ConversationService for managing active conversations
|
| 93 |
-
- ✅ Added REST API endpoints for conversation control
|
| 94 |
-
- ✅ Updated WebSocket client to be Gradio-compatible
|
| 95 |
-
|
| 96 |
-
**Success Criteria**: ✅ Conversation events stream to browser via WebSocket
|
| 97 |
-
|
| 98 |
-
### **Step 3: Gradio Chat Interface** ✅ **COMPLETE** (2025-09-18)
|
| 99 |
-
**Goal**: Visual chat display with reliable WebSocket connectivity
|
| 100 |
-
- ✅ Replaced Streamlit with working Gradio frontend
|
| 101 |
-
- ✅ Solved critical async/sync conflicts through architectural redesign
|
| 102 |
-
- ✅ Implemented thread-safe WebSocket manager with background threads
|
| 103 |
-
- ✅ Real-time message streaming operational
|
| 104 |
-
- ✅ Complete Streamlit divorce and file consolidation
|
| 105 |
-
|
| 106 |
-
**Success Criteria**: ✅ Live conversation displays in browser with reliable connectivity
|
| 107 |
-
|
| 108 |
-
### **Step 4: Persona Selection & Management** (1-2 days)
|
| 109 |
-
**Goal**: Interactive persona choosing and switching
|
| 110 |
-
- ✅ 5 personas already defined and working
|
| 111 |
-
- 🎯 Add persona dropdown/selection interface
|
| 112 |
-
- 🎯 Implement persona switching mid-conversation
|
| 113 |
-
- 🎯 Display current active personas clearly
|
| 114 |
-
- 🎯 Add persona preview (name, description)
|
| 115 |
-
|
| 116 |
-
**Success Criteria**: Can select and switch personas from UI
|
| 117 |
-
|
| 118 |
-
**Current Status**: 🎯 **NEXT PRIORITY**
|
| 119 |
-
|
| 120 |
-
### **Step 5: System Prompt Editor** (1-2 days)
|
| 121 |
-
**Goal**: Dynamic prompt customization interface
|
| 122 |
-
- 🎯 Build prompt editing interface for surveyors
|
| 123 |
-
- 🎯 Add prompt templates and presets
|
| 124 |
-
- 🎯 Implement live prompt updates without restart
|
| 125 |
-
- 🎯 Add prompt validation and preview
|
| 126 |
-
|
| 127 |
-
**Success Criteria**: Can edit surveyor prompts and see immediate effect
|
| 128 |
-
|
| 129 |
-
### **Step 6: Conversation Controls & Polish** (1-2 days)
|
| 130 |
-
**Goal**: Complete conversation management and prepare for team access
|
| 131 |
-
- 🎯 Add start/stop/pause/reset conversation controls
|
| 132 |
-
- 🎯 Implement conversation history/logging
|
| 133 |
-
- 🎯 Add export functionality (save conversations)
|
| 134 |
-
- 🎯 Polish UI styling and user experience
|
| 135 |
-
- 🎯 **Prepare ngrok deployment guide for research team access**
|
| 136 |
-
|
| 137 |
-
**Success Criteria**: Full-featured conversation simulator ready for local demo and team deployment via ngrok
|
| 138 |
-
|
| 139 |
-
## ⏱️ **Timeline Estimate**: 3-4 days remaining (ahead of schedule!)
|
| 140 |
-
**Original**: 8-12 days total | **Actual Progress**: Steps 1-3 completed (foundation + working web interface)
|
| 141 |
-
**Week 1**: ✅ Steps 1-3 COMPLETE (core functionality + working web UI)
|
| 142 |
-
**Week 2**: Steps 4-6 (persona selection + prompt editing + polish)
|
| 143 |
-
**Major Breakthrough**: Solved WebSocket async/sync conflicts - reliable real-time streaming achieved
|
| 144 |
-
|
| 145 |
-
## 🎯 **Current Priority: Step 4**
|
| 146 |
-
**Next Action**: Add persona selection interface to working Gradio frontend
|
| 147 |
-
|
| 148 |
-
---
|
| 149 |
-
|
| 150 |
-
## 🔧 **Quick Start Commands**
|
| 151 |
-
|
| 152 |
-
### **Run Current Functionality**
|
| 153 |
-
```bash
|
| 154 |
-
# Terminal 1: Start Ollama
|
| 155 |
-
ollama serve
|
| 156 |
-
|
| 157 |
-
# Terminal 2: Start FastAPI backend (NEW!)
|
| 158 |
-
cd backend && uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 159 |
-
|
| 160 |
-
# Terminal 3: Launch web interface (NEW!)
|
| 161 |
-
python frontend/gradio_app.py
|
| 162 |
-
# Opens browser to localhost:7860 with full web UI
|
| 163 |
-
|
| 164 |
-
# Alternative: Test terminal demo
|
| 165 |
-
python scripts/run_conversation_demo.py
|
| 166 |
-
|
| 167 |
-
# Verify foundation components still work
|
| 168 |
-
python scripts/test_integration.py # Should show 7/7 tests pass
|
| 169 |
-
```
|
| 170 |
-
|
| 171 |
-
### **Development Environment**
|
| 172 |
-
```bash
|
| 173 |
-
# Activate environment
|
| 174 |
-
conda activate converai
|
| 175 |
-
```
|
| 176 |
-
|
| 177 |
-
### **Future Team Deployment** (After Step 6 Complete)
|
| 178 |
-
```bash
|
| 179 |
-
# Install ngrok (one-time setup)
|
| 180 |
-
# Download from: https://ngrok.com/download
|
| 181 |
-
|
| 182 |
-
# When ready to share with research team:
|
| 183 |
-
ngrok http 7860
|
| 184 |
-
|
| 185 |
-
# Share the generated URL (e.g., https://abc123.ngrok.io) with team
|
| 186 |
-
# They can access the full application remotely using your local GPU
|
| 187 |
-
```
|
| 188 |
-
|
| 189 |
-
---
|
| 190 |
-
|
| 191 |
-
## 📝 **Recent Changes Log**
|
| 192 |
-
|
| 193 |
-
### **2025-09-18 - Step 3 Complete: Gradio Web Interface & Architecture Consolidation**
|
| 194 |
-
- ✅ **WebSocket Architecture Breakthrough**: Solved critical async/sync conflicts through complete redesign
|
| 195 |
-
- ✅ **Thread-Safe WebSocket Manager**: Created background thread architecture with message queues
|
| 196 |
-
- ✅ **Working Gradio Frontend**: `frontend/gradio_app.py` with real-time conversation streaming
|
| 197 |
-
- ✅ **Complete Streamlit Divorce**: Removed all Streamlit dependencies and files
|
| 198 |
-
- ✅ **File Consolidation**: Cleaned up deprecated files, single canonical implementation
|
| 199 |
-
- ✅ **CORS Cleanup**: Removed Streamlit origins from backend configuration
|
| 200 |
-
- ✅ **Foundation Documentation**: Moved completed Steps 1-3 to `AXIOM_WEBSOCKET_ARCHITECTURE.md`
|
| 201 |
-
- **Key Files Created/Modified**:
|
| 202 |
-
- `frontend/websocket_manager.py` (new - thread-safe WebSocket client)
|
| 203 |
-
- `frontend/gradio_app.py` (working web interface)
|
| 204 |
-
- `backend/api/main.py` (CORS cleanup)
|
| 205 |
-
- `AXIOM_WEBSOCKET_ARCHITECTURE.md` (complete foundation documentation)
|
| 206 |
-
|
| 207 |
-
**Major Achievement**: Real-time AI-to-AI conversations now work reliably in web browser
|
| 208 |
-
|
| 209 |
-
### **Earlier History**
|
| 210 |
-
See `AXIOM_IMPLEMENTATION_HISTORY.md` for foundation implementation details.
|
| 211 |
-
|
| 212 |
-
---
|
| 213 |
-
|
| 214 |
-
## 🐛 **Current Issues & Blockers**
|
| 215 |
-
|
| 216 |
-
**None** - Foundation tested and working, ready for web UI development.
|
| 217 |
-
|
| 218 |
-
---
|
| 219 |
-
|
| 220 |
-
## 🔄 **For Next Development Session**
|
| 221 |
-
|
| 222 |
-
### **Start Here**: Step 4 - Persona Selection Interface
|
| 223 |
-
### **Key Files to Work On**:
|
| 224 |
-
- `frontend/gradio_app.py` (add persona selection dropdowns)
|
| 225 |
-
- `backend/core/persona_system.py` (already working - reference for available personas)
|
| 226 |
-
- `backend/api/routes/conversations.py` (may need persona switching endpoints)
|
| 227 |
-
|
| 228 |
-
### **Context Loading**:
|
| 229 |
-
```bash
|
| 230 |
-
# Load current roadmap
|
| 231 |
-
@PROJECT_STATE.md
|
| 232 |
-
|
| 233 |
-
# Load working web interface (foundation)
|
| 234 |
-
@frontend/gradio_app.py
|
| 235 |
-
@frontend/websocket_manager.py
|
| 236 |
-
|
| 237 |
-
# Reference persona system
|
| 238 |
-
@backend/core/persona_system.py
|
| 239 |
-
|
| 240 |
-
# Reference complete WebSocket architecture
|
| 241 |
-
@AXIOM_WEBSOCKET_ARCHITECTURE.md
|
| 242 |
-
```
|
| 243 |
-
|
| 244 |
-
---
|
| 245 |
-
|
| 246 |
-
**Remember**: This is the ONLY evolving file. Update progress here as you complete each step.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Survey Simulator (Local Guide)
|
| 2 |
+
|
| 3 |
+
Welcome! This guide walks you through running the AI Survey Simulator locally so you can evaluate the interviewer/patient conversation flow without digging into the implementation details.
|
| 4 |
+
|
| 5 |
+
If you are looking for architecture deep dives or change history, head to `docs/` where all developer-facing material now lives.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## What You Get
|
| 10 |
+
|
| 11 |
+
- A Gradio web interface to monitor and control AI-to-AI healthcare survey conversations
|
| 12 |
+
- A FastAPI backend that orchestrates personas, manages the conversation state, and serves WebSocket updates
|
| 13 |
+
- Out-of-the-box personas for a surveyor and multiple patient profiles stored in `data/`
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## Prerequisites
|
| 18 |
+
|
| 19 |
+
1. **Python 3.9+** installed (`python --version`)
|
| 20 |
+
2. **Pip** available (`pip --version`)
|
| 21 |
+
3. **Ollama** running locally with an accessible model (e.g., `llama3.2:latest`), since the simulator calls the LLM through Ollama by default
|
| 22 |
+
- Install instructions: <https://ollama.ai>
|
| 23 |
+
- Pull a model: `ollama pull llama3.2:latest`
|
| 24 |
+
- Verify: `ollama list`
|
| 25 |
+
|
| 26 |
+
> ℹ️ We are actively planning support for hosted LLM providers. When that lands you will be able to configure the app via environment variables instead of relying on a local Ollama instance.
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## 1. Configure Environment Variables
|
| 31 |
+
|
| 32 |
+
Duplicate the sample configuration and adjust if necessary:
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
cp .env.example .env
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
Key values:
|
| 39 |
+
|
| 40 |
+
- `LLM_HOST` / `LLM_MODEL` — where the backend reaches your Ollama model
|
| 41 |
+
- `FRONTEND_BACKEND_BASE_URL` — the FastAPI base URL Gradio should call
|
| 42 |
+
- `FRONTEND_WEBSOCKET_URL` — the WebSocket endpoint prefix (without conversation id)
|
| 43 |
+
|
| 44 |
+
You can accept the defaults for a local run. Update them later if you move the backend or change models.
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
## 2. Set Up the Python Environment
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
git clone <repository-url>
|
| 52 |
+
cd ConversationAI
|
| 53 |
+
|
| 54 |
+
# (optional but recommended)
|
| 55 |
+
python -m venv .venv
|
| 56 |
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
| 57 |
+
|
| 58 |
+
pip install -r requirements.txt
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## 3. Start the Backend (FastAPI)
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
cd backend
|
| 67 |
+
uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
Keep this terminal running. The backend exposes REST endpoints under `http://localhost:8000` and a WebSocket endpoint the UI listens to.
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
## 4. Launch the Gradio Frontend
|
| 75 |
+
|
| 76 |
+
In a new terminal (activate the virtual environment again if you created one):
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
cd frontend
|
| 80 |
+
python gradio_app.py
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
Gradio starts on <http://localhost:7860>. Open that page in your browser.
|
| 84 |
+
|
| 85 |
+
---
|
| 86 |
+
|
| 87 |
+
## 5. Run a Conversation
|
| 88 |
+
|
| 89 |
+
1. Click **“Start Conversation”** — the app will connect to the backend automatically and begin the AI interview flow. Messages appear in the “Live AI Conversation” panel.
|
| 90 |
+
2. Watch the conversation update automatically (the UI polls once per second).
|
| 91 |
+
3. Click **“Stop Conversation”** when you are done.
|
| 92 |
+
|
| 93 |
+
If the backend or Ollama becomes unreachable, the status box will show an error message so you know where to look first.
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## Personas
|
| 98 |
+
|
| 99 |
+
- Surveyor profiles live in `data/surveyor_personas.yaml`
|
| 100 |
+
- Patient profiles live in `data/patient_personas.yaml`
|
| 101 |
+
|
| 102 |
+
To tweak a persona for experimentation:
|
| 103 |
+
|
| 104 |
+
1. Edit the YAML entry (name, tone, system prompt, etc.)
|
| 105 |
+
2. Restart the backend so it reloads the definitions
|
| 106 |
+
|
| 107 |
+
We are working on UI controls to swap personas without editing files—stay tuned.
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Advanced Configuration
|
| 112 |
+
|
| 113 |
+
- Change the log verbosity by setting `LOG_LEVEL=DEBUG` in `.env`
|
| 114 |
+
- Point to a different LLM host/model using the `LLM_*` variables
|
| 115 |
+
- When we introduce hosted-model support, the same `.env` file will control which backend is used without code edits
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## Quick Start Script
|
| 120 |
+
|
| 121 |
+
Prefer a single command? Run:
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
./run_local.sh
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
The script will:
|
| 128 |
+
|
| 129 |
+
- Load environment variables from `.env`
|
| 130 |
+
- Start `ollama serve` (if it is not already running)
|
| 131 |
+
- Launch the FastAPI backend and Gradio frontend in the background
|
| 132 |
+
|
| 133 |
+
Press `Ctrl+C` in that terminal to shut everything down cleanly.
|
| 134 |
+
If you want to watch live logs, run the backend/frontend commands manually in separate terminals instead of using this helper.
|
| 135 |
+
|
| 136 |
+
---
|
| 137 |
+
|
| 138 |
+
## Helpful Scripts
|
| 139 |
+
|
| 140 |
+
- `run_local.sh` — start/stop the full local stack with one command
|
| 141 |
+
- `dev_setup.sh` — (planned) install dependencies and verify prerequisites
|
| 142 |
+
- Smoke tests with mocked LLM responses — (planned)
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## Need Implementation Details?
|
| 147 |
+
|
| 148 |
+
For deeper implementation notes, visit the developer docs:
|
| 149 |
+
|
| 150 |
+
- `docs/overview.md`
|
| 151 |
+
- `docs/development.md`
|
| 152 |
+
- `docs/roadmap.md`
|
| 153 |
+
|
| 154 |
+
---
|
| 155 |
+
|
| 156 |
+
Happy testing! If you run into issues, capture the console output from both backend and frontend terminals—it usually reveals configuration or network problems quickly.
|
backend/api/{services/conversation_service.py → conversation_service.py}
RENAMED
|
@@ -19,18 +19,23 @@ Example:
|
|
| 19 |
import asyncio
|
| 20 |
import logging
|
| 21 |
from datetime import datetime
|
| 22 |
-
from typing import Dict, Optional
|
| 23 |
from dataclasses import dataclass
|
| 24 |
from enum import Enum
|
| 25 |
import sys
|
| 26 |
from pathlib import Path
|
| 27 |
|
| 28 |
-
# Add backend to path for imports
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
from
|
| 32 |
-
from core.
|
| 33 |
-
from
|
|
|
|
| 34 |
|
| 35 |
# Setup logging
|
| 36 |
logger = logging.getLogger(__name__)
|
|
@@ -68,24 +73,27 @@ class ConversationService:
|
|
| 68 |
websocket_manager: WebSocket connection manager for broadcasting
|
| 69 |
persona_system: Persona system for loading personas
|
| 70 |
active_conversations: Dict of active conversation instances
|
|
|
|
| 71 |
"""
|
| 72 |
|
| 73 |
-
def __init__(self, websocket_manager: ConnectionManager):
|
| 74 |
"""Initialize conversation service.
|
| 75 |
|
| 76 |
Args:
|
| 77 |
websocket_manager: WebSocket manager for message broadcasting
|
|
|
|
| 78 |
"""
|
| 79 |
self.websocket_manager = websocket_manager
|
| 80 |
self.persona_system = PersonaSystem()
|
| 81 |
self.active_conversations: Dict[str, ConversationInfo] = {}
|
|
|
|
| 82 |
|
| 83 |
async def start_conversation(self,
|
| 84 |
conversation_id: str,
|
| 85 |
surveyor_persona_id: str,
|
| 86 |
patient_persona_id: str,
|
| 87 |
-
host: str =
|
| 88 |
-
model: str =
|
| 89 |
"""Start a new AI-to-AI conversation.
|
| 90 |
|
| 91 |
Args:
|
|
@@ -114,6 +122,10 @@ class ConversationService:
|
|
| 114 |
await self._send_error(conversation_id, "Invalid persona IDs")
|
| 115 |
return False
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
# Create conversation info
|
| 118 |
conv_info = ConversationInfo(
|
| 119 |
conversation_id=conversation_id,
|
|
@@ -132,8 +144,8 @@ class ConversationService:
|
|
| 132 |
manager = ConversationManager(
|
| 133 |
surveyor_persona=surveyor_persona,
|
| 134 |
patient_persona=patient_persona,
|
| 135 |
-
host=
|
| 136 |
-
model=
|
| 137 |
)
|
| 138 |
|
| 139 |
# Start conversation streaming task
|
|
@@ -346,12 +358,13 @@ def get_conversation_service() -> ConversationService:
|
|
| 346 |
return conversation_service
|
| 347 |
|
| 348 |
|
| 349 |
-
def initialize_conversation_service(websocket_manager: ConnectionManager):
|
| 350 |
"""Initialize the global conversation service.
|
| 351 |
|
| 352 |
Args:
|
| 353 |
websocket_manager: WebSocket connection manager
|
|
|
|
| 354 |
"""
|
| 355 |
global conversation_service
|
| 356 |
-
conversation_service = ConversationService(websocket_manager)
|
| 357 |
-
logger.info("ConversationService initialized")
|
|
|
|
| 19 |
import asyncio
|
| 20 |
import logging
|
| 21 |
from datetime import datetime
|
| 22 |
+
from typing import Dict, Optional
|
| 23 |
from dataclasses import dataclass
|
| 24 |
from enum import Enum
|
| 25 |
import sys
|
| 26 |
from pathlib import Path
|
| 27 |
|
| 28 |
+
# Add backend and project root to path for imports
|
| 29 |
+
BACKEND_DIR = Path(__file__).resolve().parents[2]
|
| 30 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
| 31 |
+
for path in (BACKEND_DIR, PROJECT_ROOT):
|
| 32 |
+
if str(path) not in sys.path:
|
| 33 |
+
sys.path.insert(0, str(path))
|
| 34 |
|
| 35 |
+
from config.settings import AppSettings, get_settings # noqa: E402
|
| 36 |
+
from core.conversation_manager import ConversationManager # noqa: E402
|
| 37 |
+
from core.persona_system import PersonaSystem # noqa: E402
|
| 38 |
+
from .conversation_ws import ConnectionManager # noqa: E402
|
| 39 |
|
| 40 |
# Setup logging
|
| 41 |
logger = logging.getLogger(__name__)
|
|
|
|
| 73 |
websocket_manager: WebSocket connection manager for broadcasting
|
| 74 |
persona_system: Persona system for loading personas
|
| 75 |
active_conversations: Dict of active conversation instances
|
| 76 |
+
settings: Shared application settings
|
| 77 |
"""
|
| 78 |
|
| 79 |
+
def __init__(self, websocket_manager: ConnectionManager, settings: Optional[AppSettings] = None):
|
| 80 |
"""Initialize conversation service.
|
| 81 |
|
| 82 |
Args:
|
| 83 |
websocket_manager: WebSocket manager for message broadcasting
|
| 84 |
+
settings: Shared application settings (optional)
|
| 85 |
"""
|
| 86 |
self.websocket_manager = websocket_manager
|
| 87 |
self.persona_system = PersonaSystem()
|
| 88 |
self.active_conversations: Dict[str, ConversationInfo] = {}
|
| 89 |
+
self.settings = settings or get_settings()
|
| 90 |
|
| 91 |
async def start_conversation(self,
|
| 92 |
conversation_id: str,
|
| 93 |
surveyor_persona_id: str,
|
| 94 |
patient_persona_id: str,
|
| 95 |
+
host: Optional[str] = None,
|
| 96 |
+
model: Optional[str] = None) -> bool:
|
| 97 |
"""Start a new AI-to-AI conversation.
|
| 98 |
|
| 99 |
Args:
|
|
|
|
| 122 |
await self._send_error(conversation_id, "Invalid persona IDs")
|
| 123 |
return False
|
| 124 |
|
| 125 |
+
# Resolve LLM configuration
|
| 126 |
+
resolved_host = host or self.settings.llm.host
|
| 127 |
+
resolved_model = model or self.settings.llm.model
|
| 128 |
+
|
| 129 |
# Create conversation info
|
| 130 |
conv_info = ConversationInfo(
|
| 131 |
conversation_id=conversation_id,
|
|
|
|
| 144 |
manager = ConversationManager(
|
| 145 |
surveyor_persona=surveyor_persona,
|
| 146 |
patient_persona=patient_persona,
|
| 147 |
+
host=resolved_host,
|
| 148 |
+
model=resolved_model
|
| 149 |
)
|
| 150 |
|
| 151 |
# Start conversation streaming task
|
|
|
|
| 358 |
return conversation_service
|
| 359 |
|
| 360 |
|
| 361 |
+
def initialize_conversation_service(websocket_manager: ConnectionManager, settings: Optional[AppSettings] = None):
|
| 362 |
"""Initialize the global conversation service.
|
| 363 |
|
| 364 |
Args:
|
| 365 |
websocket_manager: WebSocket connection manager
|
| 366 |
+
settings: Shared application settings (optional)
|
| 367 |
"""
|
| 368 |
global conversation_service
|
| 369 |
+
conversation_service = ConversationService(websocket_manager, settings=settings)
|
| 370 |
+
logger.info("ConversationService initialized")
|
backend/api/{websockets/conversation_ws.py → conversation_ws.py}
RENAMED
|
@@ -238,7 +238,7 @@ async def handle_conversation_control(data: dict, conversation_id: str):
|
|
| 238 |
|
| 239 |
try:
|
| 240 |
# Import here to avoid circular imports
|
| 241 |
-
from
|
| 242 |
service = get_conversation_service()
|
| 243 |
|
| 244 |
if control_action == "stop":
|
|
@@ -295,14 +295,14 @@ async def handle_start_conversation(data: dict, conversation_id: str):
|
|
| 295 |
"""
|
| 296 |
try:
|
| 297 |
# Import here to avoid circular imports
|
| 298 |
-
from
|
| 299 |
service = get_conversation_service()
|
| 300 |
|
| 301 |
# Extract required fields
|
| 302 |
surveyor_persona_id = data.get("surveyor_persona_id")
|
| 303 |
patient_persona_id = data.get("patient_persona_id")
|
| 304 |
-
host = data.get("host"
|
| 305 |
-
model = data.get("model"
|
| 306 |
|
| 307 |
if not surveyor_persona_id or not patient_persona_id:
|
| 308 |
await manager.send_to_conversation(conversation_id, {
|
|
@@ -340,4 +340,4 @@ async def handle_start_conversation(data: dict, conversation_id: str):
|
|
| 340 |
|
| 341 |
|
| 342 |
# Export the manager for use in other modules
|
| 343 |
-
__all__ = ["websocket_endpoint", "manager"]
|
|
|
|
| 238 |
|
| 239 |
try:
|
| 240 |
# Import here to avoid circular imports
|
| 241 |
+
from .conversation_service import get_conversation_service
|
| 242 |
service = get_conversation_service()
|
| 243 |
|
| 244 |
if control_action == "stop":
|
|
|
|
| 295 |
"""
|
| 296 |
try:
|
| 297 |
# Import here to avoid circular imports
|
| 298 |
+
from .conversation_service import get_conversation_service
|
| 299 |
service = get_conversation_service()
|
| 300 |
|
| 301 |
# Extract required fields
|
| 302 |
surveyor_persona_id = data.get("surveyor_persona_id")
|
| 303 |
patient_persona_id = data.get("patient_persona_id")
|
| 304 |
+
host = data.get("host")
|
| 305 |
+
model = data.get("model")
|
| 306 |
|
| 307 |
if not surveyor_persona_id or not patient_persona_id:
|
| 308 |
await manager.send_to_conversation(conversation_id, {
|
|
|
|
| 340 |
|
| 341 |
|
| 342 |
# Export the manager for use in other modules
|
| 343 |
+
__all__ = ["websocket_endpoint", "manager"]
|
backend/api/main.py
CHANGED
|
@@ -11,18 +11,32 @@ Typical usage:
|
|
| 11 |
uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 12 |
"""
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
from fastapi import FastAPI, WebSocket
|
| 15 |
from fastapi.middleware.cors import CORSMiddleware
|
| 16 |
import uvicorn
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
# Import WebSocket endpoint and manager
|
| 20 |
-
from .
|
| 21 |
-
from .routes
|
| 22 |
-
from .
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
# Setup logging
|
| 25 |
-
logging.
|
|
|
|
| 26 |
logger = logging.getLogger(__name__)
|
| 27 |
|
| 28 |
# Initialize FastAPI app
|
|
@@ -53,8 +67,8 @@ async def startup_event():
|
|
| 53 |
"""Initialize services on startup."""
|
| 54 |
logger.info("Initializing AI Survey Simulator API...")
|
| 55 |
|
| 56 |
-
# Initialize conversation service with WebSocket manager
|
| 57 |
-
initialize_conversation_service(manager)
|
| 58 |
|
| 59 |
logger.info("API startup complete")
|
| 60 |
|
|
@@ -88,4 +102,4 @@ async def websocket_conversation_endpoint(websocket: WebSocket, conversation_id:
|
|
| 88 |
|
| 89 |
|
| 90 |
if __name__ == "__main__":
|
| 91 |
-
uvicorn.run(app, host=
|
|
|
|
| 11 |
uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 12 |
"""
|
| 13 |
|
| 14 |
+
import logging
|
| 15 |
+
import sys
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
from fastapi import FastAPI, WebSocket
|
| 19 |
from fastapi.middleware.cors import CORSMiddleware
|
| 20 |
import uvicorn
|
| 21 |
+
|
| 22 |
+
# Ensure project root is available for shared config imports
|
| 23 |
+
ROOT_DIR = Path(__file__).resolve().parents[2]
|
| 24 |
+
if str(ROOT_DIR) not in sys.path:
|
| 25 |
+
sys.path.insert(0, str(ROOT_DIR))
|
| 26 |
+
|
| 27 |
+
from config.settings import get_settings # noqa: E402
|
| 28 |
|
| 29 |
# Import WebSocket endpoint and manager
|
| 30 |
+
from .conversation_ws import websocket_endpoint, manager # noqa: E402
|
| 31 |
+
from .routes import router as conversations_router # noqa: E402
|
| 32 |
+
from .conversation_service import initialize_conversation_service # noqa: E402
|
| 33 |
+
|
| 34 |
+
# Load application settings
|
| 35 |
+
settings = get_settings()
|
| 36 |
|
| 37 |
+
# Setup logging using configured level
|
| 38 |
+
log_level = getattr(logging, settings.log_level.upper(), logging.INFO)
|
| 39 |
+
logging.basicConfig(level=log_level)
|
| 40 |
logger = logging.getLogger(__name__)
|
| 41 |
|
| 42 |
# Initialize FastAPI app
|
|
|
|
| 67 |
"""Initialize services on startup."""
|
| 68 |
logger.info("Initializing AI Survey Simulator API...")
|
| 69 |
|
| 70 |
+
# Initialize conversation service with WebSocket manager and settings
|
| 71 |
+
initialize_conversation_service(manager, settings)
|
| 72 |
|
| 73 |
logger.info("API startup complete")
|
| 74 |
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
if __name__ == "__main__":
|
| 105 |
+
uvicorn.run(app, host=settings.api.host, port=settings.api.port)
|
backend/api/{routes/conversations.py → routes.py}
RENAMED
|
@@ -23,13 +23,7 @@ from fastapi import APIRouter, HTTPException, BackgroundTasks
|
|
| 23 |
from pydantic import BaseModel, Field
|
| 24 |
from typing import Dict, List, Optional
|
| 25 |
import logging
|
| 26 |
-
import
|
| 27 |
-
from pathlib import Path
|
| 28 |
-
|
| 29 |
-
# Add backend to path for imports
|
| 30 |
-
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
| 31 |
-
|
| 32 |
-
from api.services.conversation_service import get_conversation_service
|
| 33 |
from core.persona_system import PersonaSystem
|
| 34 |
|
| 35 |
# Setup logging
|
|
@@ -45,8 +39,8 @@ class StartConversationRequest(BaseModel):
|
|
| 45 |
conversation_id: str = Field(..., description="Unique identifier for the conversation")
|
| 46 |
surveyor_persona_id: str = Field(..., description="ID of the surveyor persona")
|
| 47 |
patient_persona_id: str = Field(..., description="ID of the patient persona")
|
| 48 |
-
host: str = Field(default=
|
| 49 |
-
model: str = Field(default=
|
| 50 |
|
| 51 |
|
| 52 |
class ConversationStatusResponse(BaseModel):
|
|
@@ -284,4 +278,4 @@ async def health_check() -> Dict[str, str]:
|
|
| 284 |
return {
|
| 285 |
"status": "unhealthy",
|
| 286 |
"error": str(e)
|
| 287 |
-
}
|
|
|
|
| 23 |
from pydantic import BaseModel, Field
|
| 24 |
from typing import Dict, List, Optional
|
| 25 |
import logging
|
| 26 |
+
from .conversation_service import get_conversation_service
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
from core.persona_system import PersonaSystem
|
| 28 |
|
| 29 |
# Setup logging
|
|
|
|
| 39 |
conversation_id: str = Field(..., description="Unique identifier for the conversation")
|
| 40 |
surveyor_persona_id: str = Field(..., description="ID of the surveyor persona")
|
| 41 |
patient_persona_id: str = Field(..., description="ID of the patient persona")
|
| 42 |
+
host: Optional[str] = Field(default=None, description="Override LLM host to use")
|
| 43 |
+
model: Optional[str] = Field(default=None, description="Override LLM model to use")
|
| 44 |
|
| 45 |
|
| 46 |
class ConversationStatusResponse(BaseModel):
|
|
|
|
| 278 |
return {
|
| 279 |
"status": "unhealthy",
|
| 280 |
"error": str(e)
|
| 281 |
+
}
|
backend/core/conversation_manager.py
CHANGED
|
@@ -62,7 +62,7 @@ class ConversationManager:
|
|
| 62 |
surveyor_persona: dict = None,
|
| 63 |
patient_persona: dict = None,
|
| 64 |
host: str = "http://localhost:11434",
|
| 65 |
-
model: str = "
|
| 66 |
"""Initialize conversation manager with personas.
|
| 67 |
|
| 68 |
Args:
|
|
|
|
| 62 |
surveyor_persona: dict = None,
|
| 63 |
patient_persona: dict = None,
|
| 64 |
host: str = "http://localhost:11434",
|
| 65 |
+
model: str = "llama3.2:latest"):
|
| 66 |
"""Initialize conversation manager with personas.
|
| 67 |
|
| 68 |
Args:
|
backend/core/llm_client.py
CHANGED
|
@@ -14,7 +14,7 @@ Classes:
|
|
| 14 |
VLLMClient: Client for vLLM backend
|
| 15 |
|
| 16 |
Example:
|
| 17 |
-
client = OllamaClient(host="http://localhost:11434", model="
|
| 18 |
response = await client.generate(prompt="Hello", system_prompt="You are helpful")
|
| 19 |
"""
|
| 20 |
|
|
@@ -182,7 +182,7 @@ class OllamaClient(LLMClient):
|
|
| 182 |
Returns:
|
| 183 |
Generated text response
|
| 184 |
"""
|
| 185 |
-
async def
|
| 186 |
messages = []
|
| 187 |
if system_prompt:
|
| 188 |
messages.append({"role": "system", "content": system_prompt})
|
|
@@ -220,7 +220,38 @@ class OllamaClient(LLMClient):
|
|
| 220 |
|
| 221 |
return data["message"]["content"]
|
| 222 |
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
async def health_check(self) -> Dict[str, Any]:
|
| 226 |
"""Check if Ollama server is healthy and list available models.
|
|
@@ -336,7 +367,7 @@ def create_llm_client_from_config(config_path: Optional[str] = None,
|
|
| 336 |
config = {
|
| 337 |
"backend": "ollama",
|
| 338 |
"host": "http://localhost:11434",
|
| 339 |
-
"model": "
|
| 340 |
"timeout": 120,
|
| 341 |
"max_retries": 3,
|
| 342 |
"retry_delay": 1.0
|
|
@@ -432,4 +463,4 @@ async def test_llm_connection(client: LLMClient) -> Dict[str, Any]:
|
|
| 432 |
results["error"] = str(e)
|
| 433 |
logger.error(f"LLM connection test failed: {e}")
|
| 434 |
|
| 435 |
-
return results
|
|
|
|
| 14 |
VLLMClient: Client for vLLM backend
|
| 15 |
|
| 16 |
Example:
|
| 17 |
+
client = OllamaClient(host="http://localhost:11434", model="llama3.2:latest")
|
| 18 |
response = await client.generate(prompt="Hello", system_prompt="You are helpful")
|
| 19 |
"""
|
| 20 |
|
|
|
|
| 182 |
Returns:
|
| 183 |
Generated text response
|
| 184 |
"""
|
| 185 |
+
async def _make_chat_request():
|
| 186 |
messages = []
|
| 187 |
if system_prompt:
|
| 188 |
messages.append({"role": "system", "content": system_prompt})
|
|
|
|
| 220 |
|
| 221 |
return data["message"]["content"]
|
| 222 |
|
| 223 |
+
async def _make_generate_request():
|
| 224 |
+
payload = {
|
| 225 |
+
"model": self.model,
|
| 226 |
+
"prompt": prompt,
|
| 227 |
+
"stream": False,
|
| 228 |
+
**kwargs
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
# Preserve system prompt behavior by prepending when using generate endpoint
|
| 232 |
+
if system_prompt:
|
| 233 |
+
payload["system"] = system_prompt
|
| 234 |
+
|
| 235 |
+
response = await self.client.post(
|
| 236 |
+
f"{self.host}/api/generate",
|
| 237 |
+
json=payload
|
| 238 |
+
)
|
| 239 |
+
response.raise_for_status()
|
| 240 |
+
data = response.json()
|
| 241 |
+
|
| 242 |
+
# Track token usage if available (some versions omit this field)
|
| 243 |
+
if "prompt_eval_count" in data:
|
| 244 |
+
self.total_tokens += data.get("prompt_eval_count", 0) + data.get("eval_count", 0)
|
| 245 |
+
|
| 246 |
+
return data.get("response") or data.get("generated_text", "")
|
| 247 |
+
|
| 248 |
+
try:
|
| 249 |
+
return await self._retry_request(_make_chat_request)
|
| 250 |
+
except httpx.HTTPStatusError as exc:
|
| 251 |
+
if exc.response is not None and exc.response.status_code == 404:
|
| 252 |
+
logger.warning("Ollama /api/chat endpoint not found. Falling back to /api/generate")
|
| 253 |
+
return await self._retry_request(_make_generate_request)
|
| 254 |
+
raise
|
| 255 |
|
| 256 |
async def health_check(self) -> Dict[str, Any]:
|
| 257 |
"""Check if Ollama server is healthy and list available models.
|
|
|
|
| 367 |
config = {
|
| 368 |
"backend": "ollama",
|
| 369 |
"host": "http://localhost:11434",
|
| 370 |
+
"model": "llama3.2:latest",
|
| 371 |
"timeout": 120,
|
| 372 |
"max_retries": 3,
|
| 373 |
"retry_delay": 1.0
|
|
|
|
| 463 |
results["error"] = str(e)
|
| 464 |
logger.error(f"LLM connection test failed: {e}")
|
| 465 |
|
| 466 |
+
return results
|
backend/core/persona_system.py
CHANGED
|
@@ -139,8 +139,8 @@ class PersonaSystem:
|
|
| 139 |
personas_dir: Directory containing persona YAML files
|
| 140 |
"""
|
| 141 |
if personas_dir is None:
|
| 142 |
-
# Default to data
|
| 143 |
-
personas_dir = Path(__file__).parent.parent.parent / "data"
|
| 144 |
|
| 145 |
self.personas_dir = Path(personas_dir)
|
| 146 |
self.personas: Dict[str, Dict[str, Any]] = {}
|
|
@@ -353,4 +353,4 @@ def get_persona_system() -> PersonaSystem:
|
|
| 353 |
global _persona_system
|
| 354 |
if _persona_system is None:
|
| 355 |
_persona_system = PersonaSystem()
|
| 356 |
-
return _persona_system
|
|
|
|
| 139 |
personas_dir: Directory containing persona YAML files
|
| 140 |
"""
|
| 141 |
if personas_dir is None:
|
| 142 |
+
# Default to data directory
|
| 143 |
+
personas_dir = Path(__file__).parent.parent.parent / "data"
|
| 144 |
|
| 145 |
self.personas_dir = Path(personas_dir)
|
| 146 |
self.personas: Dict[str, Dict[str, Any]] = {}
|
|
|
|
| 353 |
global _persona_system
|
| 354 |
if _persona_system is None:
|
| 355 |
_persona_system = PersonaSystem()
|
| 356 |
+
return _persona_system
|
config/default_config.yaml
CHANGED
|
@@ -29,7 +29,7 @@ llm:
|
|
| 29 |
# Ollama Configuration
|
| 30 |
ollama:
|
| 31 |
host: "http://localhost:11434"
|
| 32 |
-
model: "
|
| 33 |
timeout: 120 # seconds
|
| 34 |
|
| 35 |
# vLLM Configuration
|
|
|
|
| 29 |
# Ollama Configuration
|
| 30 |
ollama:
|
| 31 |
host: "http://localhost:11434"
|
| 32 |
+
model: "llama3.2:latest"
|
| 33 |
timeout: 120 # seconds
|
| 34 |
|
| 35 |
# vLLM Configuration
|
config/settings.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Centralized application settings loaded from environment variables.
|
| 2 |
+
|
| 3 |
+
Uses pydantic-settings so both backend and frontend can share defaults and
|
| 4 |
+
override them through a `.env` file or process environment variables.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class APISettings(BaseSettings):
|
| 12 |
+
"""Configuration for the FastAPI backend."""
|
| 13 |
+
|
| 14 |
+
host: str = "0.0.0.0"
|
| 15 |
+
port: int = 8000
|
| 16 |
+
log_level: str = "INFO"
|
| 17 |
+
|
| 18 |
+
model_config = SettingsConfigDict(
|
| 19 |
+
env_prefix="API_",
|
| 20 |
+
env_file=".env",
|
| 21 |
+
env_file_encoding="utf-8",
|
| 22 |
+
extra="ignore",
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class LLMSettings(BaseSettings):
|
| 27 |
+
"""Configuration for the language model backend."""
|
| 28 |
+
|
| 29 |
+
backend: str = "ollama"
|
| 30 |
+
host: str = "http://localhost:11434"
|
| 31 |
+
model: str = "llama3.2:latest"
|
| 32 |
+
timeout: int = 120
|
| 33 |
+
|
| 34 |
+
model_config = SettingsConfigDict(
|
| 35 |
+
env_prefix="LLM_",
|
| 36 |
+
env_file=".env",
|
| 37 |
+
env_file_encoding="utf-8",
|
| 38 |
+
extra="ignore",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class FrontendSettings(BaseSettings):
|
| 43 |
+
"""Configuration for the Gradio frontend."""
|
| 44 |
+
|
| 45 |
+
backend_base_url: str = "http://localhost:8000"
|
| 46 |
+
websocket_url: str = "ws://localhost:8000/ws/conversation"
|
| 47 |
+
|
| 48 |
+
model_config = SettingsConfigDict(
|
| 49 |
+
env_prefix="FRONTEND_",
|
| 50 |
+
env_file=".env",
|
| 51 |
+
env_file_encoding="utf-8",
|
| 52 |
+
extra="ignore",
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class AppSettings(BaseSettings):
|
| 57 |
+
"""Aggregate configuration exposed to the application."""
|
| 58 |
+
|
| 59 |
+
api: APISettings = APISettings()
|
| 60 |
+
llm: LLMSettings = LLMSettings()
|
| 61 |
+
frontend: FrontendSettings = FrontendSettings()
|
| 62 |
+
log_level: str = "INFO"
|
| 63 |
+
|
| 64 |
+
model_config = SettingsConfigDict(
|
| 65 |
+
env_file=".env",
|
| 66 |
+
env_file_encoding="utf-8",
|
| 67 |
+
extra="ignore",
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@lru_cache
|
| 72 |
+
def get_settings() -> AppSettings:
|
| 73 |
+
"""Return the singleton settings instance."""
|
| 74 |
+
return AppSettings()
|
data/{personas/patient_personas.yaml → patient_personas.yaml}
RENAMED
|
File without changes
|
data/{personas/surveyor_personas.yaml → surveyor_personas.yaml}
RENAMED
|
File without changes
|
docs/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Developer Documentation Index
|
| 2 |
+
|
| 3 |
+
These short guides are all you need to extend the AI Survey Simulator:
|
| 4 |
+
|
| 5 |
+
- `overview.md` — architecture summary, major components, and repository map.
|
| 6 |
+
- `development.md` — setup, runtime instructions, and implementation guidelines.
|
| 7 |
+
- `roadmap.md` — current status and prioritized future work.
|
| 8 |
+
|
| 9 |
+
Keep documentation lean: update the relevant file when behavior changes or priorities shift.
|
docs/development.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide
|
| 2 |
+
|
| 3 |
+
This guide captures what future contributors need to know to extend the AI Survey Simulator quickly.
|
| 4 |
+
|
| 5 |
+
## Environment Essentials
|
| 6 |
+
|
| 7 |
+
- Python 3.9+
|
| 8 |
+
- Ollama running locally (or another LLM provider wired into `llm_client.py`)
|
| 9 |
+
- Optional GPU for faster inference
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
cp .env.example .env # adjust values as needed
|
| 13 |
+
pip install -r requirements.txt
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
Key environment variables (see `.env.example`):
|
| 17 |
+
|
| 18 |
+
- `LLM_HOST` / `LLM_MODEL` — target model endpoint
|
| 19 |
+
- `FRONTEND_BACKEND_BASE_URL` and `FRONTEND_WEBSOCKET_URL` — how the UI talks to FastAPI
|
| 20 |
+
- `LOG_LEVEL` — INFO by default
|
| 21 |
+
|
| 22 |
+
## Running the Stack
|
| 23 |
+
|
| 24 |
+
### One Command
|
| 25 |
+
```bash
|
| 26 |
+
./run_local.sh
|
| 27 |
+
```
|
| 28 |
+
- Starts `ollama serve` (if not already running)
|
| 29 |
+
- Launches FastAPI backend and Gradio frontend in the background
|
| 30 |
+
- Press `Ctrl+C` to stop all three processes
|
| 31 |
+
|
| 32 |
+
### Manual Terminals (for logs)
|
| 33 |
+
```bash
|
| 34 |
+
# Terminal 1
|
| 35 |
+
ollama serve
|
| 36 |
+
|
| 37 |
+
# Terminal 2
|
| 38 |
+
cd backend
|
| 39 |
+
uvicorn api.main:app --reload --host 0.0.0.0 --port 8000
|
| 40 |
+
|
| 41 |
+
# Terminal 3
|
| 42 |
+
cd frontend
|
| 43 |
+
python gradio_app.py
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
## Making Changes Safely
|
| 47 |
+
|
| 48 |
+
- Prefer editing personas via YAML (`data/`) and restart the backend to reload.
|
| 49 |
+
- All configuration flows through `config/settings.py`; add new settings there and reference them via `get_settings()`.
|
| 50 |
+
- When adding LLM providers, implement a new client in `backend/core/llm_client.py` and hook it into the existing factory.
|
| 51 |
+
- Keep WebSocket message schemas stable (`backend/api/conversation_ws.py`); update both backend and frontend consumers if you change them.
|
| 52 |
+
|
| 53 |
+
## Testing & Verification
|
| 54 |
+
|
| 55 |
+
- No automated test suite yet. Add lightweight `pytest` modules under `tests/` as you extend functionality.
|
| 56 |
+
- Manually verify conversations through the Gradio UI.
|
| 57 |
+
- If you need to debug the conversation loop, instrument `backend/core/conversation_manager.py` or launch a shell and run it directly.
|
| 58 |
+
|
| 59 |
+
## Roadmap & Next Steps
|
| 60 |
+
|
| 61 |
+
See `docs/roadmap.md` for current priorities, open questions, and suggested next features (persona selector UI, hosted LLM support, etc.).
|
docs/overview.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# System Overview
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
|
| 5 |
+
The AI Survey Simulator orchestrates AI-to-AI healthcare survey conversations so researchers can explore interviewer and patient persona behavior without involving real participants.
|
| 6 |
+
|
| 7 |
+
## Architecture at a Glance
|
| 8 |
+
|
| 9 |
+
- **Gradio Frontend (`frontend/`)**
|
| 10 |
+
Presents the control panel, connects to the backend via WebSocket, and renders streaming messages.
|
| 11 |
+
|
| 12 |
+
- **FastAPI Backend (`backend/api/`)**
|
| 13 |
+
Hosts REST endpoints for conversation control, WebSocket endpoints for live streaming, and the conversation service that manages active sessions.
|
| 14 |
+
|
| 15 |
+
- **Core Logic (`backend/core/`)**
|
| 16 |
+
Contains reusable building blocks: persona loading (`persona_system.py`), conversation flow management (`conversation_manager.py`), and LLM client adapters (`llm_client.py`).
|
| 17 |
+
|
| 18 |
+
- **LLM Backend (Ollama by default)**
|
| 19 |
+
The backend uses `LLM_HOST`/`LLM_MODEL` from `.env` to reach a local Ollama server. Other providers can be integrated by extending `llm_client.py`.
|
| 20 |
+
|
| 21 |
+
- **Data Assets (`data/`)**
|
| 22 |
+
Persona definitions live in YAML files (`patient_personas.yaml`, `surveyor_personas.yaml`). Update these to add or refine personas.
|
| 23 |
+
|
| 24 |
+
## Runtime Flow
|
| 25 |
+
|
| 26 |
+
1. Frontend requests a new conversation (REST) or emits `start_conversation` over WebSocket.
|
| 27 |
+
2. Backend spawns a `ConversationManager`, which alternates surveyor/patient turns using the configured LLM.
|
| 28 |
+
3. Generated messages stream back to the frontend over the WebSocket connection.
|
| 29 |
+
4. Conversation statuses and errors are broadcast so the UI can show progress and failures.
|
| 30 |
+
|
| 31 |
+
## Repository Map (Key Paths)
|
| 32 |
+
|
| 33 |
+
```
|
| 34 |
+
backend/
|
| 35 |
+
api/
|
| 36 |
+
main.py # FastAPI entry point
|
| 37 |
+
routes.py # REST endpoints
|
| 38 |
+
conversation_service.py
|
| 39 |
+
conversation_ws.py
|
| 40 |
+
core/
|
| 41 |
+
conversation_manager.py
|
| 42 |
+
persona_system.py
|
| 43 |
+
llm_client.py
|
| 44 |
+
frontend/
|
| 45 |
+
gradio_app.py
|
| 46 |
+
websocket_manager.py
|
| 47 |
+
data/
|
| 48 |
+
patient_personas.yaml
|
| 49 |
+
surveyor_personas.yaml
|
| 50 |
+
config/
|
| 51 |
+
settings.py # Shared configuration loader
|
| 52 |
+
.env.example
|
| 53 |
+
run_local.sh
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
Keep this mental model in mind when extending the simulator—it highlights where to plug in new personas, swap LLMs, or modify UI behavior.
|
docs/roadmap.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Roadmap & Status
|
| 2 |
+
|
| 3 |
+
_Last updated: 2025-11-05_
|
| 4 |
+
|
| 5 |
+
## Current Capabilities
|
| 6 |
+
|
| 7 |
+
- Gradio UI driven by WebSocket streaming
|
| 8 |
+
- FastAPI backend with conversation management service
|
| 9 |
+
- Personas defined via YAML and loaded dynamically
|
| 10 |
+
- Ollama integration with fallback to `/api/generate`
|
| 11 |
+
|
| 12 |
+
## Near-Term Priorities
|
| 13 |
+
|
| 14 |
+
1. **Persona Selection in UI**
|
| 15 |
+
Allow users to choose surveyor/patient personas from dropdowns instead of hard-coded IDs.
|
| 16 |
+
|
| 17 |
+
2. **Hosted LLM Support**
|
| 18 |
+
Add an HTTP client implementation for a cloud provider (Hugging Face Inference, OpenRouter, etc.) and expose configuration via `.env`.
|
| 19 |
+
|
| 20 |
+
3. **Basic Test Coverage**
|
| 21 |
+
Introduce smoke tests (mocked LLM responses) to prevent regressions in conversation flow.
|
| 22 |
+
|
| 23 |
+
4. **Export / Logging Enhancements**
|
| 24 |
+
Persist conversation transcripts and expose a simple export (JSON/CSV) endpoint or UI action.
|
| 25 |
+
|
| 26 |
+
## Longer-Term Ideas
|
| 27 |
+
|
| 28 |
+
- Interactive persona editor within the UI
|
| 29 |
+
- Conversation playback and analytics
|
| 30 |
+
- Multi-model comparison mode
|
| 31 |
+
- Cloud-hosted deployment (Hugging Face Spaces or similar)
|
| 32 |
+
|
| 33 |
+
## How to Contribute
|
| 34 |
+
|
| 35 |
+
1. Sync with this roadmap and open a planning thread or issue for new work.
|
| 36 |
+
2. Keep docs up to date—update this file when priorities shift.
|
| 37 |
+
3. Follow the patterns in `backend/core/` and `config/settings.py` to keep configuration centralized.
|
frontend/gradio_app.py
CHANGED
|
@@ -21,17 +21,23 @@ project_root = Path(__file__).parent.parent
|
|
| 21 |
sys.path.insert(0, str(project_root))
|
| 22 |
sys.path.insert(0, str(project_root / "frontend"))
|
| 23 |
|
|
|
|
| 24 |
from websocket_manager import WebSocketManager, ManagerState
|
| 25 |
|
| 26 |
-
#
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
logger = logging.getLogger(__name__)
|
| 29 |
|
| 30 |
# Global state
|
| 31 |
-
backend_url =
|
| 32 |
conversation_id = f"gradio_conv_{int(time.time())}"
|
| 33 |
ws_manager = None
|
| 34 |
conversation_active = False
|
|
|
|
| 35 |
|
| 36 |
# Message storage for display
|
| 37 |
all_messages = []
|
|
@@ -45,7 +51,7 @@ def initialize_websocket() -> str:
|
|
| 45 |
ws_manager.stop()
|
| 46 |
|
| 47 |
try:
|
| 48 |
-
ws_url = f"
|
| 49 |
ws_manager = WebSocketManager(ws_url, conversation_id)
|
| 50 |
|
| 51 |
success = ws_manager.start()
|
|
@@ -62,12 +68,27 @@ def initialize_websocket() -> str:
|
|
| 62 |
return f"❌ Connection error: {e}"
|
| 63 |
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
def start_conversation() -> tuple:
|
| 66 |
"""Start a new AI-to-AI conversation."""
|
| 67 |
global conversation_active, all_messages
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
|
|
|
| 71 |
|
| 72 |
if conversation_active:
|
| 73 |
return get_message_display(), "⚠️ Conversation already in progress"
|
|
@@ -82,8 +103,8 @@ def start_conversation() -> tuple:
|
|
| 82 |
"content": "start",
|
| 83 |
"surveyor_persona_id": "friendly_researcher_001",
|
| 84 |
"patient_persona_id": "cooperative_senior_001",
|
| 85 |
-
"host":
|
| 86 |
-
"model":
|
| 87 |
}
|
| 88 |
|
| 89 |
success = ws_manager.send_message(message)
|
|
@@ -91,7 +112,10 @@ def start_conversation() -> tuple:
|
|
| 91 |
if success:
|
| 92 |
conversation_active = True
|
| 93 |
logger.info("Conversation start message sent")
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
| 95 |
else:
|
| 96 |
return get_message_display(), "❌ Failed to send start message"
|
| 97 |
|
|
@@ -241,7 +265,7 @@ with gr.Blocks(title="🏥 AI Survey Simulator v2") as app:
|
|
| 241 |
# Main chat interface
|
| 242 |
chat_display = gr.Textbox(
|
| 243 |
label="Live AI Conversation",
|
| 244 |
-
value="Click '
|
| 245 |
lines=20,
|
| 246 |
max_lines=25,
|
| 247 |
interactive=False,
|
|
@@ -250,15 +274,13 @@ with gr.Blocks(title="🏥 AI Survey Simulator v2") as app:
|
|
| 250 |
|
| 251 |
# Control buttons
|
| 252 |
with gr.Row():
|
| 253 |
-
connect_btn = gr.Button("🔌 Connect to Backend", variant="secondary")
|
| 254 |
start_btn = gr.Button("▶️ Start Conversation", variant="primary")
|
| 255 |
stop_btn = gr.Button("⏹️ Stop Conversation", variant="stop")
|
| 256 |
-
refresh_btn = gr.Button("🔄 Refresh Messages", variant="secondary")
|
| 257 |
|
| 258 |
# Status message
|
| 259 |
status_msg = gr.Textbox(
|
| 260 |
label="Status Messages",
|
| 261 |
-
value="Ready to
|
| 262 |
interactive=False,
|
| 263 |
lines=2
|
| 264 |
)
|
|
@@ -276,12 +298,11 @@ with gr.Blocks(title="🏥 AI Survey Simulator v2") as app:
|
|
| 276 |
<div style="margin-top: 20px; padding: 15px; background-color: #f0f8ff; border-radius: 8px;">
|
| 277 |
<h3>📋 Instructions</h3>
|
| 278 |
<ol>
|
| 279 |
-
<li><strong>
|
| 280 |
-
<li><strong>
|
| 281 |
-
<li><strong>Refresh Messages</strong> to see new responses</li>
|
| 282 |
<li><strong>Stop</strong> when finished</li>
|
| 283 |
</ol>
|
| 284 |
-
<p><small>💡 <strong>Tip</strong>:
|
| 285 |
</div>
|
| 286 |
""")
|
| 287 |
|
|
@@ -290,16 +311,11 @@ with gr.Blocks(title="🏥 AI Survey Simulator v2") as app:
|
|
| 290 |
<strong>🔧 Requirements:</strong><br>
|
| 291 |
• Ollama server running<br>
|
| 292 |
• FastAPI backend on port 8000<br>
|
| 293 |
-
•
|
| 294 |
</div>
|
| 295 |
""")
|
| 296 |
|
| 297 |
# Event handlers
|
| 298 |
-
connect_btn.click(
|
| 299 |
-
fn=initialize_websocket,
|
| 300 |
-
outputs=[status_msg]
|
| 301 |
-
)
|
| 302 |
-
|
| 303 |
start_btn.click(
|
| 304 |
fn=start_conversation,
|
| 305 |
outputs=[chat_display, status_msg]
|
|
@@ -310,9 +326,10 @@ with gr.Blocks(title="🏥 AI Survey Simulator v2") as app:
|
|
| 310 |
outputs=[chat_display, status_msg]
|
| 311 |
)
|
| 312 |
|
| 313 |
-
|
| 314 |
fn=refresh_messages,
|
| 315 |
-
outputs=[chat_display, status_panel]
|
|
|
|
| 316 |
)
|
| 317 |
|
| 318 |
# Launch configuration
|
|
@@ -332,4 +349,4 @@ if __name__ == "__main__":
|
|
| 332 |
inbrowser=True
|
| 333 |
)
|
| 334 |
finally:
|
| 335 |
-
cleanup_on_exit()
|
|
|
|
| 21 |
sys.path.insert(0, str(project_root))
|
| 22 |
sys.path.insert(0, str(project_root / "frontend"))
|
| 23 |
|
| 24 |
+
from config.settings import get_settings
|
| 25 |
from websocket_manager import WebSocketManager, ManagerState
|
| 26 |
|
| 27 |
+
# Load shared settings
|
| 28 |
+
settings = get_settings()
|
| 29 |
+
|
| 30 |
+
# Setup logging using configured level
|
| 31 |
+
log_level = getattr(logging, settings.log_level.upper(), logging.INFO)
|
| 32 |
+
logging.basicConfig(level=log_level)
|
| 33 |
logger = logging.getLogger(__name__)
|
| 34 |
|
| 35 |
# Global state
|
| 36 |
+
backend_url = settings.frontend.backend_base_url
|
| 37 |
conversation_id = f"gradio_conv_{int(time.time())}"
|
| 38 |
ws_manager = None
|
| 39 |
conversation_active = False
|
| 40 |
+
ws_base = settings.frontend.websocket_url.rstrip("/")
|
| 41 |
|
| 42 |
# Message storage for display
|
| 43 |
all_messages = []
|
|
|
|
| 51 |
ws_manager.stop()
|
| 52 |
|
| 53 |
try:
|
| 54 |
+
ws_url = f"{ws_base}/{conversation_id}"
|
| 55 |
ws_manager = WebSocketManager(ws_url, conversation_id)
|
| 56 |
|
| 57 |
success = ws_manager.start()
|
|
|
|
| 68 |
return f"❌ Connection error: {e}"
|
| 69 |
|
| 70 |
|
| 71 |
+
def ensure_connection() -> tuple[bool, str]:
|
| 72 |
+
"""Ensure there is an active WebSocket connection."""
|
| 73 |
+
global ws_manager
|
| 74 |
+
|
| 75 |
+
if ws_manager and ws_manager.state == ManagerState.CONNECTED:
|
| 76 |
+
return True, "🟢 Connected to backend"
|
| 77 |
+
|
| 78 |
+
status_message = initialize_websocket()
|
| 79 |
+
if ws_manager and ws_manager.state == ManagerState.CONNECTED:
|
| 80 |
+
return True, status_message
|
| 81 |
+
|
| 82 |
+
return False, status_message
|
| 83 |
+
|
| 84 |
+
|
| 85 |
def start_conversation() -> tuple:
|
| 86 |
"""Start a new AI-to-AI conversation."""
|
| 87 |
global conversation_active, all_messages
|
| 88 |
|
| 89 |
+
connected, connect_message = ensure_connection()
|
| 90 |
+
if not connected:
|
| 91 |
+
return get_message_display(), connect_message
|
| 92 |
|
| 93 |
if conversation_active:
|
| 94 |
return get_message_display(), "⚠️ Conversation already in progress"
|
|
|
|
| 103 |
"content": "start",
|
| 104 |
"surveyor_persona_id": "friendly_researcher_001",
|
| 105 |
"patient_persona_id": "cooperative_senior_001",
|
| 106 |
+
"host": settings.llm.host,
|
| 107 |
+
"model": settings.llm.model
|
| 108 |
}
|
| 109 |
|
| 110 |
success = ws_manager.send_message(message)
|
|
|
|
| 112 |
if success:
|
| 113 |
conversation_active = True
|
| 114 |
logger.info("Conversation start message sent")
|
| 115 |
+
status_feedback = "✅ Conversation started! AI responses will appear below..."
|
| 116 |
+
if connect_message.startswith("✅"):
|
| 117 |
+
status_feedback = f"{connect_message}\n{status_feedback}"
|
| 118 |
+
return get_message_display(), status_feedback
|
| 119 |
else:
|
| 120 |
return get_message_display(), "❌ Failed to send start message"
|
| 121 |
|
|
|
|
| 265 |
# Main chat interface
|
| 266 |
chat_display = gr.Textbox(
|
| 267 |
label="Live AI Conversation",
|
| 268 |
+
value="Click 'Start Conversation' to begin",
|
| 269 |
lines=20,
|
| 270 |
max_lines=25,
|
| 271 |
interactive=False,
|
|
|
|
| 274 |
|
| 275 |
# Control buttons
|
| 276 |
with gr.Row():
|
|
|
|
| 277 |
start_btn = gr.Button("▶️ Start Conversation", variant="primary")
|
| 278 |
stop_btn = gr.Button("⏹️ Stop Conversation", variant="stop")
|
|
|
|
| 279 |
|
| 280 |
# Status message
|
| 281 |
status_msg = gr.Textbox(
|
| 282 |
label="Status Messages",
|
| 283 |
+
value="Ready to start a conversation.",
|
| 284 |
interactive=False,
|
| 285 |
lines=2
|
| 286 |
)
|
|
|
|
| 298 |
<div style="margin-top: 20px; padding: 15px; background-color: #f0f8ff; border-radius: 8px;">
|
| 299 |
<h3>📋 Instructions</h3>
|
| 300 |
<ol>
|
| 301 |
+
<li><strong>Start Conversation</strong> to auto-connect and begin</li>
|
| 302 |
+
<li><strong>Watch the conversation update automatically</strong></li>
|
|
|
|
| 303 |
<li><strong>Stop</strong> when finished</li>
|
| 304 |
</ol>
|
| 305 |
+
<p><small>💡 <strong>Tip</strong>: The panel refreshes once per second while connected.</small></p>
|
| 306 |
</div>
|
| 307 |
""")
|
| 308 |
|
|
|
|
| 311 |
<strong>🔧 Requirements:</strong><br>
|
| 312 |
• Ollama server running<br>
|
| 313 |
• FastAPI backend on port 8000<br>
|
| 314 |
+
• llama3.2:latest model available
|
| 315 |
</div>
|
| 316 |
""")
|
| 317 |
|
| 318 |
# Event handlers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
start_btn.click(
|
| 320 |
fn=start_conversation,
|
| 321 |
outputs=[chat_display, status_msg]
|
|
|
|
| 326 |
outputs=[chat_display, status_msg]
|
| 327 |
)
|
| 328 |
|
| 329 |
+
app.load(
|
| 330 |
fn=refresh_messages,
|
| 331 |
+
outputs=[chat_display, status_panel],
|
| 332 |
+
every=1.0
|
| 333 |
)
|
| 334 |
|
| 335 |
# Launch configuration
|
|
|
|
| 349 |
inbrowser=True
|
| 350 |
)
|
| 351 |
finally:
|
| 352 |
+
cleanup_on_exit()
|
run_local.sh
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
# Convenience launcher for the local AI Survey Simulator stack.
|
| 4 |
+
# Starts (if needed) Ollama, then the FastAPI backend, then the Gradio frontend.
|
| 5 |
+
|
| 6 |
+
set -Eeuo pipefail
|
| 7 |
+
|
| 8 |
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 9 |
+
ENV_FILE="${ROOT_DIR}/.env"
|
| 10 |
+
|
| 11 |
+
# Load environment variables if .env exists
|
| 12 |
+
if [[ -f "${ENV_FILE}" ]]; then
|
| 13 |
+
echo "🔧 Loading environment from ${ENV_FILE}"
|
| 14 |
+
set -o allexport
|
| 15 |
+
source "${ENV_FILE}"
|
| 16 |
+
set +o allexport
|
| 17 |
+
fi
|
| 18 |
+
|
| 19 |
+
API_HOST="${API_HOST:-0.0.0.0}"
|
| 20 |
+
API_PORT="${API_PORT:-8000}"
|
| 21 |
+
LOG_LEVEL="${LOG_LEVEL:-INFO}"
|
| 22 |
+
|
| 23 |
+
OLLAMA_STARTED=0
|
| 24 |
+
BACKEND_PID=0
|
| 25 |
+
FRONTEND_PID=0
|
| 26 |
+
|
| 27 |
+
cleanup() {
|
| 28 |
+
echo -e "\n🧹 Shutting down..."
|
| 29 |
+
if [[ ${FRONTEND_PID} -ne 0 ]]; then
|
| 30 |
+
echo " • Stopping frontend (PID ${FRONTEND_PID})"
|
| 31 |
+
kill "${FRONTEND_PID}" 2>/dev/null || true
|
| 32 |
+
fi
|
| 33 |
+
if [[ ${BACKEND_PID} -ne 0 ]]; then
|
| 34 |
+
echo " • Stopping backend (PID ${BACKEND_PID})"
|
| 35 |
+
kill "${BACKEND_PID}" 2>/dev/null || true
|
| 36 |
+
fi
|
| 37 |
+
if [[ ${OLLAMA_STARTED} -eq 1 ]]; then
|
| 38 |
+
echo " • Stopping ollama serve (PID ${OLLAMA_PID})"
|
| 39 |
+
kill "${OLLAMA_PID}" 2>/dev/null || true
|
| 40 |
+
fi
|
| 41 |
+
wait || true
|
| 42 |
+
echo "✅ Shutdown complete"
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
trap cleanup EXIT SIGINT SIGTERM
|
| 46 |
+
|
| 47 |
+
start_ollama() {
|
| 48 |
+
if ! command -v ollama >/dev/null 2>&1; then
|
| 49 |
+
echo "❌ ollama command not found. Install Ollama and ensure it is on your PATH."
|
| 50 |
+
exit 1
|
| 51 |
+
fi
|
| 52 |
+
|
| 53 |
+
if pgrep -f "ollama serve" >/dev/null 2>&1; then
|
| 54 |
+
echo "🟢 ollama serve already running"
|
| 55 |
+
else
|
| 56 |
+
echo "🚀 Starting ollama serve (background)"
|
| 57 |
+
ollama serve >/dev/null 2>&1 &
|
| 58 |
+
OLLAMA_PID=$!
|
| 59 |
+
OLLAMA_STARTED=1
|
| 60 |
+
sleep 2
|
| 61 |
+
fi
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
start_backend() {
|
| 65 |
+
echo "🚀 Starting FastAPI backend on ${API_HOST}:${API_PORT} (background)"
|
| 66 |
+
(
|
| 67 |
+
cd "${ROOT_DIR}/backend"
|
| 68 |
+
uvicorn api.main:app --host "${API_HOST}" --port "${API_PORT}" --log-level "${LOG_LEVEL,,}"
|
| 69 |
+
) >/dev/null 2>&1 &
|
| 70 |
+
BACKEND_PID=$!
|
| 71 |
+
sleep 2
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
start_frontend() {
|
| 75 |
+
echo "🚀 Starting Gradio frontend (background)"
|
| 76 |
+
(
|
| 77 |
+
cd "${ROOT_DIR}/frontend"
|
| 78 |
+
python gradio_app.py
|
| 79 |
+
) >/dev/null 2>&1 &
|
| 80 |
+
FRONTEND_PID=$!
|
| 81 |
+
sleep 2
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
echo "==============================================="
|
| 85 |
+
echo " AI Survey Simulator - Local Run"
|
| 86 |
+
echo " Press Ctrl+C to stop all services"
|
| 87 |
+
echo "==============================================="
|
| 88 |
+
|
| 89 |
+
start_ollama
|
| 90 |
+
start_backend
|
| 91 |
+
start_frontend
|
| 92 |
+
|
| 93 |
+
echo "✅ All services started."
|
| 94 |
+
echo " • FastAPI backend: http://${API_HOST}:${API_PORT}"
|
| 95 |
+
echo " • Gradio UI: http://localhost:7860"
|
| 96 |
+
echo " • Ollama host: ${LLM_HOST:-http://localhost:11434}"
|
| 97 |
+
echo "==============================================="
|
| 98 |
+
echo "Services are running in the background of this shell."
|
| 99 |
+
echo "To view live logs, run each service manually in its own terminal."
|
| 100 |
+
echo "==============================================="
|
| 101 |
+
|
| 102 |
+
# Keep the script alive until interrupted so background processes persist.
|
| 103 |
+
while true; do
|
| 104 |
+
sleep 60
|
| 105 |
+
done
|
scripts/check_setup.py
DELETED
|
@@ -1,234 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Quick setup verification script.
|
| 3 |
-
|
| 4 |
-
This script checks if the development environment is properly configured
|
| 5 |
-
and all components are ready for development.
|
| 6 |
-
|
| 7 |
-
Usage:
|
| 8 |
-
python scripts/check_setup.py
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
import sys
|
| 12 |
-
import subprocess
|
| 13 |
-
import importlib
|
| 14 |
-
import asyncio
|
| 15 |
-
from pathlib import Path
|
| 16 |
-
|
| 17 |
-
def print_header(title: str):
|
| 18 |
-
"""Print formatted header."""
|
| 19 |
-
print(f"\n{'='*50}")
|
| 20 |
-
print(f"🔍 {title}")
|
| 21 |
-
print('='*50)
|
| 22 |
-
|
| 23 |
-
def check_python_packages():
|
| 24 |
-
"""Check if required Python packages are installed."""
|
| 25 |
-
print_header("Python Environment Check")
|
| 26 |
-
|
| 27 |
-
required_packages = [
|
| 28 |
-
'fastapi', 'uvicorn', 'websockets', 'streamlit',
|
| 29 |
-
'httpx', 'pydantic', 'pyyaml', 'loguru'
|
| 30 |
-
]
|
| 31 |
-
|
| 32 |
-
missing_packages = []
|
| 33 |
-
|
| 34 |
-
for package in required_packages:
|
| 35 |
-
try:
|
| 36 |
-
importlib.import_module(package)
|
| 37 |
-
print(f"✅ {package}")
|
| 38 |
-
except ImportError:
|
| 39 |
-
print(f"❌ {package} - Missing")
|
| 40 |
-
missing_packages.append(package)
|
| 41 |
-
|
| 42 |
-
if missing_packages:
|
| 43 |
-
print(f"\n⚠️ Missing packages: {', '.join(missing_packages)}")
|
| 44 |
-
print("Install with: pip install -r requirements.txt")
|
| 45 |
-
return False
|
| 46 |
-
else:
|
| 47 |
-
print("\n🎉 All required packages installed!")
|
| 48 |
-
return True
|
| 49 |
-
|
| 50 |
-
def check_ollama():
|
| 51 |
-
"""Check if Ollama is installed and available."""
|
| 52 |
-
print_header("Ollama Check")
|
| 53 |
-
|
| 54 |
-
try:
|
| 55 |
-
# Check if ollama command exists
|
| 56 |
-
result = subprocess.run(['ollama', '--version'],
|
| 57 |
-
capture_output=True, text=True, timeout=5)
|
| 58 |
-
if result.returncode == 0:
|
| 59 |
-
print(f"✅ Ollama installed: {result.stdout.strip()}")
|
| 60 |
-
|
| 61 |
-
# Check if server is running
|
| 62 |
-
try:
|
| 63 |
-
result = subprocess.run(['ollama', 'list'],
|
| 64 |
-
capture_output=True, text=True, timeout=5)
|
| 65 |
-
if result.returncode == 0:
|
| 66 |
-
print("✅ Ollama server is running")
|
| 67 |
-
|
| 68 |
-
# List available models
|
| 69 |
-
if result.stdout.strip():
|
| 70 |
-
print("📦 Available models:")
|
| 71 |
-
for line in result.stdout.strip().split('\n')[1:]: # Skip header
|
| 72 |
-
if line.strip():
|
| 73 |
-
print(f" - {line.split()[0]}")
|
| 74 |
-
else:
|
| 75 |
-
print("⚠️ No models installed")
|
| 76 |
-
print(" Install with: ollama pull llama2:7b")
|
| 77 |
-
else:
|
| 78 |
-
print("❌ Ollama server not responding")
|
| 79 |
-
print(" Start with: ollama serve")
|
| 80 |
-
return False
|
| 81 |
-
except subprocess.TimeoutExpired:
|
| 82 |
-
print("❌ Ollama command timed out")
|
| 83 |
-
return False
|
| 84 |
-
else:
|
| 85 |
-
print("❌ Ollama command failed")
|
| 86 |
-
return False
|
| 87 |
-
|
| 88 |
-
except FileNotFoundError:
|
| 89 |
-
print("❌ Ollama not installed")
|
| 90 |
-
print(" Install with: curl -fsSL https://ollama.ai/install.sh | sh")
|
| 91 |
-
return False
|
| 92 |
-
except subprocess.TimeoutExpired:
|
| 93 |
-
print("❌ Ollama command timed out")
|
| 94 |
-
return False
|
| 95 |
-
|
| 96 |
-
return True
|
| 97 |
-
|
| 98 |
-
def check_project_structure():
|
| 99 |
-
"""Check if project structure is complete."""
|
| 100 |
-
print_header("Project Structure Check")
|
| 101 |
-
|
| 102 |
-
required_dirs = [
|
| 103 |
-
'backend/api', 'backend/core', 'backend/models', 'backend/storage',
|
| 104 |
-
'frontend/components', 'frontend/utils',
|
| 105 |
-
'data/personas', 'config', 'scripts', 'tests', '.claude/commands'
|
| 106 |
-
]
|
| 107 |
-
|
| 108 |
-
required_files = [
|
| 109 |
-
'CLAUDE.md', 'STATUS.md', 'TODO_CONTEXT.md', 'DEVELOPMENT_LOG.md',
|
| 110 |
-
'requirements.txt', '.env.example',
|
| 111 |
-
'backend/api/main.py', 'backend/core/llm_client.py',
|
| 112 |
-
'backend/core/persona_system.py',
|
| 113 |
-
'data/personas/patient_personas.yaml',
|
| 114 |
-
'config/default_config.yaml'
|
| 115 |
-
]
|
| 116 |
-
|
| 117 |
-
missing_items = []
|
| 118 |
-
|
| 119 |
-
# Check directories
|
| 120 |
-
for dir_path in required_dirs:
|
| 121 |
-
if Path(dir_path).exists():
|
| 122 |
-
print(f"✅ {dir_path}/")
|
| 123 |
-
else:
|
| 124 |
-
print(f"❌ {dir_path}/ - Missing")
|
| 125 |
-
missing_items.append(dir_path)
|
| 126 |
-
|
| 127 |
-
# Check files
|
| 128 |
-
for file_path in required_files:
|
| 129 |
-
if Path(file_path).exists():
|
| 130 |
-
print(f"✅ {file_path}")
|
| 131 |
-
else:
|
| 132 |
-
print(f"❌ {file_path} - Missing")
|
| 133 |
-
missing_items.append(file_path)
|
| 134 |
-
|
| 135 |
-
if missing_items:
|
| 136 |
-
print(f"\n⚠️ Missing items: {len(missing_items)}")
|
| 137 |
-
return False
|
| 138 |
-
else:
|
| 139 |
-
print("\n🎉 Project structure complete!")
|
| 140 |
-
return True
|
| 141 |
-
|
| 142 |
-
async def run_component_tests():
|
| 143 |
-
"""Run basic component tests."""
|
| 144 |
-
print_header("Component Tests")
|
| 145 |
-
|
| 146 |
-
# Import here to avoid issues if packages aren't installed
|
| 147 |
-
try:
|
| 148 |
-
sys.path.insert(0, str(Path('backend')))
|
| 149 |
-
from core.llm_client import OllamaClient
|
| 150 |
-
from core.persona_system import PersonaSystem
|
| 151 |
-
|
| 152 |
-
# Test persona system
|
| 153 |
-
try:
|
| 154 |
-
persona_system = PersonaSystem()
|
| 155 |
-
personas = persona_system.list_personas()
|
| 156 |
-
print(f"✅ Persona system: {len(personas)} personas loaded")
|
| 157 |
-
except Exception as e:
|
| 158 |
-
print(f"❌ Persona system failed: {e}")
|
| 159 |
-
return False
|
| 160 |
-
|
| 161 |
-
# Test LLM client (without actual connection)
|
| 162 |
-
try:
|
| 163 |
-
client = OllamaClient(host="http://localhost:11434", model="llama2:7b")
|
| 164 |
-
print("✅ LLM client can be created")
|
| 165 |
-
await client.close()
|
| 166 |
-
except Exception as e:
|
| 167 |
-
print(f"❌ LLM client creation failed: {e}")
|
| 168 |
-
return False
|
| 169 |
-
|
| 170 |
-
return True
|
| 171 |
-
|
| 172 |
-
except ImportError as e:
|
| 173 |
-
print(f"❌ Import error: {e}")
|
| 174 |
-
return False
|
| 175 |
-
|
| 176 |
-
def print_next_steps(all_checks_passed: bool):
|
| 177 |
-
"""Print recommended next steps."""
|
| 178 |
-
print_header("Next Steps")
|
| 179 |
-
|
| 180 |
-
if all_checks_passed:
|
| 181 |
-
print("🎉 Environment is ready for development!")
|
| 182 |
-
print("\nRecommended workflow:")
|
| 183 |
-
print("1. Load project context:")
|
| 184 |
-
print(" @CLAUDE.md @STATUS.md")
|
| 185 |
-
print("\n2. Choose your task:")
|
| 186 |
-
print(" /conversation-task # Next priority")
|
| 187 |
-
print(" /websocket-task # If WebSocket needs testing")
|
| 188 |
-
print(" /llm-task # If LLM needs testing")
|
| 189 |
-
print("\n3. Test components:")
|
| 190 |
-
print(" python scripts/test_websocket.py")
|
| 191 |
-
print(" python scripts/test_llm_connection.py")
|
| 192 |
-
print("\n4. Begin implementation!")
|
| 193 |
-
|
| 194 |
-
else:
|
| 195 |
-
print("⚠️ Setup incomplete. Please fix the issues above.")
|
| 196 |
-
print("\nQuick setup commands:")
|
| 197 |
-
print("1. Install Python packages:")
|
| 198 |
-
print(" pip install -r requirements.txt")
|
| 199 |
-
print("\n2. Install Ollama:")
|
| 200 |
-
print(" curl -fsSL https://ollama.ai/install.sh | sh")
|
| 201 |
-
print(" ollama pull llama2:7b")
|
| 202 |
-
print("\n3. Re-run this check:")
|
| 203 |
-
print(" python scripts/check_setup.py")
|
| 204 |
-
|
| 205 |
-
async def main():
|
| 206 |
-
"""Main check function."""
|
| 207 |
-
print("🔍 AI Survey Simulator - Setup Verification")
|
| 208 |
-
|
| 209 |
-
checks = []
|
| 210 |
-
|
| 211 |
-
# Run all checks
|
| 212 |
-
checks.append(check_python_packages())
|
| 213 |
-
checks.append(check_project_structure())
|
| 214 |
-
checks.append(check_ollama())
|
| 215 |
-
checks.append(await run_component_tests())
|
| 216 |
-
|
| 217 |
-
# Summary
|
| 218 |
-
passed = sum(checks)
|
| 219 |
-
total = len(checks)
|
| 220 |
-
|
| 221 |
-
print_header(f"Summary: {passed}/{total} checks passed")
|
| 222 |
-
|
| 223 |
-
all_passed = passed == total
|
| 224 |
-
print_next_steps(all_passed)
|
| 225 |
-
|
| 226 |
-
return 0 if all_passed else 1
|
| 227 |
-
|
| 228 |
-
if __name__ == "__main__":
|
| 229 |
-
try:
|
| 230 |
-
exit_code = asyncio.run(main())
|
| 231 |
-
sys.exit(exit_code)
|
| 232 |
-
except KeyboardInterrupt:
|
| 233 |
-
print("\n⏹️ Check interrupted by user")
|
| 234 |
-
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/run_conversation_demo.py
DELETED
|
@@ -1,233 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Terminal demo of AI-to-AI conversation.
|
| 3 |
-
|
| 4 |
-
This script demonstrates the core conversation engine by running
|
| 5 |
-
a live AI survey conversation between a surveyor and patient persona
|
| 6 |
-
in the terminal with rich formatting.
|
| 7 |
-
|
| 8 |
-
Usage:
|
| 9 |
-
python scripts/run_conversation_demo.py [--host HOST] [--model MODEL]
|
| 10 |
-
|
| 11 |
-
Example:
|
| 12 |
-
python scripts/run_conversation_demo.py --host http://localhost:11434 --model llama2:7b
|
| 13 |
-
"""
|
| 14 |
-
|
| 15 |
-
import asyncio
|
| 16 |
-
import sys
|
| 17 |
-
import argparse
|
| 18 |
-
from pathlib import Path
|
| 19 |
-
|
| 20 |
-
# Add backend to path for imports
|
| 21 |
-
project_root = Path(__file__).parent.parent
|
| 22 |
-
sys.path.insert(0, str(project_root / "backend"))
|
| 23 |
-
|
| 24 |
-
try:
|
| 25 |
-
from core.conversation_manager import ConversationManager, ConversationState
|
| 26 |
-
from core.persona_system import PersonaSystem
|
| 27 |
-
from rich.console import Console
|
| 28 |
-
from rich.panel import Panel
|
| 29 |
-
from rich.text import Text
|
| 30 |
-
from rich.live import Live
|
| 31 |
-
from rich.layout import Layout
|
| 32 |
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
| 33 |
-
import rich.traceback
|
| 34 |
-
from rich.prompt import Prompt
|
| 35 |
-
from rich import print as rprint
|
| 36 |
-
except ImportError as e:
|
| 37 |
-
print(f"❌ Import error: {e}")
|
| 38 |
-
print("Make sure you're running from the project root and all dependencies are installed:")
|
| 39 |
-
print(" pip install rich")
|
| 40 |
-
print(" conda activate converai")
|
| 41 |
-
sys.exit(1)
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
def setup_rich():
|
| 45 |
-
"""Configure rich for beautiful terminal output."""
|
| 46 |
-
rich.traceback.install()
|
| 47 |
-
return Console()
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
def display_personas(console: Console, persona_system: PersonaSystem):
|
| 51 |
-
"""Display available personas for selection."""
|
| 52 |
-
console.print("\n[bold blue]📋 Available Personas[/bold blue]")
|
| 53 |
-
|
| 54 |
-
# Show surveyors
|
| 55 |
-
console.print("[bold]Surveyors:[/bold]")
|
| 56 |
-
surveyors = persona_system.list_personas("surveyor")
|
| 57 |
-
for i, surveyor in enumerate(surveyors):
|
| 58 |
-
name = surveyor.get("name", "Unknown")
|
| 59 |
-
description = surveyor.get("description", "No description")
|
| 60 |
-
console.print(f" {i+1}. [cyan]{name}[/cyan] - {description[:60]}...")
|
| 61 |
-
|
| 62 |
-
# Show patients
|
| 63 |
-
console.print("\n[bold]Patients:[/bold]")
|
| 64 |
-
patients = persona_system.list_personas("patient")
|
| 65 |
-
for i, patient in enumerate(patients):
|
| 66 |
-
name = patient.get("name", "Unknown")
|
| 67 |
-
description = patient.get("description", "No description")
|
| 68 |
-
console.print(f" {i+1}. [green]{name}[/green] - {description[:60]}...")
|
| 69 |
-
|
| 70 |
-
console.print()
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
def format_message(message: dict, console: Console):
|
| 74 |
-
"""Format a conversation message for display."""
|
| 75 |
-
role = message["role"]
|
| 76 |
-
content = message["content"]
|
| 77 |
-
persona_name = message["persona"]
|
| 78 |
-
timestamp = message["timestamp"]
|
| 79 |
-
turn = message.get("turn", 0)
|
| 80 |
-
|
| 81 |
-
if role == "surveyor":
|
| 82 |
-
# Blue panel for surveyor
|
| 83 |
-
title = f"🔹 Dr. {persona_name} (Turn {turn})"
|
| 84 |
-
panel = Panel(
|
| 85 |
-
content,
|
| 86 |
-
title=title,
|
| 87 |
-
border_style="blue",
|
| 88 |
-
padding=(0, 1),
|
| 89 |
-
)
|
| 90 |
-
else:
|
| 91 |
-
# Green panel for patient
|
| 92 |
-
title = f"💬 {persona_name} (Turn {turn})"
|
| 93 |
-
panel = Panel(
|
| 94 |
-
content,
|
| 95 |
-
title=title,
|
| 96 |
-
border_style="green",
|
| 97 |
-
padding=(0, 1),
|
| 98 |
-
)
|
| 99 |
-
|
| 100 |
-
console.print(panel)
|
| 101 |
-
console.print() # Add spacing
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
async def run_conversation_demo(host: str = "http://localhost:11434", model: str = "llama2:7b"):
|
| 105 |
-
"""Run the conversation demo."""
|
| 106 |
-
console = setup_rich()
|
| 107 |
-
|
| 108 |
-
try:
|
| 109 |
-
# Header
|
| 110 |
-
console.print("[bold green]🤖 AI Survey Conversation Demo[/bold green]")
|
| 111 |
-
console.print(f"Host: {host}")
|
| 112 |
-
console.print(f"Model: {model}")
|
| 113 |
-
console.print("=" * 50)
|
| 114 |
-
|
| 115 |
-
# Initialize persona system
|
| 116 |
-
console.print("[yellow]Loading persona system...[/yellow]")
|
| 117 |
-
persona_system = PersonaSystem()
|
| 118 |
-
|
| 119 |
-
# Show available personas
|
| 120 |
-
display_personas(console, persona_system)
|
| 121 |
-
|
| 122 |
-
# Let user choose personas or use defaults
|
| 123 |
-
use_defaults = Prompt.ask(
|
| 124 |
-
"Use default personas (Dr. Sarah Mitchell + Margaret Thompson)?",
|
| 125 |
-
choices=["y", "n"],
|
| 126 |
-
default="y"
|
| 127 |
-
)
|
| 128 |
-
|
| 129 |
-
if use_defaults.lower() == "y":
|
| 130 |
-
# Use first surveyor and patient
|
| 131 |
-
surveyors = persona_system.list_personas("surveyor")
|
| 132 |
-
patients = persona_system.list_personas("patient")
|
| 133 |
-
|
| 134 |
-
if not surveyors or not patients:
|
| 135 |
-
console.print("[red]❌ No personas found! Check persona configuration.[/red]")
|
| 136 |
-
return
|
| 137 |
-
|
| 138 |
-
surveyor_persona = surveyors[0]
|
| 139 |
-
patient_persona = patients[0]
|
| 140 |
-
else:
|
| 141 |
-
console.print("[yellow]Manual persona selection not implemented yet. Using defaults.[/yellow]")
|
| 142 |
-
surveyors = persona_system.list_personas("surveyor")
|
| 143 |
-
patients = persona_system.list_personas("patient")
|
| 144 |
-
surveyor_persona = surveyors[0]
|
| 145 |
-
patient_persona = patients[0]
|
| 146 |
-
|
| 147 |
-
console.print(f"[blue]Selected Surveyor:[/blue] {surveyor_persona.get('name', 'Unknown')}")
|
| 148 |
-
console.print(f"[green]Selected Patient:[/green] {patient_persona.get('name', 'Unknown')}")
|
| 149 |
-
console.print()
|
| 150 |
-
|
| 151 |
-
# Create conversation manager
|
| 152 |
-
console.print("[yellow]Initializing conversation manager...[/yellow]")
|
| 153 |
-
manager = ConversationManager(
|
| 154 |
-
surveyor_persona=surveyor_persona,
|
| 155 |
-
patient_persona=patient_persona,
|
| 156 |
-
host=host,
|
| 157 |
-
model=model
|
| 158 |
-
)
|
| 159 |
-
|
| 160 |
-
console.print("[green]✅ Ready! Starting conversation...[/green]")
|
| 161 |
-
console.print("=" * 50)
|
| 162 |
-
console.print()
|
| 163 |
-
|
| 164 |
-
# Run conversation
|
| 165 |
-
message_count = 0
|
| 166 |
-
try:
|
| 167 |
-
async for message in manager.conduct_conversation():
|
| 168 |
-
message_count += 1
|
| 169 |
-
format_message(message, console)
|
| 170 |
-
|
| 171 |
-
# Small delay for readability
|
| 172 |
-
await asyncio.sleep(1)
|
| 173 |
-
|
| 174 |
-
# Check for errors
|
| 175 |
-
if message.get("error"):
|
| 176 |
-
console.print(f"[yellow]⚠️ Technical error detected in message {message_count}[/yellow]")
|
| 177 |
-
|
| 178 |
-
# Progress indicator
|
| 179 |
-
if message_count % 2 == 0: # Every patient response
|
| 180 |
-
console.print(f"[dim]Conversation progress: {message_count} messages...[/dim]")
|
| 181 |
-
|
| 182 |
-
except KeyboardInterrupt:
|
| 183 |
-
console.print("\n[yellow]⏹️ Conversation interrupted by user[/yellow]")
|
| 184 |
-
except Exception as e:
|
| 185 |
-
console.print(f"\n[red]❌ Conversation error: {e}[/red]")
|
| 186 |
-
import traceback
|
| 187 |
-
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
| 188 |
-
finally:
|
| 189 |
-
# Clean up
|
| 190 |
-
try:
|
| 191 |
-
await manager.close()
|
| 192 |
-
except:
|
| 193 |
-
pass
|
| 194 |
-
|
| 195 |
-
# Summary
|
| 196 |
-
console.print("=" * 50)
|
| 197 |
-
if message_count > 0:
|
| 198 |
-
console.print(f"[bold green]🎉 Conversation Complete![/bold green]")
|
| 199 |
-
console.print(f"Total messages: {message_count}")
|
| 200 |
-
console.print(f"Conversation ID: {manager.conversation_id}")
|
| 201 |
-
else:
|
| 202 |
-
console.print("[yellow]⚠️ No messages were generated.[/yellow]")
|
| 203 |
-
console.print("Check that Ollama is running: ollama serve")
|
| 204 |
-
|
| 205 |
-
except Exception as e:
|
| 206 |
-
console.print(f"[red]❌ Demo failed: {e}[/red]")
|
| 207 |
-
import traceback
|
| 208 |
-
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
| 209 |
-
return 1
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
def main():
|
| 213 |
-
"""Main entry point."""
|
| 214 |
-
parser = argparse.ArgumentParser(description="AI Survey Conversation Demo")
|
| 215 |
-
parser.add_argument("--host", default="http://localhost:11434",
|
| 216 |
-
help="Ollama server host (default: http://localhost:11434)")
|
| 217 |
-
parser.add_argument("--model", default="llama2:7b",
|
| 218 |
-
help="LLM model to use (default: llama2:7b)")
|
| 219 |
-
|
| 220 |
-
args = parser.parse_args()
|
| 221 |
-
|
| 222 |
-
try:
|
| 223 |
-
return asyncio.run(run_conversation_demo(args.host, args.model))
|
| 224 |
-
except KeyboardInterrupt:
|
| 225 |
-
print("\n⏹️ Demo interrupted by user")
|
| 226 |
-
return 0
|
| 227 |
-
except Exception as e:
|
| 228 |
-
print(f"❌ Failed to run demo: {e}")
|
| 229 |
-
return 1
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
if __name__ == "__main__":
|
| 233 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/test_integration.py
DELETED
|
@@ -1,387 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Integration test script for AI Survey Simulator.
|
| 3 |
-
|
| 4 |
-
This script tests the full integration of all components:
|
| 5 |
-
- LLM client with personas
|
| 6 |
-
- Persona system loading
|
| 7 |
-
- End-to-end conversation flow
|
| 8 |
-
- Configuration loading
|
| 9 |
-
|
| 10 |
-
Usage:
|
| 11 |
-
python scripts/test_integration.py
|
| 12 |
-
"""
|
| 13 |
-
|
| 14 |
-
import asyncio
|
| 15 |
-
import sys
|
| 16 |
-
import yaml
|
| 17 |
-
from pathlib import Path
|
| 18 |
-
|
| 19 |
-
# Add backend to path for imports
|
| 20 |
-
sys.path.insert(0, str(Path(__file__).parent.parent / "backend"))
|
| 21 |
-
|
| 22 |
-
try:
|
| 23 |
-
from core.llm_client import OllamaClient
|
| 24 |
-
from core.persona_system import PersonaSystem
|
| 25 |
-
except ImportError as e:
|
| 26 |
-
print(f"❌ Import error: {e}")
|
| 27 |
-
print("Make sure you're running from the project root directory")
|
| 28 |
-
sys.exit(1)
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
class IntegrationTester:
|
| 32 |
-
"""Full integration test suite."""
|
| 33 |
-
|
| 34 |
-
def __init__(self, host: str = "http://localhost:11434", model: str = "llama2:7b"):
|
| 35 |
-
"""Initialize integration tester.
|
| 36 |
-
|
| 37 |
-
Args:
|
| 38 |
-
host: Ollama server host
|
| 39 |
-
model: Model to test with
|
| 40 |
-
"""
|
| 41 |
-
self.host = host
|
| 42 |
-
self.model = model
|
| 43 |
-
self.client = None
|
| 44 |
-
self.persona_system = None
|
| 45 |
-
|
| 46 |
-
async def run_all_tests(self) -> bool:
|
| 47 |
-
"""Run all integration tests.
|
| 48 |
-
|
| 49 |
-
Returns:
|
| 50 |
-
True if all tests pass
|
| 51 |
-
"""
|
| 52 |
-
print("🔗 AI Survey Simulator - Integration Test Suite")
|
| 53 |
-
print("=" * 50)
|
| 54 |
-
print(f"Host: {self.host}")
|
| 55 |
-
print(f"Model: {self.model}")
|
| 56 |
-
print()
|
| 57 |
-
|
| 58 |
-
tests = [
|
| 59 |
-
("Persona System Loading", self.test_persona_loading),
|
| 60 |
-
("LLM Client Creation", self.test_llm_client_creation),
|
| 61 |
-
("Basic Persona Response", self.test_basic_persona_response),
|
| 62 |
-
("Surveyor Persona Test", self.test_surveyor_persona),
|
| 63 |
-
("Patient Persona Test", self.test_patient_persona),
|
| 64 |
-
("Multi-turn Conversation", self.test_multi_turn_conversation),
|
| 65 |
-
("Configuration Loading", self.test_config_loading)
|
| 66 |
-
]
|
| 67 |
-
|
| 68 |
-
results = []
|
| 69 |
-
|
| 70 |
-
try:
|
| 71 |
-
for test_name, test_func in tests:
|
| 72 |
-
print(f"Running: {test_name}...")
|
| 73 |
-
try:
|
| 74 |
-
result = await test_func()
|
| 75 |
-
if result:
|
| 76 |
-
print(f"✅ {test_name}")
|
| 77 |
-
else:
|
| 78 |
-
print(f"❌ {test_name}")
|
| 79 |
-
results.append(result)
|
| 80 |
-
except Exception as e:
|
| 81 |
-
print(f"❌ {test_name} - Exception: {e}")
|
| 82 |
-
results.append(False)
|
| 83 |
-
print()
|
| 84 |
-
|
| 85 |
-
finally:
|
| 86 |
-
if self.client:
|
| 87 |
-
await self.client.close()
|
| 88 |
-
|
| 89 |
-
# Summary
|
| 90 |
-
passed = sum(results)
|
| 91 |
-
total = len(results)
|
| 92 |
-
print("=" * 50)
|
| 93 |
-
print(f"Results: {passed}/{total} tests passed")
|
| 94 |
-
|
| 95 |
-
if passed == total:
|
| 96 |
-
print("🎉 All integration tests passed!")
|
| 97 |
-
print("✅ Ready for conversation orchestration implementation!")
|
| 98 |
-
else:
|
| 99 |
-
print("⚠️ Some integration tests failed. Check setup.")
|
| 100 |
-
|
| 101 |
-
return passed == total
|
| 102 |
-
|
| 103 |
-
async def test_persona_loading(self) -> bool:
|
| 104 |
-
"""Test loading persona system and personas."""
|
| 105 |
-
try:
|
| 106 |
-
self.persona_system = PersonaSystem()
|
| 107 |
-
|
| 108 |
-
# Check if personas loaded
|
| 109 |
-
personas = self.persona_system.list_personas()
|
| 110 |
-
|
| 111 |
-
if len(personas) > 0:
|
| 112 |
-
print(f" Loaded {len(personas)} personas:")
|
| 113 |
-
for persona in personas[:3]: # Show first 3
|
| 114 |
-
persona_id = persona.get('id', 'unknown')
|
| 115 |
-
persona_name = persona.get('name', 'Unknown')
|
| 116 |
-
print(f" - {persona_id}: {persona_name}")
|
| 117 |
-
|
| 118 |
-
if len(personas) > 3:
|
| 119 |
-
print(f" ... and {len(personas) - 3} more")
|
| 120 |
-
|
| 121 |
-
return True
|
| 122 |
-
else:
|
| 123 |
-
print(" No personas loaded")
|
| 124 |
-
return False
|
| 125 |
-
|
| 126 |
-
except Exception as e:
|
| 127 |
-
print(f" Persona loading failed: {e}")
|
| 128 |
-
return False
|
| 129 |
-
|
| 130 |
-
async def test_llm_client_creation(self) -> bool:
|
| 131 |
-
"""Test LLM client creation and basic connectivity."""
|
| 132 |
-
try:
|
| 133 |
-
self.client = OllamaClient(host=self.host, model=self.model)
|
| 134 |
-
|
| 135 |
-
# Test health check
|
| 136 |
-
health = await self.client.health_check()
|
| 137 |
-
|
| 138 |
-
if health["status"] == "healthy" and health["model_available"]:
|
| 139 |
-
print(f" Client created successfully")
|
| 140 |
-
print(f" Model available: {health['model_available']}")
|
| 141 |
-
return True
|
| 142 |
-
else:
|
| 143 |
-
print(f" Health check failed: {health}")
|
| 144 |
-
return False
|
| 145 |
-
|
| 146 |
-
except Exception as e:
|
| 147 |
-
print(f" Client creation failed: {e}")
|
| 148 |
-
return False
|
| 149 |
-
|
| 150 |
-
async def test_basic_persona_response(self) -> bool:
|
| 151 |
-
"""Test basic persona response generation."""
|
| 152 |
-
try:
|
| 153 |
-
if not self.persona_system or not self.client:
|
| 154 |
-
print(" Prerequisites not met")
|
| 155 |
-
return False
|
| 156 |
-
|
| 157 |
-
# Get a patient persona
|
| 158 |
-
patient_personas = self.persona_system.list_personas("patient")
|
| 159 |
-
|
| 160 |
-
if not patient_personas:
|
| 161 |
-
print(" No patient personas found")
|
| 162 |
-
return False
|
| 163 |
-
|
| 164 |
-
persona = patient_personas[0]
|
| 165 |
-
persona_id = persona.get('id')
|
| 166 |
-
|
| 167 |
-
# Build system prompt using conversation prompt method
|
| 168 |
-
system_prompt, _ = self.persona_system.build_conversation_prompt(persona_id)
|
| 169 |
-
|
| 170 |
-
response = await self.client.generate(
|
| 171 |
-
prompt="How are you feeling today?",
|
| 172 |
-
system_prompt=system_prompt
|
| 173 |
-
)
|
| 174 |
-
|
| 175 |
-
if response and len(response.strip()) > 0:
|
| 176 |
-
print(f" Persona: {persona.get('name', persona_id)}")
|
| 177 |
-
print(f" Response: {response[:100]}{'...' if len(response) > 100 else ''}")
|
| 178 |
-
return True
|
| 179 |
-
else:
|
| 180 |
-
print(" Empty response received")
|
| 181 |
-
return False
|
| 182 |
-
|
| 183 |
-
except Exception as e:
|
| 184 |
-
print(f" Basic persona response failed: {e}")
|
| 185 |
-
return False
|
| 186 |
-
|
| 187 |
-
async def test_surveyor_persona(self) -> bool:
|
| 188 |
-
"""Test surveyor persona functionality."""
|
| 189 |
-
try:
|
| 190 |
-
if not self.persona_system or not self.client:
|
| 191 |
-
print(" Prerequisites not met")
|
| 192 |
-
return False
|
| 193 |
-
|
| 194 |
-
# Get a surveyor persona
|
| 195 |
-
surveyor_personas = self.persona_system.list_personas("surveyor")
|
| 196 |
-
|
| 197 |
-
if not surveyor_personas:
|
| 198 |
-
print(" No surveyor personas found")
|
| 199 |
-
return False
|
| 200 |
-
|
| 201 |
-
persona = surveyor_personas[0]
|
| 202 |
-
persona_id = persona.get('id')
|
| 203 |
-
|
| 204 |
-
# Build system prompt using conversation prompt method
|
| 205 |
-
system_prompt, _ = self.persona_system.build_conversation_prompt(persona_id)
|
| 206 |
-
|
| 207 |
-
response = await self.client.generate(
|
| 208 |
-
prompt="Please introduce yourself and ask your first survey question.",
|
| 209 |
-
system_prompt=system_prompt
|
| 210 |
-
)
|
| 211 |
-
|
| 212 |
-
if response and len(response.strip()) > 0:
|
| 213 |
-
print(f" Surveyor: {persona.get('name', persona_id)}")
|
| 214 |
-
print(f" Introduction: {response[:120]}{'...' if len(response) > 120 else ''}")
|
| 215 |
-
return True
|
| 216 |
-
else:
|
| 217 |
-
print(" Empty response received")
|
| 218 |
-
return False
|
| 219 |
-
|
| 220 |
-
except Exception as e:
|
| 221 |
-
print(f" Surveyor persona test failed: {e}")
|
| 222 |
-
return False
|
| 223 |
-
|
| 224 |
-
async def test_patient_persona(self) -> bool:
|
| 225 |
-
"""Test patient persona functionality."""
|
| 226 |
-
try:
|
| 227 |
-
if not self.persona_system or not self.client:
|
| 228 |
-
print(" Prerequisites not met")
|
| 229 |
-
return False
|
| 230 |
-
|
| 231 |
-
# Get a patient persona (different from basic test)
|
| 232 |
-
patient_personas = self.persona_system.list_personas("patient")
|
| 233 |
-
|
| 234 |
-
if len(patient_personas) < 2:
|
| 235 |
-
print(" Not enough patient personas for varied testing")
|
| 236 |
-
return len(patient_personas) > 0 # Still pass if we have at least one
|
| 237 |
-
|
| 238 |
-
persona = patient_personas[1] if len(patient_personas) > 1 else patient_personas[0] # Use second patient or first
|
| 239 |
-
persona_id = persona.get('id')
|
| 240 |
-
|
| 241 |
-
# Build system prompt using conversation prompt method
|
| 242 |
-
system_prompt, _ = self.persona_system.build_conversation_prompt(persona_id)
|
| 243 |
-
|
| 244 |
-
# Test with a survey question
|
| 245 |
-
response = await self.client.generate(
|
| 246 |
-
prompt="On a scale of 1-10, how would you rate your overall health today?",
|
| 247 |
-
system_prompt=system_prompt
|
| 248 |
-
)
|
| 249 |
-
|
| 250 |
-
if response and len(response.strip()) > 0:
|
| 251 |
-
print(f" Patient: {persona.get('name', persona_id)}")
|
| 252 |
-
print(f" Health rating: {response[:100]}{'...' if len(response) > 100 else ''}")
|
| 253 |
-
return True
|
| 254 |
-
else:
|
| 255 |
-
print(" Empty response received")
|
| 256 |
-
return False
|
| 257 |
-
|
| 258 |
-
except Exception as e:
|
| 259 |
-
print(f" Patient persona test failed: {e}")
|
| 260 |
-
return False
|
| 261 |
-
|
| 262 |
-
async def test_multi_turn_conversation(self) -> bool:
|
| 263 |
-
"""Test multi-turn conversation capability."""
|
| 264 |
-
try:
|
| 265 |
-
if not self.persona_system or not self.client:
|
| 266 |
-
print(" Prerequisites not met")
|
| 267 |
-
return False
|
| 268 |
-
|
| 269 |
-
# Get personas for conversation
|
| 270 |
-
patient_personas = self.persona_system.list_personas("patient")
|
| 271 |
-
|
| 272 |
-
if not patient_personas:
|
| 273 |
-
print(" No patient personas for conversation")
|
| 274 |
-
return False
|
| 275 |
-
|
| 276 |
-
persona = patient_personas[0]
|
| 277 |
-
persona_id = persona.get('id')
|
| 278 |
-
|
| 279 |
-
# Simulate conversation history
|
| 280 |
-
conversation_history = [
|
| 281 |
-
{"role": "assistant", "content": "Hello! How are you feeling today?"},
|
| 282 |
-
{"role": "user", "content": "I'm doing okay, thank you for asking."}
|
| 283 |
-
]
|
| 284 |
-
|
| 285 |
-
# Build prompt with history
|
| 286 |
-
system_prompt, prompt_with_history = self.persona_system.build_conversation_prompt(
|
| 287 |
-
persona_id=persona_id,
|
| 288 |
-
conversation_history=conversation_history,
|
| 289 |
-
user_prompt="Can you tell me more about any health concerns you might have?"
|
| 290 |
-
)
|
| 291 |
-
|
| 292 |
-
response = await self.client.generate(
|
| 293 |
-
prompt=prompt_with_history,
|
| 294 |
-
system_prompt=system_prompt
|
| 295 |
-
)
|
| 296 |
-
|
| 297 |
-
if response and len(response.strip()) > 0:
|
| 298 |
-
print(f" Multi-turn conversation successful")
|
| 299 |
-
print(f" Response: {response[:80]}{'...' if len(response) > 80 else ''}")
|
| 300 |
-
return True
|
| 301 |
-
else:
|
| 302 |
-
print(" Empty response received")
|
| 303 |
-
return False
|
| 304 |
-
|
| 305 |
-
except Exception as e:
|
| 306 |
-
print(f" Multi-turn conversation test failed: {e}")
|
| 307 |
-
return False
|
| 308 |
-
|
| 309 |
-
async def test_config_loading(self) -> bool:
|
| 310 |
-
"""Test configuration loading."""
|
| 311 |
-
try:
|
| 312 |
-
config_path = Path(__file__).parent.parent / "config" / "default_config.yaml"
|
| 313 |
-
|
| 314 |
-
if not config_path.exists():
|
| 315 |
-
print(f" Config file not found: {config_path}")
|
| 316 |
-
return False
|
| 317 |
-
|
| 318 |
-
with open(config_path, 'r') as f:
|
| 319 |
-
config = yaml.safe_load(f)
|
| 320 |
-
|
| 321 |
-
# Check for essential config sections
|
| 322 |
-
required_sections = ['llm', 'api']
|
| 323 |
-
missing_sections = [section for section in required_sections if section not in config]
|
| 324 |
-
|
| 325 |
-
if missing_sections:
|
| 326 |
-
print(f" Missing config sections: {missing_sections}")
|
| 327 |
-
return False
|
| 328 |
-
|
| 329 |
-
# Personas section is optional since personas are loaded from separate files
|
| 330 |
-
|
| 331 |
-
print(f" Config loaded successfully")
|
| 332 |
-
print(f" LLM backend: {config.get('llm', {}).get('backend', 'unknown')}")
|
| 333 |
-
print(f" API port: {config.get('api', {}).get('port', 'unknown')}")
|
| 334 |
-
|
| 335 |
-
return True
|
| 336 |
-
|
| 337 |
-
except Exception as e:
|
| 338 |
-
print(f" Config loading failed: {e}")
|
| 339 |
-
return False
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
async def main():
|
| 343 |
-
"""Main integration test function."""
|
| 344 |
-
print("🔗 AI Survey Simulator - Integration Test")
|
| 345 |
-
print("=" * 45)
|
| 346 |
-
print()
|
| 347 |
-
print("This test verifies that all core components work together:")
|
| 348 |
-
print("- Persona system")
|
| 349 |
-
print("- LLM client")
|
| 350 |
-
print("- Configuration loading")
|
| 351 |
-
print("- End-to-end conversation flow")
|
| 352 |
-
print()
|
| 353 |
-
|
| 354 |
-
# Parse command line arguments for custom host/model
|
| 355 |
-
host = "http://localhost:11434"
|
| 356 |
-
model = "llama2:7b"
|
| 357 |
-
|
| 358 |
-
if len(sys.argv) > 1:
|
| 359 |
-
host = sys.argv[1]
|
| 360 |
-
if len(sys.argv) > 2:
|
| 361 |
-
model = sys.argv[2]
|
| 362 |
-
|
| 363 |
-
print(f"Testing with host: {host}, model: {model}")
|
| 364 |
-
print()
|
| 365 |
-
|
| 366 |
-
# Run integration tests
|
| 367 |
-
tester = IntegrationTester(host=host, model=model)
|
| 368 |
-
success = await tester.run_all_tests()
|
| 369 |
-
|
| 370 |
-
if success:
|
| 371 |
-
print()
|
| 372 |
-
print("🚀 Next Steps:")
|
| 373 |
-
print("1. Implement conversation orchestration")
|
| 374 |
-
print("2. Connect WebSocket + LLM + Personas")
|
| 375 |
-
print("3. Test AI-to-AI conversations")
|
| 376 |
-
print("4. Build Streamlit UI integration")
|
| 377 |
-
|
| 378 |
-
return 0 if success else 1
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
if __name__ == "__main__":
|
| 382 |
-
try:
|
| 383 |
-
exit_code = asyncio.run(main())
|
| 384 |
-
sys.exit(exit_code)
|
| 385 |
-
except KeyboardInterrupt:
|
| 386 |
-
print("\n⏹️ Integration tests interrupted by user")
|
| 387 |
-
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/test_llm_connection.py
DELETED
|
@@ -1,317 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Test script for LLM connectivity and functionality.
|
| 3 |
-
|
| 4 |
-
This script tests the LLM integration with Ollama to ensure
|
| 5 |
-
language model connections work correctly.
|
| 6 |
-
|
| 7 |
-
Usage:
|
| 8 |
-
python scripts/test_llm_connection.py
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
import asyncio
|
| 12 |
-
import sys
|
| 13 |
-
import time
|
| 14 |
-
from pathlib import Path
|
| 15 |
-
|
| 16 |
-
# Add backend to path for imports
|
| 17 |
-
sys.path.insert(0, str(Path(__file__).parent.parent / "backend"))
|
| 18 |
-
|
| 19 |
-
try:
|
| 20 |
-
from core.llm_client import OllamaClient, create_llm_client
|
| 21 |
-
except ImportError as e:
|
| 22 |
-
print(f"❌ Import error: {e}")
|
| 23 |
-
print("Make sure you're running from the project root directory")
|
| 24 |
-
sys.exit(1)
|
| 25 |
-
|
| 26 |
-
class LLMTester:
|
| 27 |
-
"""Test suite for LLM functionality."""
|
| 28 |
-
|
| 29 |
-
def __init__(self, host: str = "http://localhost:11434", model: str = "llama2:7b"):
|
| 30 |
-
"""Initialize LLM tester.
|
| 31 |
-
|
| 32 |
-
Args:
|
| 33 |
-
host: Ollama server host
|
| 34 |
-
model: Model to test with
|
| 35 |
-
"""
|
| 36 |
-
self.host = host
|
| 37 |
-
self.model = model
|
| 38 |
-
self.client = None
|
| 39 |
-
|
| 40 |
-
async def run_all_tests(self) -> bool:
|
| 41 |
-
"""Run all LLM tests.
|
| 42 |
-
|
| 43 |
-
Returns:
|
| 44 |
-
True if all tests pass
|
| 45 |
-
"""
|
| 46 |
-
print("🧠 LLM Connection Test Suite")
|
| 47 |
-
print("=" * 40)
|
| 48 |
-
print(f"Host: {self.host}")
|
| 49 |
-
print(f"Model: {self.model}")
|
| 50 |
-
print()
|
| 51 |
-
|
| 52 |
-
tests = [
|
| 53 |
-
("Connection Health Check", self.test_health_check),
|
| 54 |
-
("Basic Generation", self.test_basic_generation),
|
| 55 |
-
("System Prompt", self.test_system_prompt),
|
| 56 |
-
("Parameter Control", self.test_parameter_control),
|
| 57 |
-
("Error Handling", self.test_error_handling),
|
| 58 |
-
("Performance Stats", self.test_performance_stats),
|
| 59 |
-
("Retry Logic", self.test_retry_logic)
|
| 60 |
-
]
|
| 61 |
-
|
| 62 |
-
results = []
|
| 63 |
-
|
| 64 |
-
# Initialize client
|
| 65 |
-
self.client = OllamaClient(host=self.host, model=self.model)
|
| 66 |
-
|
| 67 |
-
try:
|
| 68 |
-
for test_name, test_func in tests:
|
| 69 |
-
print(f"Running: {test_name}...")
|
| 70 |
-
try:
|
| 71 |
-
result = await test_func()
|
| 72 |
-
if result:
|
| 73 |
-
print(f"✅ {test_name}")
|
| 74 |
-
else:
|
| 75 |
-
print(f"❌ {test_name}")
|
| 76 |
-
results.append(result)
|
| 77 |
-
except Exception as e:
|
| 78 |
-
print(f"❌ {test_name} - Exception: {e}")
|
| 79 |
-
results.append(False)
|
| 80 |
-
print()
|
| 81 |
-
|
| 82 |
-
finally:
|
| 83 |
-
if self.client:
|
| 84 |
-
await self.client.close()
|
| 85 |
-
|
| 86 |
-
# Summary
|
| 87 |
-
passed = sum(results)
|
| 88 |
-
total = len(results)
|
| 89 |
-
print("=" * 40)
|
| 90 |
-
print(f"Results: {passed}/{total} tests passed")
|
| 91 |
-
|
| 92 |
-
if passed == total:
|
| 93 |
-
print("🎉 All LLM tests passed!")
|
| 94 |
-
else:
|
| 95 |
-
print("⚠️ Some tests failed. Check Ollama setup.")
|
| 96 |
-
|
| 97 |
-
return passed == total
|
| 98 |
-
|
| 99 |
-
async def test_health_check(self) -> bool:
|
| 100 |
-
"""Test LLM server health check."""
|
| 101 |
-
try:
|
| 102 |
-
health = await self.client.health_check()
|
| 103 |
-
|
| 104 |
-
if health["status"] == "healthy":
|
| 105 |
-
print(f" Server: {health['status']}")
|
| 106 |
-
print(f" Model available: {health['model_available']}")
|
| 107 |
-
print(f" Available models: {len(health.get('available_models', []))}")
|
| 108 |
-
|
| 109 |
-
if not health['model_available']:
|
| 110 |
-
print(f" ⚠️ Model '{self.model}' not found!")
|
| 111 |
-
print(f" Available: {health['available_models']}")
|
| 112 |
-
return False
|
| 113 |
-
|
| 114 |
-
return True
|
| 115 |
-
else:
|
| 116 |
-
print(f" Server unhealthy: {health.get('error', 'Unknown error')}")
|
| 117 |
-
return False
|
| 118 |
-
|
| 119 |
-
except Exception as e:
|
| 120 |
-
print(f" Health check failed: {e}")
|
| 121 |
-
return False
|
| 122 |
-
|
| 123 |
-
async def test_basic_generation(self) -> bool:
|
| 124 |
-
"""Test basic text generation."""
|
| 125 |
-
try:
|
| 126 |
-
prompt = "Say 'Hello, World!' in exactly those words."
|
| 127 |
-
response = await self.client.generate(prompt)
|
| 128 |
-
|
| 129 |
-
if response and len(response.strip()) > 0:
|
| 130 |
-
print(f" Prompt: {prompt}")
|
| 131 |
-
print(f" Response: {response[:100]}{'...' if len(response) > 100 else ''}")
|
| 132 |
-
return True
|
| 133 |
-
else:
|
| 134 |
-
print(" Empty or invalid response")
|
| 135 |
-
return False
|
| 136 |
-
|
| 137 |
-
except Exception as e:
|
| 138 |
-
print(f" Generation failed: {e}")
|
| 139 |
-
return False
|
| 140 |
-
|
| 141 |
-
async def test_system_prompt(self) -> bool:
|
| 142 |
-
"""Test system prompt functionality."""
|
| 143 |
-
try:
|
| 144 |
-
system_prompt = "You are a helpful assistant. Always respond with exactly 3 words."
|
| 145 |
-
user_prompt = "What is AI?"
|
| 146 |
-
|
| 147 |
-
response = await self.client.generate(
|
| 148 |
-
prompt=user_prompt,
|
| 149 |
-
system_prompt=system_prompt
|
| 150 |
-
)
|
| 151 |
-
|
| 152 |
-
if response:
|
| 153 |
-
word_count = len(response.strip().split())
|
| 154 |
-
print(f" System: {system_prompt}")
|
| 155 |
-
print(f" User: {user_prompt}")
|
| 156 |
-
print(f" Response: '{response}' ({word_count} words)")
|
| 157 |
-
|
| 158 |
-
# Check if roughly follows instructions (3 words ± 1)
|
| 159 |
-
return 2 <= word_count <= 4
|
| 160 |
-
else:
|
| 161 |
-
print(" No response received")
|
| 162 |
-
return False
|
| 163 |
-
|
| 164 |
-
except Exception as e:
|
| 165 |
-
print(f" System prompt test failed: {e}")
|
| 166 |
-
return False
|
| 167 |
-
|
| 168 |
-
async def test_parameter_control(self) -> bool:
|
| 169 |
-
"""Test parameter control (temperature, etc.)."""
|
| 170 |
-
try:
|
| 171 |
-
prompt = "Generate a random number between 1 and 10."
|
| 172 |
-
|
| 173 |
-
# Test with low temperature (more deterministic)
|
| 174 |
-
response1 = await self.client.generate(
|
| 175 |
-
prompt=prompt,
|
| 176 |
-
temperature=0.1
|
| 177 |
-
)
|
| 178 |
-
|
| 179 |
-
# Test with high temperature (more random)
|
| 180 |
-
response2 = await self.client.generate(
|
| 181 |
-
prompt=prompt,
|
| 182 |
-
temperature=0.9
|
| 183 |
-
)
|
| 184 |
-
|
| 185 |
-
print(f" Low temp (0.1): {response1[:50]}")
|
| 186 |
-
print(f" High temp (0.9): {response2[:50]}")
|
| 187 |
-
|
| 188 |
-
# Both should have responses
|
| 189 |
-
return bool(response1 and response2)
|
| 190 |
-
|
| 191 |
-
except Exception as e:
|
| 192 |
-
print(f" Parameter test failed: {e}")
|
| 193 |
-
return False
|
| 194 |
-
|
| 195 |
-
async def test_error_handling(self) -> bool:
|
| 196 |
-
"""Test error handling with invalid requests."""
|
| 197 |
-
try:
|
| 198 |
-
# Test with invalid model temporarily
|
| 199 |
-
invalid_client = OllamaClient(
|
| 200 |
-
host=self.host,
|
| 201 |
-
model="nonexistent-model-12345"
|
| 202 |
-
)
|
| 203 |
-
|
| 204 |
-
try:
|
| 205 |
-
await invalid_client.generate("Test prompt")
|
| 206 |
-
print(" Expected error but got response")
|
| 207 |
-
return False
|
| 208 |
-
except Exception as e:
|
| 209 |
-
print(f" Correctly caught error: {type(e).__name__}")
|
| 210 |
-
return True
|
| 211 |
-
finally:
|
| 212 |
-
await invalid_client.close()
|
| 213 |
-
|
| 214 |
-
except Exception as e:
|
| 215 |
-
print(f" Error handling test failed: {e}")
|
| 216 |
-
return False
|
| 217 |
-
|
| 218 |
-
async def test_performance_stats(self) -> bool:
|
| 219 |
-
"""Test performance statistics tracking."""
|
| 220 |
-
try:
|
| 221 |
-
initial_stats = self.client.get_stats()
|
| 222 |
-
initial_count = initial_stats["request_count"]
|
| 223 |
-
|
| 224 |
-
# Make a few requests
|
| 225 |
-
for i in range(3):
|
| 226 |
-
await self.client.generate(f"Count to {i+1}")
|
| 227 |
-
|
| 228 |
-
final_stats = self.client.get_stats()
|
| 229 |
-
final_count = final_stats["request_count"]
|
| 230 |
-
|
| 231 |
-
print(f" Requests: {initial_count} → {final_count}")
|
| 232 |
-
print(f" Avg time: {final_stats['average_time']}s")
|
| 233 |
-
print(f" Total time: {final_stats['total_time']}s")
|
| 234 |
-
|
| 235 |
-
return final_count > initial_count
|
| 236 |
-
|
| 237 |
-
except Exception as e:
|
| 238 |
-
print(f" Performance stats test failed: {e}")
|
| 239 |
-
return False
|
| 240 |
-
|
| 241 |
-
async def test_retry_logic(self) -> bool:
|
| 242 |
-
"""Test retry logic with connection issues."""
|
| 243 |
-
try:
|
| 244 |
-
# Test with unreachable host
|
| 245 |
-
retry_client = OllamaClient(
|
| 246 |
-
host="http://localhost:99999", # Invalid port
|
| 247 |
-
model=self.model,
|
| 248 |
-
max_retries=2,
|
| 249 |
-
retry_delay=0.1 # Fast retry for testing
|
| 250 |
-
)
|
| 251 |
-
|
| 252 |
-
start_time = time.time()
|
| 253 |
-
try:
|
| 254 |
-
await retry_client.generate("Test prompt")
|
| 255 |
-
print(" Expected connection error but got response")
|
| 256 |
-
return False
|
| 257 |
-
except Exception as e:
|
| 258 |
-
elapsed = time.time() - start_time
|
| 259 |
-
print(f" Retry logic worked: {type(e).__name__}")
|
| 260 |
-
print(f" Time elapsed: {elapsed:.1f}s (expected ~0.3s for 2 retries)")
|
| 261 |
-
return elapsed > 0.2 # Should take time for retries
|
| 262 |
-
finally:
|
| 263 |
-
await retry_client.close()
|
| 264 |
-
|
| 265 |
-
except Exception as e:
|
| 266 |
-
print(f" Retry logic test failed: {e}")
|
| 267 |
-
return False
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
def print_setup_instructions():
|
| 271 |
-
"""Print setup instructions for users."""
|
| 272 |
-
print("LLM Connection Test")
|
| 273 |
-
print("=" * 20)
|
| 274 |
-
print()
|
| 275 |
-
print("Prerequisites:")
|
| 276 |
-
print("1. Ollama must be installed and running:")
|
| 277 |
-
print(" curl -fsSL https://ollama.ai/install.sh | sh")
|
| 278 |
-
print(" ollama serve")
|
| 279 |
-
print()
|
| 280 |
-
print("2. Pull a model (adjust model name in script if needed):")
|
| 281 |
-
print(" ollama pull llama2:7b")
|
| 282 |
-
print()
|
| 283 |
-
print("3. Verify Ollama is running:")
|
| 284 |
-
print(" curl http://localhost:11434/api/tags")
|
| 285 |
-
print()
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
async def main():
|
| 289 |
-
"""Main test function."""
|
| 290 |
-
print_setup_instructions()
|
| 291 |
-
|
| 292 |
-
# Parse command line arguments for custom host/model
|
| 293 |
-
host = "http://localhost:11434"
|
| 294 |
-
model = "llama2:7b"
|
| 295 |
-
|
| 296 |
-
if len(sys.argv) > 1:
|
| 297 |
-
host = sys.argv[1]
|
| 298 |
-
if len(sys.argv) > 2:
|
| 299 |
-
model = sys.argv[2]
|
| 300 |
-
|
| 301 |
-
print(f"Testing with host: {host}, model: {model}")
|
| 302 |
-
print()
|
| 303 |
-
|
| 304 |
-
# Run tests
|
| 305 |
-
tester = LLMTester(host=host, model=model)
|
| 306 |
-
success = await tester.run_all_tests()
|
| 307 |
-
|
| 308 |
-
return 0 if success else 1
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
if __name__ == "__main__":
|
| 312 |
-
try:
|
| 313 |
-
exit_code = asyncio.run(main())
|
| 314 |
-
sys.exit(exit_code)
|
| 315 |
-
except KeyboardInterrupt:
|
| 316 |
-
print("\n⏹️ Tests interrupted by user")
|
| 317 |
-
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/test_websocket.py
DELETED
|
@@ -1,250 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Test script for WebSocket functionality.
|
| 3 |
-
|
| 4 |
-
This script tests the WebSocket connection between frontend and backend
|
| 5 |
-
to ensure real-time messaging works correctly.
|
| 6 |
-
|
| 7 |
-
Usage:
|
| 8 |
-
python scripts/test_websocket.py
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
import asyncio
|
| 12 |
-
import json
|
| 13 |
-
import sys
|
| 14 |
-
import logging
|
| 15 |
-
from datetime import datetime
|
| 16 |
-
|
| 17 |
-
try:
|
| 18 |
-
import websockets
|
| 19 |
-
from websockets.exceptions import ConnectionClosed
|
| 20 |
-
except ImportError:
|
| 21 |
-
print("Error: websockets library not installed")
|
| 22 |
-
print("Install with: pip install websockets")
|
| 23 |
-
sys.exit(1)
|
| 24 |
-
|
| 25 |
-
# Configure logging
|
| 26 |
-
logging.basicConfig(level=logging.INFO)
|
| 27 |
-
logger = logging.getLogger(__name__)
|
| 28 |
-
|
| 29 |
-
async def test_websocket_connection():
|
| 30 |
-
"""Test basic WebSocket connection and messaging."""
|
| 31 |
-
|
| 32 |
-
# Test configuration
|
| 33 |
-
BACKEND_URL = "ws://localhost:8000"
|
| 34 |
-
CONVERSATION_ID = "test-conversation-123"
|
| 35 |
-
WS_URL = f"{BACKEND_URL}/ws/conversation/{CONVERSATION_ID}"
|
| 36 |
-
|
| 37 |
-
print(f"Testing WebSocket connection to: {WS_URL}")
|
| 38 |
-
|
| 39 |
-
try:
|
| 40 |
-
# Connect to WebSocket
|
| 41 |
-
async with websockets.connect(WS_URL) as websocket:
|
| 42 |
-
print("✅ WebSocket connection established")
|
| 43 |
-
|
| 44 |
-
# Test 1: Wait for connection confirmation
|
| 45 |
-
try:
|
| 46 |
-
response = await asyncio.wait_for(websocket.recv(), timeout=5.0)
|
| 47 |
-
data = json.loads(response)
|
| 48 |
-
|
| 49 |
-
if data.get("type") == "connection_status":
|
| 50 |
-
print("✅ Received connection confirmation")
|
| 51 |
-
print(f" Status: {data.get('status')}")
|
| 52 |
-
print(f" Message: {data.get('message')}")
|
| 53 |
-
else:
|
| 54 |
-
print(f"⚠️ Unexpected first message: {data}")
|
| 55 |
-
|
| 56 |
-
except asyncio.TimeoutError:
|
| 57 |
-
print("❌ No connection confirmation received within 5 seconds")
|
| 58 |
-
return False
|
| 59 |
-
|
| 60 |
-
# Test 2: Send conversation message
|
| 61 |
-
test_message = {
|
| 62 |
-
"type": "conversation_message",
|
| 63 |
-
"role": "surveyor",
|
| 64 |
-
"content": "Hello, this is a test message",
|
| 65 |
-
"conversation_id": CONVERSATION_ID,
|
| 66 |
-
"timestamp": datetime.now().isoformat()
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
await websocket.send(json.dumps(test_message))
|
| 70 |
-
print("✅ Sent test conversation message")
|
| 71 |
-
|
| 72 |
-
# Wait for echo/response
|
| 73 |
-
try:
|
| 74 |
-
response = await asyncio.wait_for(websocket.recv(), timeout=5.0)
|
| 75 |
-
data = json.loads(response)
|
| 76 |
-
|
| 77 |
-
if data.get("type") == "conversation_message":
|
| 78 |
-
print("✅ Received echoed conversation message")
|
| 79 |
-
print(f" Role: {data.get('role')}")
|
| 80 |
-
print(f" Content: {data.get('content')}")
|
| 81 |
-
else:
|
| 82 |
-
print(f"⚠️ Unexpected message type: {data.get('type')}")
|
| 83 |
-
|
| 84 |
-
except asyncio.TimeoutError:
|
| 85 |
-
print("❌ No response to conversation message within 5 seconds")
|
| 86 |
-
return False
|
| 87 |
-
|
| 88 |
-
# Test 3: Send heartbeat
|
| 89 |
-
heartbeat_message = {
|
| 90 |
-
"type": "heartbeat",
|
| 91 |
-
"content": "ping",
|
| 92 |
-
"timestamp": datetime.now().isoformat()
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
await websocket.send(json.dumps(heartbeat_message))
|
| 96 |
-
print("✅ Sent heartbeat message")
|
| 97 |
-
|
| 98 |
-
# Wait for heartbeat response
|
| 99 |
-
try:
|
| 100 |
-
response = await asyncio.wait_for(websocket.recv(), timeout=5.0)
|
| 101 |
-
data = json.loads(response)
|
| 102 |
-
|
| 103 |
-
if data.get("type") == "heartbeat_response":
|
| 104 |
-
print("✅ Received heartbeat response")
|
| 105 |
-
else:
|
| 106 |
-
print(f"⚠️ Unexpected heartbeat response: {data.get('type')}")
|
| 107 |
-
|
| 108 |
-
except asyncio.TimeoutError:
|
| 109 |
-
print("❌ No heartbeat response within 5 seconds")
|
| 110 |
-
return False
|
| 111 |
-
|
| 112 |
-
# Test 4: Send conversation control
|
| 113 |
-
control_message = {
|
| 114 |
-
"type": "conversation_control",
|
| 115 |
-
"action": "start",
|
| 116 |
-
"content": "Starting conversation",
|
| 117 |
-
"timestamp": datetime.now().isoformat()
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
await websocket.send(json.dumps(control_message))
|
| 121 |
-
print("✅ Sent conversation control message")
|
| 122 |
-
|
| 123 |
-
# Wait for control response
|
| 124 |
-
try:
|
| 125 |
-
response = await asyncio.wait_for(websocket.recv(), timeout=5.0)
|
| 126 |
-
data = json.loads(response)
|
| 127 |
-
|
| 128 |
-
if data.get("type") == "conversation_control":
|
| 129 |
-
print("✅ Received conversation control response")
|
| 130 |
-
print(f" Action: {data.get('action')}")
|
| 131 |
-
print(f" Message: {data.get('message')}")
|
| 132 |
-
else:
|
| 133 |
-
print(f"⚠️ Unexpected control response: {data.get('type')}")
|
| 134 |
-
|
| 135 |
-
except asyncio.TimeoutError:
|
| 136 |
-
print("❌ No conversation control response within 5 seconds")
|
| 137 |
-
return False
|
| 138 |
-
|
| 139 |
-
print("🎉 All WebSocket tests passed!")
|
| 140 |
-
return True
|
| 141 |
-
|
| 142 |
-
except ConnectionRefusedError:
|
| 143 |
-
print("❌ Connection refused - is the backend server running?")
|
| 144 |
-
print(" Start with: cd backend && uvicorn api.main:app --reload")
|
| 145 |
-
return False
|
| 146 |
-
|
| 147 |
-
except Exception as e:
|
| 148 |
-
print(f"❌ WebSocket test failed: {e}")
|
| 149 |
-
return False
|
| 150 |
-
|
| 151 |
-
async def test_invalid_messages():
|
| 152 |
-
"""Test handling of invalid messages."""
|
| 153 |
-
|
| 154 |
-
BACKEND_URL = "ws://localhost:8000"
|
| 155 |
-
CONVERSATION_ID = "test-invalid-messages"
|
| 156 |
-
WS_URL = f"{BACKEND_URL}/ws/conversation/{CONVERSATION_ID}"
|
| 157 |
-
|
| 158 |
-
print(f"\nTesting invalid message handling...")
|
| 159 |
-
|
| 160 |
-
try:
|
| 161 |
-
async with websockets.connect(WS_URL) as websocket:
|
| 162 |
-
# Skip connection confirmation
|
| 163 |
-
await websocket.recv()
|
| 164 |
-
|
| 165 |
-
# Test invalid JSON
|
| 166 |
-
await websocket.send("invalid json")
|
| 167 |
-
print("✅ Sent invalid JSON")
|
| 168 |
-
|
| 169 |
-
# Test missing required fields
|
| 170 |
-
invalid_message = {
|
| 171 |
-
"type": "conversation_message"
|
| 172 |
-
# Missing "content" field
|
| 173 |
-
}
|
| 174 |
-
await websocket.send(json.dumps(invalid_message))
|
| 175 |
-
print("✅ Sent message with missing fields")
|
| 176 |
-
|
| 177 |
-
# Test invalid message type
|
| 178 |
-
invalid_type = {
|
| 179 |
-
"type": "invalid_type",
|
| 180 |
-
"content": "test"
|
| 181 |
-
}
|
| 182 |
-
await websocket.send(json.dumps(invalid_type))
|
| 183 |
-
print("✅ Sent message with invalid type")
|
| 184 |
-
|
| 185 |
-
# Check for error responses
|
| 186 |
-
error_count = 0
|
| 187 |
-
for _ in range(3):
|
| 188 |
-
try:
|
| 189 |
-
response = await asyncio.wait_for(websocket.recv(), timeout=2.0)
|
| 190 |
-
data = json.loads(response)
|
| 191 |
-
if data.get("type") == "error":
|
| 192 |
-
error_count += 1
|
| 193 |
-
print(f"✅ Received error response: {data.get('error')}")
|
| 194 |
-
except asyncio.TimeoutError:
|
| 195 |
-
break
|
| 196 |
-
|
| 197 |
-
if error_count > 0:
|
| 198 |
-
print(f"✅ Received {error_count} error responses for invalid messages")
|
| 199 |
-
else:
|
| 200 |
-
print("⚠️ No error responses received for invalid messages")
|
| 201 |
-
|
| 202 |
-
return True
|
| 203 |
-
|
| 204 |
-
except Exception as e:
|
| 205 |
-
print(f"❌ Invalid message test failed: {e}")
|
| 206 |
-
return False
|
| 207 |
-
|
| 208 |
-
def print_usage():
|
| 209 |
-
"""Print usage instructions."""
|
| 210 |
-
print("WebSocket Test Script")
|
| 211 |
-
print("====================")
|
| 212 |
-
print()
|
| 213 |
-
print("This script tests the WebSocket functionality of the AI Survey Simulator.")
|
| 214 |
-
print()
|
| 215 |
-
print("Prerequisites:")
|
| 216 |
-
print("1. Backend server must be running:")
|
| 217 |
-
print(" cd backend && uvicorn api.main:app --reload")
|
| 218 |
-
print()
|
| 219 |
-
print("2. WebSocket library must be installed:")
|
| 220 |
-
print(" pip install websockets")
|
| 221 |
-
print()
|
| 222 |
-
print("Usage:")
|
| 223 |
-
print(" python scripts/test_websocket.py")
|
| 224 |
-
|
| 225 |
-
async def main():
|
| 226 |
-
"""Main test function."""
|
| 227 |
-
print_usage()
|
| 228 |
-
print("\nStarting WebSocket tests...\n")
|
| 229 |
-
|
| 230 |
-
# Test 1: Basic functionality
|
| 231 |
-
success1 = await test_websocket_connection()
|
| 232 |
-
|
| 233 |
-
# Test 2: Invalid message handling
|
| 234 |
-
success2 = await test_invalid_messages()
|
| 235 |
-
|
| 236 |
-
print("\n" + "="*50)
|
| 237 |
-
if success1 and success2:
|
| 238 |
-
print("🎉 All WebSocket tests completed successfully!")
|
| 239 |
-
return 0
|
| 240 |
-
else:
|
| 241 |
-
print("❌ Some tests failed. Check the output above.")
|
| 242 |
-
return 1
|
| 243 |
-
|
| 244 |
-
if __name__ == "__main__":
|
| 245 |
-
try:
|
| 246 |
-
exit_code = asyncio.run(main())
|
| 247 |
-
sys.exit(exit_code)
|
| 248 |
-
except KeyboardInterrupt:
|
| 249 |
-
print("\n⏹️ Tests interrupted by user")
|
| 250 |
-
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|