MikelWL commited on
Commit
e3892d4
·
1 Parent(s): 0a865e9

refactor: Simplified and streamlined the core demo and overall codebase

Browse files
.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
- # AI Survey Simulator Environment Variables
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 Backend Configuration
21
- # Choose one: ollama, vllm, openai
22
  LLM_BACKEND=ollama
 
 
 
23
 
24
- # Ollama Configuration
25
- OLLAMA_HOST=http://localhost:11434
26
- OLLAMA_MODEL=llama2:13b
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
- # PostgreSQL Configuration (for production)
48
- # DATABASE_TYPE=postgresql
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, Set
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
- sys.path.insert(0, str(Path(__file__).parent.parent.parent))
 
 
 
 
30
 
31
- from core.conversation_manager import ConversationManager, ConversationState
32
- from core.persona_system import PersonaSystem
33
- from api.websockets.conversation_ws import ConnectionManager
 
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 = "http://localhost:11434",
88
- model: str = "llama2:7b") -> bool:
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=host,
136
- model=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 ..services.conversation_service import get_conversation_service
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 ..services.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", "http://localhost:11434")
305
- model = data.get("model", "llama2:7b")
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
- import logging
 
 
 
 
 
 
18
 
19
  # Import WebSocket endpoint and manager
20
- from .websockets.conversation_ws import websocket_endpoint, manager
21
- from .routes.conversations import router as conversations_router
22
- from .services.conversation_service import initialize_conversation_service
 
 
 
23
 
24
- # Setup logging
25
- logging.basicConfig(level=logging.INFO)
 
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="0.0.0.0", port=8000)
 
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 sys
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="http://localhost:11434", description="Ollama server host")
49
- model: str = Field(default="llama2:7b", description="LLM model to use")
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 = "llama2:7b"):
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="llama2:13b")
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 _make_request():
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
- return await self._retry_request(_make_request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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": "llama2:7b",
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/personas directory
143
- personas_dir = Path(__file__).parent.parent.parent / "data" / "personas"
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: "llama2:13b"
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
- # Setup logging
27
- logging.basicConfig(level=logging.INFO)
 
 
 
 
28
  logger = logging.getLogger(__name__)
29
 
30
  # Global state
31
- backend_url = "http://localhost:8000"
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"ws://localhost:8000/ws/conversation/{conversation_id}"
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
- if not ws_manager or ws_manager.state != ManagerState.CONNECTED:
70
- return get_message_display(), "❌ Not connected to backend. Please connect first."
 
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": "http://localhost:11434",
86
- "model": "llama2:7b"
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
- return get_message_display(), "✅ Conversation started! AI responses will appear below..."
 
 
 
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 'Connect to Backend' to begin",
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 connect...",
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>Connect</strong> to backend first</li>
280
- <li><strong>Start Conversation</strong> to begin AI chat</li>
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>: Click refresh regularly to see new AI messages as they arrive!</small></p>
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
- llama2:7b model available
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
- refresh_btn.click(
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)