Spaces:
Sleeping
Sleeping
Commit
·
0452a50
1
Parent(s):
da3f5f6
Add Docker support and remove Ollama
Browse files- DOCKER_COMMANDS.md +266 -0
- DOCKER_GUIDE.md +225 -0
- Dockerfile +1 -0
- backend/api/mcp_clients/rag_client.py +1 -1
- backend/api/routes/admin.py +7 -2
- backend/api/routes/agent.py +3 -4
- backend/api/routes/analytics.py +77 -36
- backend/api/routes/rag.py +1 -1
- backend/api/services/agent_orchestrator.py +34 -25
- backend/api/services/document_ingestion.py +1 -1
- backend/api/services/llm_client.py +76 -164
- backend/api/services/metadata_extractor.py +1 -3
- backend/api/services/rule_enhancer.py +1 -3
- backend/api/storage/analytics_store.py +10 -11
- backend/mcp_server/common/logging.py +7 -1
- docker-commands.ps1 +56 -0
- docker-compose.yml +33 -0
- docker-entrypoint.sh +5 -2
- env.example +6 -15
- run-docker.ps1 +59 -0
DOCKER_COMMANDS.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Docker Commands for IntegraChat
|
| 2 |
+
|
| 3 |
+
## Quick Start Commands
|
| 4 |
+
|
| 5 |
+
### 1. Stop and Remove Existing Container
|
| 6 |
+
```powershell
|
| 7 |
+
docker rm -f integrachat
|
| 8 |
+
```
|
| 9 |
+
|
| 10 |
+
### 2. Build the Docker Image
|
| 11 |
+
```powershell
|
| 12 |
+
docker build -t integrachat:latest .
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
### 3. Run the Container
|
| 16 |
+
```powershell
|
| 17 |
+
docker run -d --name integrachat `
|
| 18 |
+
-p 7860:7860 `
|
| 19 |
+
-p 8000:8000 `
|
| 20 |
+
-p 8900:8900 `
|
| 21 |
+
--env-file .env `
|
| 22 |
+
-e DOCKER_CONTAINER=1 `
|
| 23 |
+
integrachat:latest
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### 4. View Logs
|
| 27 |
+
```powershell
|
| 28 |
+
# Follow logs (live)
|
| 29 |
+
docker logs -f integrachat
|
| 30 |
+
|
| 31 |
+
# Last 50 lines
|
| 32 |
+
docker logs --tail 50 integrachat
|
| 33 |
+
|
| 34 |
+
# Last 100 lines with timestamps
|
| 35 |
+
docker logs --tail 100 -t integrachat
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Container Management
|
| 41 |
+
|
| 42 |
+
### Check Container Status
|
| 43 |
+
```powershell
|
| 44 |
+
# Check if running
|
| 45 |
+
docker ps --filter "name=integrachat"
|
| 46 |
+
|
| 47 |
+
# Check all containers (including stopped)
|
| 48 |
+
docker ps -a --filter "name=integrachat"
|
| 49 |
+
|
| 50 |
+
# Detailed status
|
| 51 |
+
docker ps --filter "name=integrachat" --format "Container: {{.Names}} | Status: {{.Status}} | Ports: {{.Ports}}"
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### Stop Container
|
| 55 |
+
```powershell
|
| 56 |
+
docker stop integrachat
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### Start Container (if stopped)
|
| 60 |
+
```powershell
|
| 61 |
+
docker start integrachat
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### Restart Container
|
| 65 |
+
```powershell
|
| 66 |
+
docker restart integrachat
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### Remove Container
|
| 70 |
+
```powershell
|
| 71 |
+
# Stop and remove
|
| 72 |
+
docker rm -f integrachat
|
| 73 |
+
|
| 74 |
+
# Remove only if stopped
|
| 75 |
+
docker rm integrachat
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
## Image Management
|
| 81 |
+
|
| 82 |
+
### List Images
|
| 83 |
+
```powershell
|
| 84 |
+
docker images integrachat
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### Remove Image
|
| 88 |
+
```powershell
|
| 89 |
+
docker rmi integrachat:latest
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
### Rebuild Without Cache
|
| 93 |
+
```powershell
|
| 94 |
+
docker build --no-cache -t integrachat:latest .
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Debugging Commands
|
| 100 |
+
|
| 101 |
+
### Execute Commands Inside Container
|
| 102 |
+
```powershell
|
| 103 |
+
# Open shell in container
|
| 104 |
+
docker exec -it integrachat /bin/bash
|
| 105 |
+
|
| 106 |
+
# Run Python command
|
| 107 |
+
docker exec integrachat python --version
|
| 108 |
+
|
| 109 |
+
# Check environment variables
|
| 110 |
+
docker exec integrachat printenv | Select-String -Pattern "GROQ|MCP|API"
|
| 111 |
+
|
| 112 |
+
# Check if services are running
|
| 113 |
+
docker exec integrachat ps aux
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
### Check Service Health
|
| 117 |
+
```powershell
|
| 118 |
+
# Check FastAPI health
|
| 119 |
+
docker exec integrachat curl -s http://localhost:8000/health
|
| 120 |
+
|
| 121 |
+
# Check MCP server health
|
| 122 |
+
docker exec integrachat curl -s http://localhost:8900/health
|
| 123 |
+
|
| 124 |
+
# Check Gradio (from host)
|
| 125 |
+
Invoke-WebRequest -Uri http://localhost:7860 -UseBasicParsing -TimeoutSec 5
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
### View Service Logs
|
| 129 |
+
```powershell
|
| 130 |
+
# FastAPI logs
|
| 131 |
+
docker exec integrachat tail -n 50 /app/logs/fastapi.log
|
| 132 |
+
|
| 133 |
+
# MCP server logs
|
| 134 |
+
docker exec integrachat tail -n 50 /app/logs/mcp.log
|
| 135 |
+
|
| 136 |
+
# Gradio logs
|
| 137 |
+
docker exec integrachat tail -n 50 /app/logs/gradio.log
|
| 138 |
+
|
| 139 |
+
# All logs
|
| 140 |
+
docker exec integrachat tail -n 50 /app/logs/*.log
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
### Check Ports
|
| 144 |
+
```powershell
|
| 145 |
+
# Check what ports are mapped
|
| 146 |
+
docker port integrachat
|
| 147 |
+
|
| 148 |
+
# Check if ports are listening (from host)
|
| 149 |
+
netstat -an | Select-String -Pattern "7860|8000|8900"
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
---
|
| 153 |
+
|
| 154 |
+
## Complete Rebuild Sequence
|
| 155 |
+
|
| 156 |
+
```powershell
|
| 157 |
+
# 1. Stop and remove container
|
| 158 |
+
docker rm -f integrachat
|
| 159 |
+
|
| 160 |
+
# 2. Remove old image (optional)
|
| 161 |
+
docker rmi integrachat:latest
|
| 162 |
+
|
| 163 |
+
# 3. Build new image
|
| 164 |
+
docker build -t integrachat:latest .
|
| 165 |
+
|
| 166 |
+
# 4. Run container
|
| 167 |
+
docker run -d --name integrachat `
|
| 168 |
+
-p 7860:7860 `
|
| 169 |
+
-p 8000:8000 `
|
| 170 |
+
-p 8900:8900 `
|
| 171 |
+
--env-file .env `
|
| 172 |
+
-e DOCKER_CONTAINER=1 `
|
| 173 |
+
integrachat:latest
|
| 174 |
+
|
| 175 |
+
# 5. Check status
|
| 176 |
+
docker ps --filter "name=integrachat"
|
| 177 |
+
|
| 178 |
+
# 6. View logs
|
| 179 |
+
docker logs -f integrachat
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
---
|
| 183 |
+
|
| 184 |
+
## Quick Health Check
|
| 185 |
+
|
| 186 |
+
```powershell
|
| 187 |
+
# Check all services
|
| 188 |
+
Write-Host "Container Status:" -ForegroundColor Cyan
|
| 189 |
+
docker ps --filter "name=integrachat" --format " {{.Names}}: {{.Status}}"
|
| 190 |
+
|
| 191 |
+
Write-Host "`nService Health:" -ForegroundColor Cyan
|
| 192 |
+
Write-Host " FastAPI:" -NoNewline
|
| 193 |
+
docker exec integrachat curl -s http://localhost:8000/health 2>&1 | Out-Null
|
| 194 |
+
if ($LASTEXITCODE -eq 0) { Write-Host " ✓ Running" -ForegroundColor Green } else { Write-Host " ✗ Not responding" -ForegroundColor Red }
|
| 195 |
+
|
| 196 |
+
Write-Host " MCP Server:" -NoNewline
|
| 197 |
+
docker exec integrachat curl -s http://localhost:8900/health 2>&1 | Out-Null
|
| 198 |
+
if ($LASTEXITCODE -eq 0) { Write-Host " ✓ Running" -ForegroundColor Green } else { Write-Host " ✗ Not responding" -ForegroundColor Red }
|
| 199 |
+
|
| 200 |
+
Write-Host " Gradio UI:" -NoNewline
|
| 201 |
+
try { $response = Invoke-WebRequest -Uri http://localhost:7860 -UseBasicParsing -TimeoutSec 2; Write-Host " ✓ Running" -ForegroundColor Green } catch { Write-Host " ✗ Not responding" -ForegroundColor Red }
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
---
|
| 205 |
+
|
| 206 |
+
## Access URLs
|
| 207 |
+
|
| 208 |
+
Once the container is running, access:
|
| 209 |
+
|
| 210 |
+
- **Gradio UI**: http://localhost:7860
|
| 211 |
+
- **FastAPI API**: http://localhost:8000
|
| 212 |
+
- **API Docs**: http://localhost:8000/docs
|
| 213 |
+
- **MCP Server**: http://localhost:8900
|
| 214 |
+
- **MCP Server Docs**: http://localhost:8900/docs
|
| 215 |
+
|
| 216 |
+
---
|
| 217 |
+
|
| 218 |
+
## Troubleshooting
|
| 219 |
+
|
| 220 |
+
### Container won't start
|
| 221 |
+
```powershell
|
| 222 |
+
# Check logs for errors
|
| 223 |
+
docker logs integrachat
|
| 224 |
+
|
| 225 |
+
# Check if ports are already in use
|
| 226 |
+
netstat -an | Select-String -Pattern "7860|8000|8900"
|
| 227 |
+
```
|
| 228 |
+
|
| 229 |
+
### Services not responding
|
| 230 |
+
```powershell
|
| 231 |
+
# Restart container
|
| 232 |
+
docker restart integrachat
|
| 233 |
+
|
| 234 |
+
# Check service logs inside container
|
| 235 |
+
docker exec integrachat tail -n 100 /app/logs/*.log
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
### Clear everything and start fresh
|
| 239 |
+
```powershell
|
| 240 |
+
# Stop and remove container
|
| 241 |
+
docker rm -f integrachat
|
| 242 |
+
|
| 243 |
+
# Remove image
|
| 244 |
+
docker rmi integrachat:latest
|
| 245 |
+
|
| 246 |
+
# Clear build cache (optional)
|
| 247 |
+
docker builder prune -f
|
| 248 |
+
|
| 249 |
+
# Rebuild from scratch
|
| 250 |
+
docker build --no-cache -t integrachat:latest .
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
---
|
| 254 |
+
|
| 255 |
+
## Environment Variables
|
| 256 |
+
|
| 257 |
+
Make sure your `.env` file has:
|
| 258 |
+
- `GROQ_API_KEY` - Your Groq API key
|
| 259 |
+
- `GROQ_MODEL` - Model name (default: llama-3.1-8b-instant)
|
| 260 |
+
- `RAG_MCP_URL` - http://localhost:8900/rag
|
| 261 |
+
- `WEB_MCP_URL` - http://localhost:8900/web
|
| 262 |
+
- `ADMIN_MCP_URL` - http://localhost:8900/admin
|
| 263 |
+
- `MCP_PORT` - 8900
|
| 264 |
+
- `API_PORT` - 8000
|
| 265 |
+
- `POSTGRESQL_URL` - Your database connection string (optional)
|
| 266 |
+
|
DOCKER_GUIDE.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Docker Setup Guide for IntegraChat
|
| 2 |
+
|
| 3 |
+
## Quick Start
|
| 4 |
+
|
| 5 |
+
### Option 1: Use PowerShell Script (Easiest for Windows)
|
| 6 |
+
|
| 7 |
+
```powershell
|
| 8 |
+
# Run the helper script
|
| 9 |
+
.\run-docker.ps1
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
### Option 2: Build and Run with Docker Compose (Recommended)
|
| 13 |
+
|
| 14 |
+
```powershell
|
| 15 |
+
# PowerShell
|
| 16 |
+
docker-compose up -d
|
| 17 |
+
|
| 18 |
+
# Or with rebuild
|
| 19 |
+
docker-compose up -d --build
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
### Build and Run Manually
|
| 23 |
+
|
| 24 |
+
**PowerShell (Windows):**
|
| 25 |
+
```powershell
|
| 26 |
+
# Build the image
|
| 27 |
+
docker build -t integrachat:latest .
|
| 28 |
+
|
| 29 |
+
# Run the container (PowerShell uses backticks for line continuation)
|
| 30 |
+
docker run -d --name integrachat `
|
| 31 |
+
-p 7860:7860 -p 8000:8000 -p 8900:8900 `
|
| 32 |
+
-e DOCKER_CONTAINER=1 `
|
| 33 |
+
integrachat:latest
|
| 34 |
+
|
| 35 |
+
# Or use a single line:
|
| 36 |
+
docker run -d --name integrachat -p 7860:7860 -p 8000:8000 -p 8900:8900 -e DOCKER_CONTAINER=1 integrachat:latest
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
**Bash/Linux/Mac:**
|
| 40 |
+
```bash
|
| 41 |
+
# Build the image
|
| 42 |
+
docker build -t integrachat:latest .
|
| 43 |
+
|
| 44 |
+
# Run the container
|
| 45 |
+
docker run -d --name integrachat \
|
| 46 |
+
-p 7860:7860 -p 8000:8000 -p 8900:8900 \
|
| 47 |
+
-e DOCKER_CONTAINER=1 \
|
| 48 |
+
integrachat:latest
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## Container Management
|
| 52 |
+
|
| 53 |
+
### View Logs
|
| 54 |
+
```bash
|
| 55 |
+
# All logs (streaming)
|
| 56 |
+
docker logs -f integrachat
|
| 57 |
+
|
| 58 |
+
# Specific service logs
|
| 59 |
+
docker exec integrachat tail -f /app/logs/fastapi.log
|
| 60 |
+
docker exec integrachat tail -f /app/logs/gradio.log
|
| 61 |
+
docker exec integrachat tail -f /app/logs/mcp.log
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### Stop Container
|
| 65 |
+
```bash
|
| 66 |
+
docker stop integrachat
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### Start Container
|
| 70 |
+
```bash
|
| 71 |
+
docker start integrachat
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### Remove Container
|
| 75 |
+
```bash
|
| 76 |
+
docker stop integrachat
|
| 77 |
+
docker rm integrachat
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### Rebuild After Changes
|
| 81 |
+
|
| 82 |
+
**PowerShell:**
|
| 83 |
+
```powershell
|
| 84 |
+
docker stop integrachat
|
| 85 |
+
docker rm integrachat
|
| 86 |
+
docker build -t integrachat:latest .
|
| 87 |
+
docker run -d --name integrachat -p 7860:7860 -p 8000:8000 -p 8900:8900 -e DOCKER_CONTAINER=1 integrachat:latest
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
**Bash:**
|
| 91 |
+
```bash
|
| 92 |
+
docker stop integrachat
|
| 93 |
+
docker rm integrachat
|
| 94 |
+
docker build -t integrachat:latest .
|
| 95 |
+
docker run -d --name integrachat \
|
| 96 |
+
-p 7860:7860 -p 8000:8000 -p 8900:8900 \
|
| 97 |
+
-e DOCKER_CONTAINER=1 \
|
| 98 |
+
integrachat:latest
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Access Services
|
| 102 |
+
|
| 103 |
+
- **Gradio UI**: http://localhost:7860
|
| 104 |
+
- **FastAPI API**: http://localhost:8000
|
| 105 |
+
- **MCP Server**: http://localhost:8900
|
| 106 |
+
- **API Docs**: http://localhost:8000/docs
|
| 107 |
+
- **MCP Docs**: http://localhost:8900/docs
|
| 108 |
+
|
| 109 |
+
## Environment Variables
|
| 110 |
+
|
| 111 |
+
Create a `.env` file (or use docker-compose.yml) to configure:
|
| 112 |
+
|
| 113 |
+
```env
|
| 114 |
+
# LLM Configuration
|
| 115 |
+
LLM_BACKEND=groq # or "ollama"
|
| 116 |
+
GROQ_API_KEY=your_key_here
|
| 117 |
+
GROQ_MODEL=llama-3.1-8b-instant
|
| 118 |
+
|
| 119 |
+
# Supabase (optional - for analytics)
|
| 120 |
+
SUPABASE_URL=https://your-project.supabase.co
|
| 121 |
+
SUPABASE_SERVICE_KEY=your_service_key
|
| 122 |
+
|
| 123 |
+
# Ports (defaults shown)
|
| 124 |
+
API_PORT=8000
|
| 125 |
+
MCP_PORT=8900
|
| 126 |
+
GRADIO_PORT=7860
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
## Docker Compose
|
| 130 |
+
|
| 131 |
+
The `docker-compose.yml` file provides:
|
| 132 |
+
- Easy service management
|
| 133 |
+
- Environment variable support
|
| 134 |
+
- Volume mounting for logs
|
| 135 |
+
- Health checks
|
| 136 |
+
- Auto-restart on failure
|
| 137 |
+
|
| 138 |
+
### Using Docker Compose
|
| 139 |
+
|
| 140 |
+
```bash
|
| 141 |
+
# Start services
|
| 142 |
+
docker-compose up -d
|
| 143 |
+
|
| 144 |
+
# View logs
|
| 145 |
+
docker-compose logs -f
|
| 146 |
+
|
| 147 |
+
# Stop services
|
| 148 |
+
docker-compose down
|
| 149 |
+
|
| 150 |
+
# Rebuild and restart
|
| 151 |
+
docker-compose up -d --build
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
## PowerShell-Specific Notes
|
| 155 |
+
|
| 156 |
+
### Line Continuation
|
| 157 |
+
PowerShell uses backticks (`` ` ``) for line continuation, not backslashes (`\`):
|
| 158 |
+
|
| 159 |
+
```powershell
|
| 160 |
+
# ✅ Correct (PowerShell)
|
| 161 |
+
docker run -d --name integrachat `
|
| 162 |
+
-p 7860:7860 `
|
| 163 |
+
-p 8000:8000 `
|
| 164 |
+
integrachat:latest
|
| 165 |
+
|
| 166 |
+
# ✅ Also correct (single line)
|
| 167 |
+
docker run -d --name integrachat -p 7860:7860 -p 8000:8000 -p 8900:8900 -e DOCKER_CONTAINER=1 integrachat:latest
|
| 168 |
+
|
| 169 |
+
# ❌ Wrong (bash syntax - doesn't work in PowerShell)
|
| 170 |
+
docker run -d --name integrachat \
|
| 171 |
+
-p 7860:7860 \
|
| 172 |
+
integrachat:latest
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
### Quick Commands Script
|
| 176 |
+
Use `run-docker.ps1` for easy container management:
|
| 177 |
+
```powershell
|
| 178 |
+
.\run-docker.ps1
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
## Troubleshooting
|
| 182 |
+
|
| 183 |
+
### Check Container Status
|
| 184 |
+
```bash
|
| 185 |
+
docker ps -a | grep integrachat
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
### Check Service Health
|
| 189 |
+
```bash
|
| 190 |
+
# FastAPI health
|
| 191 |
+
curl http://localhost:8000/health
|
| 192 |
+
|
| 193 |
+
# MCP health
|
| 194 |
+
curl http://localhost:8900/health
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
### View All Logs
|
| 198 |
+
```bash
|
| 199 |
+
docker exec integrachat tail -n 100 /app/logs/*.log
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
### Restart Services Inside Container
|
| 203 |
+
```bash
|
| 204 |
+
# Container will auto-restart services, but you can manually restart:
|
| 205 |
+
docker restart integrachat
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
### Clean Up
|
| 209 |
+
```bash
|
| 210 |
+
# Remove container and image
|
| 211 |
+
docker stop integrachat
|
| 212 |
+
docker rm integrachat
|
| 213 |
+
docker rmi integrachat:latest
|
| 214 |
+
|
| 215 |
+
# Remove all unused Docker resources
|
| 216 |
+
docker system prune -a
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
## Notes
|
| 220 |
+
|
| 221 |
+
- The container runs all three services (MCP, FastAPI, Gradio) automatically
|
| 222 |
+
- Logs are written to `/app/logs/` inside the container
|
| 223 |
+
- The entrypoint script handles service startup and health checks
|
| 224 |
+
- Supabase warnings are expected if credentials are not configured (analytics will be disabled gracefully)
|
| 225 |
+
|
Dockerfile
CHANGED
|
@@ -8,6 +8,7 @@ WORKDIR /app
|
|
| 8 |
# Install system dependencies
|
| 9 |
RUN apt-get update && \
|
| 10 |
apt-get install -y --no-install-recommends \
|
|
|
|
| 11 |
build-essential \
|
| 12 |
curl \
|
| 13 |
git && \
|
|
|
|
| 8 |
# Install system dependencies
|
| 9 |
RUN apt-get update && \
|
| 10 |
apt-get install -y --no-install-recommends \
|
| 11 |
+
--fix-missing \
|
| 12 |
build-essential \
|
| 13 |
curl \
|
| 14 |
git && \
|
backend/api/mcp_clients/rag_client.py
CHANGED
|
@@ -11,7 +11,7 @@ class RAGClient:
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
def __init__(self):
|
| 14 |
-
self.base_url = os.getenv("RAG_MCP_URL", "http://localhost:
|
| 15 |
if not self.base_url:
|
| 16 |
raise ValueError("RAG_MCP_URL environment variable is not set")
|
| 17 |
self.search_endpoint = f"{self.base_url}/search"
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
def __init__(self):
|
| 14 |
+
self.base_url = os.getenv("RAG_MCP_URL", "http://localhost:8900/rag")
|
| 15 |
if not self.base_url:
|
| 16 |
raise ValueError("RAG_MCP_URL environment variable is not set")
|
| 17 |
self.search_endpoint = f"{self.base_url}/search"
|
backend/api/routes/admin.py
CHANGED
|
@@ -39,11 +39,16 @@ def _get_analytics_store() -> Optional[AnalyticsStore]:
|
|
| 39 |
try:
|
| 40 |
_analytics_store = AnalyticsStore()
|
| 41 |
except RuntimeError as exc:
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
_analytics_failed = True
|
| 44 |
_analytics_store = None
|
| 45 |
except Exception as exc: # pragma: no cover - unexpected failures
|
| 46 |
-
logger.debug("
|
| 47 |
_analytics_failed = True
|
| 48 |
_analytics_store = None
|
| 49 |
|
|
|
|
| 39 |
try:
|
| 40 |
_analytics_store = AnalyticsStore()
|
| 41 |
except RuntimeError as exc:
|
| 42 |
+
# Only log at warning level if credentials are configured (actual error)
|
| 43 |
+
# Otherwise log at debug level (expected when Supabase is not configured)
|
| 44 |
+
if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_KEY"):
|
| 45 |
+
logger.warning("Analytics disabled: %s", str(exc).split('\n')[0]) # Only first line
|
| 46 |
+
else:
|
| 47 |
+
logger.debug("Analytics disabled: %s", str(exc).split('\n')[0])
|
| 48 |
_analytics_failed = True
|
| 49 |
_analytics_store = None
|
| 50 |
except Exception as exc: # pragma: no cover - unexpected failures
|
| 51 |
+
logger.debug("Analytics unexpected init failure: %s", exc)
|
| 52 |
_analytics_failed = True
|
| 53 |
_analytics_store = None
|
| 54 |
|
backend/api/routes/agent.py
CHANGED
|
@@ -23,10 +23,9 @@ router = APIRouter()
|
|
| 23 |
|
| 24 |
|
| 25 |
orchestrator = AgentOrchestrator(
|
| 26 |
-
rag_mcp_url=os.getenv("RAG_MCP_URL", "http://localhost:
|
| 27 |
-
web_mcp_url=os.getenv("WEB_MCP_URL", "http://localhost:
|
| 28 |
-
admin_mcp_url=os.getenv("ADMIN_MCP_URL", "http://localhost:
|
| 29 |
-
llm_backend=os.getenv("LLM_BACKEND", "ollama")
|
| 30 |
)
|
| 31 |
|
| 32 |
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
orchestrator = AgentOrchestrator(
|
| 26 |
+
rag_mcp_url=os.getenv("RAG_MCP_URL", "http://localhost:8900/rag"),
|
| 27 |
+
web_mcp_url=os.getenv("WEB_MCP_URL", "http://localhost:8900/web"),
|
| 28 |
+
admin_mcp_url=os.getenv("ADMIN_MCP_URL", "http://localhost:8900/admin")
|
|
|
|
| 29 |
)
|
| 30 |
|
| 31 |
|
backend/api/routes/analytics.py
CHANGED
|
@@ -19,17 +19,24 @@ try:
|
|
| 19 |
analytics_store: Optional[AnalyticsStore] = AnalyticsStore()
|
| 20 |
else:
|
| 21 |
analytics_store = None
|
| 22 |
-
logger.
|
| 23 |
"AnalyticsStore: Supabase credentials not configured. "
|
| 24 |
"Analytics endpoints will return 503."
|
| 25 |
)
|
| 26 |
except RuntimeError as exc:
|
| 27 |
analytics_store = None
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
@router.get("/overview")
|
|
@@ -43,16 +50,30 @@ async def analytics_overview(
|
|
| 43 |
Includes total queries, tool usage, red-flag count, and active users.
|
| 44 |
"""
|
| 45 |
|
| 46 |
-
if analytics_store is None:
|
| 47 |
-
raise HTTPException(
|
| 48 |
-
status_code=503,
|
| 49 |
-
detail="Analytics is disabled because Supabase is not configured on this deployment.",
|
| 50 |
-
)
|
| 51 |
-
|
| 52 |
if not x_tenant_id:
|
| 53 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 54 |
require_api_permission(x_user_role, "view_analytics")
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 57 |
|
| 58 |
tool_usage = analytics_store.get_tool_usage_stats(x_tenant_id, since_timestamp)
|
|
@@ -83,16 +104,18 @@ async def analytics_tool_usage(
|
|
| 83 |
Includes counts, latency, tokens, and success/error rates.
|
| 84 |
"""
|
| 85 |
|
| 86 |
-
if analytics_store is None:
|
| 87 |
-
raise HTTPException(
|
| 88 |
-
status_code=503,
|
| 89 |
-
detail="Analytics is disabled because Supabase is not configured on this deployment.",
|
| 90 |
-
)
|
| 91 |
-
|
| 92 |
if not x_tenant_id:
|
| 93 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 94 |
require_api_permission(x_user_role, "view_analytics")
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 97 |
tool_usage = analytics_store.get_tool_usage_stats(x_tenant_id, since_timestamp)
|
| 98 |
|
|
@@ -115,16 +138,18 @@ async def analytics_redflags(
|
|
| 115 |
Includes rule details, severity, confidence, and timestamps.
|
| 116 |
"""
|
| 117 |
|
| 118 |
-
if analytics_store is None:
|
| 119 |
-
raise HTTPException(
|
| 120 |
-
status_code=503,
|
| 121 |
-
detail="Analytics is disabled because Supabase is not configured on this deployment.",
|
| 122 |
-
)
|
| 123 |
-
|
| 124 |
if not x_tenant_id:
|
| 125 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 126 |
require_api_permission(x_user_role, "view_analytics")
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 129 |
redflags = analytics_store.get_redflag_violations(x_tenant_id, limit, since_timestamp)
|
| 130 |
|
|
@@ -151,16 +176,24 @@ async def analytics_activity(
|
|
| 151 |
Includes total queries, active users, last query timestamp, and individual activity records for heatmap visualization.
|
| 152 |
"""
|
| 153 |
|
| 154 |
-
if analytics_store is None:
|
| 155 |
-
raise HTTPException(
|
| 156 |
-
status_code=503,
|
| 157 |
-
detail="Analytics is disabled because Supabase is not configured on this deployment.",
|
| 158 |
-
)
|
| 159 |
-
|
| 160 |
if not x_tenant_id:
|
| 161 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 162 |
require_api_permission(x_user_role, "view_analytics")
|
| 163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 165 |
activity = analytics_store.get_activity_summary(x_tenant_id, since_timestamp)
|
| 166 |
|
|
@@ -186,16 +219,24 @@ async def analytics_rag_quality(
|
|
| 186 |
Includes average hits, scores, and latency.
|
| 187 |
"""
|
| 188 |
|
| 189 |
-
if analytics_store is None:
|
| 190 |
-
raise HTTPException(
|
| 191 |
-
status_code=503,
|
| 192 |
-
detail="Analytics is disabled because Supabase is not configured on this deployment.",
|
| 193 |
-
)
|
| 194 |
-
|
| 195 |
if not x_tenant_id:
|
| 196 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 197 |
require_api_permission(x_user_role, "view_analytics")
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 200 |
rag_quality = analytics_store.get_rag_quality_metrics(x_tenant_id, since_timestamp)
|
| 201 |
|
|
|
|
| 19 |
analytics_store: Optional[AnalyticsStore] = AnalyticsStore()
|
| 20 |
else:
|
| 21 |
analytics_store = None
|
| 22 |
+
logger.debug(
|
| 23 |
"AnalyticsStore: Supabase credentials not configured. "
|
| 24 |
"Analytics endpoints will return 503."
|
| 25 |
)
|
| 26 |
except RuntimeError as exc:
|
| 27 |
analytics_store = None
|
| 28 |
+
# Only log at warning level if credentials are configured (actual error)
|
| 29 |
+
# Otherwise log at debug level (expected when Supabase is not configured)
|
| 30 |
+
if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_KEY"):
|
| 31 |
+
logger.warning(
|
| 32 |
+
"AnalyticsStore initialization failed (%s). Analytics endpoints will return 503.",
|
| 33 |
+
str(exc).split('\n')[0], # Only first line
|
| 34 |
+
)
|
| 35 |
+
else:
|
| 36 |
+
logger.debug(
|
| 37 |
+
"AnalyticsStore not configured (%s). Analytics endpoints will return 503.",
|
| 38 |
+
str(exc).split('\n')[0],
|
| 39 |
+
)
|
| 40 |
|
| 41 |
|
| 42 |
@router.get("/overview")
|
|
|
|
| 50 |
Includes total queries, tool usage, red-flag count, and active users.
|
| 51 |
"""
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
if not x_tenant_id:
|
| 54 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 55 |
require_api_permission(x_user_role, "view_analytics")
|
| 56 |
|
| 57 |
+
# Return empty data if analytics is not configured (instead of 503)
|
| 58 |
+
if analytics_store is None:
|
| 59 |
+
return {
|
| 60 |
+
"tenant_id": x_tenant_id,
|
| 61 |
+
"overview": {
|
| 62 |
+
"total_queries": 0,
|
| 63 |
+
"tool_usage": {},
|
| 64 |
+
"redflag_count": 0,
|
| 65 |
+
"active_users": 0,
|
| 66 |
+
"last_query": None,
|
| 67 |
+
"rag_quality": {
|
| 68 |
+
"total_searches": 0,
|
| 69 |
+
"avg_hits_per_search": 0,
|
| 70 |
+
"avg_score": 0.0,
|
| 71 |
+
"avg_top_score": 0.0,
|
| 72 |
+
"avg_latency_ms": 0.0
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 78 |
|
| 79 |
tool_usage = analytics_store.get_tool_usage_stats(x_tenant_id, since_timestamp)
|
|
|
|
| 104 |
Includes counts, latency, tokens, and success/error rates.
|
| 105 |
"""
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
if not x_tenant_id:
|
| 108 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 109 |
require_api_permission(x_user_role, "view_analytics")
|
| 110 |
|
| 111 |
+
# Return empty data if analytics is not configured (instead of 503)
|
| 112 |
+
if analytics_store is None:
|
| 113 |
+
return {
|
| 114 |
+
"tenant_id": x_tenant_id,
|
| 115 |
+
"tool_usage": {},
|
| 116 |
+
"period_days": days
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 120 |
tool_usage = analytics_store.get_tool_usage_stats(x_tenant_id, since_timestamp)
|
| 121 |
|
|
|
|
| 138 |
Includes rule details, severity, confidence, and timestamps.
|
| 139 |
"""
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
if not x_tenant_id:
|
| 142 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 143 |
require_api_permission(x_user_role, "view_analytics")
|
| 144 |
|
| 145 |
+
# Return empty data if analytics is not configured (instead of 503)
|
| 146 |
+
if analytics_store is None:
|
| 147 |
+
return {
|
| 148 |
+
"tenant_id": x_tenant_id,
|
| 149 |
+
"redflags": [],
|
| 150 |
+
"count": 0
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 154 |
redflags = analytics_store.get_redflag_violations(x_tenant_id, limit, since_timestamp)
|
| 155 |
|
|
|
|
| 176 |
Includes total queries, active users, last query timestamp, and individual activity records for heatmap visualization.
|
| 177 |
"""
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
if not x_tenant_id:
|
| 180 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 181 |
require_api_permission(x_user_role, "view_analytics")
|
| 182 |
|
| 183 |
+
# Return empty data if analytics is not configured (instead of 503)
|
| 184 |
+
if analytics_store is None:
|
| 185 |
+
return {
|
| 186 |
+
"tenant_id": x_tenant_id,
|
| 187 |
+
"activity": {
|
| 188 |
+
"total_queries": 0,
|
| 189 |
+
"active_users": 0,
|
| 190 |
+
"redflag_count": 0,
|
| 191 |
+
"last_query": None
|
| 192 |
+
},
|
| 193 |
+
"activities": [],
|
| 194 |
+
"period_days": days
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 198 |
activity = analytics_store.get_activity_summary(x_tenant_id, since_timestamp)
|
| 199 |
|
|
|
|
| 219 |
Includes average hits, scores, and latency.
|
| 220 |
"""
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
if not x_tenant_id:
|
| 223 |
raise HTTPException(status_code=400, detail="Missing tenant ID")
|
| 224 |
require_api_permission(x_user_role, "view_analytics")
|
| 225 |
|
| 226 |
+
# Return empty data if analytics is not configured (instead of 503)
|
| 227 |
+
if analytics_store is None:
|
| 228 |
+
return {
|
| 229 |
+
"tenant_id": x_tenant_id,
|
| 230 |
+
"rag_quality": {
|
| 231 |
+
"total_searches": 0,
|
| 232 |
+
"avg_hits_per_search": 0,
|
| 233 |
+
"avg_score": 0.0,
|
| 234 |
+
"avg_top_score": 0.0,
|
| 235 |
+
"avg_latency_ms": 0.0
|
| 236 |
+
},
|
| 237 |
+
"period_days": days
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
since_timestamp = int((datetime.now() - timedelta(days=days)).timestamp()) if days else None
|
| 241 |
rag_quality = analytics_store.get_rag_quality_metrics(x_tenant_id, since_timestamp)
|
| 242 |
|
backend/api/routes/rag.py
CHANGED
|
@@ -261,7 +261,7 @@ async def rag_ingest_document(
|
|
| 261 |
error_msg = (
|
| 262 |
f"RAG server error: {error_detail}\n\n"
|
| 263 |
f"Please check:\n"
|
| 264 |
-
f"1. RAG_MCP_URL is set correctly (default: http://localhost:
|
| 265 |
f"2. RAG MCP server is running\n"
|
| 266 |
f"3. Database connection (POSTGRESQL_URL) is configured in the RAG server"
|
| 267 |
)
|
|
|
|
| 261 |
error_msg = (
|
| 262 |
f"RAG server error: {error_detail}\n\n"
|
| 263 |
f"Please check:\n"
|
| 264 |
+
f"1. RAG_MCP_URL is set correctly (default: http://localhost:8900/rag)\n"
|
| 265 |
f"2. RAG MCP server is running\n"
|
| 266 |
f"3. Database connection (POSTGRESQL_URL) is configured in the RAG server"
|
| 267 |
)
|
backend/api/services/agent_orchestrator.py
CHANGED
|
@@ -40,9 +40,10 @@ load_dotenv()
|
|
| 40 |
|
| 41 |
class AgentOrchestrator:
|
| 42 |
|
| 43 |
-
def __init__(self, rag_mcp_url: str, web_mcp_url: str, admin_mcp_url: str
|
| 44 |
self.mcp = MCPClient(rag_mcp_url, web_mcp_url, admin_mcp_url)
|
| 45 |
-
|
|
|
|
| 46 |
|
| 47 |
# pass admin_mcp_url so detector can call back
|
| 48 |
self.redflag = RedFlagDetector(
|
|
@@ -68,15 +69,20 @@ class AgentOrchestrator:
|
|
| 68 |
return
|
| 69 |
|
| 70 |
if self._analytics_disabled:
|
| 71 |
-
|
| 72 |
else:
|
| 73 |
store = self._get_analytics()
|
| 74 |
if store is None:
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
elif store.use_supabase:
|
| 77 |
-
|
| 78 |
else:
|
| 79 |
-
|
| 80 |
|
| 81 |
AgentOrchestrator._analytics_backend_logged = True
|
| 82 |
|
|
@@ -90,11 +96,17 @@ class AgentOrchestrator:
|
|
| 90 |
try:
|
| 91 |
self._analytics = AnalyticsStore()
|
| 92 |
except RuntimeError as exc:
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
self._analytics_failed = True
|
| 95 |
self._analytics = None
|
| 96 |
except Exception as exc: # pragma: no cover - unexpected initialization failures
|
| 97 |
-
logger.debug("
|
| 98 |
self._analytics_failed = True
|
| 99 |
self._analytics = None
|
| 100 |
|
|
@@ -1169,14 +1181,13 @@ Answer:"""
|
|
| 1169 |
fallback = await self.llm.simple_call(req.message, temperature=req.temperature)
|
| 1170 |
except Exception as llm_error:
|
| 1171 |
error_msg = str(llm_error)
|
| 1172 |
-
if "
|
| 1173 |
fallback = (
|
| 1174 |
f"I encountered an error while processing your request: {str(e)}\n\n"
|
| 1175 |
-
f"Additionally, the AI service (
|
| 1176 |
f"To fix:\n"
|
| 1177 |
-
f"1.
|
| 1178 |
-
f"2.
|
| 1179 |
-
f"3. Pull model: `ollama pull {os.getenv('OLLAMA_MODEL', 'llama3.1:latest')}`"
|
| 1180 |
)
|
| 1181 |
else:
|
| 1182 |
fallback = f"I encountered an error while processing your request: {str(e)}. Additionally, the AI service is unavailable: {error_msg}"
|
|
@@ -1315,15 +1326,14 @@ Answer:"""
|
|
| 1315 |
except Exception as e:
|
| 1316 |
# If LLM fails, return a helpful error message
|
| 1317 |
error_msg = str(e)
|
| 1318 |
-
if "
|
| 1319 |
llm_out = (
|
| 1320 |
-
f"I couldn't connect to the AI service (
|
| 1321 |
f"Error: {error_msg}\n\n"
|
| 1322 |
f"To fix this:\n"
|
| 1323 |
-
f"1.
|
| 1324 |
-
f"2.
|
| 1325 |
-
f"3.
|
| 1326 |
-
f"4. Or set OLLAMA_URL and OLLAMA_MODEL in your .env file"
|
| 1327 |
)
|
| 1328 |
else:
|
| 1329 |
llm_out = f"I apologize, but I'm unable to process your request right now. The AI service is unavailable: {error_msg}"
|
|
@@ -1997,15 +2007,14 @@ Answer:"""
|
|
| 1997 |
tool_traces.append({"tool": "llm", "error": str(e)})
|
| 1998 |
error_msg = str(e)
|
| 1999 |
# Provide helpful error message
|
| 2000 |
-
if "
|
| 2001 |
fallback = (
|
| 2002 |
-
f"I couldn't connect to the AI service (
|
| 2003 |
f"Error: {error_msg}\n\n"
|
| 2004 |
f"To fix this:\n"
|
| 2005 |
-
f"1.
|
| 2006 |
-
f"2.
|
| 2007 |
-
f"3.
|
| 2008 |
-
f"4. Or set OLLAMA_URL and OLLAMA_MODEL in your .env file"
|
| 2009 |
)
|
| 2010 |
else:
|
| 2011 |
fallback = f"I encountered an error while synthesizing the response: {error_msg}"
|
|
|
|
| 40 |
|
| 41 |
class AgentOrchestrator:
|
| 42 |
|
| 43 |
+
def __init__(self, rag_mcp_url: str, web_mcp_url: str, admin_mcp_url: str):
|
| 44 |
self.mcp = MCPClient(rag_mcp_url, web_mcp_url, admin_mcp_url)
|
| 45 |
+
# Groq-only LLM client
|
| 46 |
+
self.llm = LLMClient(api_key=os.getenv("GROQ_API_KEY"), model=os.getenv("GROQ_MODEL"))
|
| 47 |
|
| 48 |
# pass admin_mcp_url so detector can call back
|
| 49 |
self.redflag = RedFlagDetector(
|
|
|
|
| 69 |
return
|
| 70 |
|
| 71 |
if self._analytics_disabled:
|
| 72 |
+
logger.info("Analytics: Disabled via ANALYTICS_DISABLED")
|
| 73 |
else:
|
| 74 |
store = self._get_analytics()
|
| 75 |
if store is None:
|
| 76 |
+
# Only log if credentials might be missing (not if package is missing)
|
| 77 |
+
import os
|
| 78 |
+
if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_KEY"):
|
| 79 |
+
logger.warning("Analytics: Disabled (Supabase initialization failed)")
|
| 80 |
+
else:
|
| 81 |
+
logger.debug("Analytics: Disabled (Supabase not configured)")
|
| 82 |
elif store.use_supabase:
|
| 83 |
+
logger.info("Analytics: Using Supabase backend")
|
| 84 |
else:
|
| 85 |
+
logger.warning("Analytics: Using fallback backend")
|
| 86 |
|
| 87 |
AgentOrchestrator._analytics_backend_logged = True
|
| 88 |
|
|
|
|
| 96 |
try:
|
| 97 |
self._analytics = AnalyticsStore()
|
| 98 |
except RuntimeError as exc:
|
| 99 |
+
# Only log at warning level if credentials are configured (actual error)
|
| 100 |
+
# Otherwise log at debug level (expected when Supabase is not configured)
|
| 101 |
+
import os
|
| 102 |
+
if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_KEY"):
|
| 103 |
+
logger.warning("Analytics disabled: %s", str(exc).split('\n')[0]) # Only first line
|
| 104 |
+
else:
|
| 105 |
+
logger.debug("Analytics disabled: %s", str(exc).split('\n')[0])
|
| 106 |
self._analytics_failed = True
|
| 107 |
self._analytics = None
|
| 108 |
except Exception as exc: # pragma: no cover - unexpected initialization failures
|
| 109 |
+
logger.debug("Analytics unexpected init failure: %s", exc)
|
| 110 |
self._analytics_failed = True
|
| 111 |
self._analytics = None
|
| 112 |
|
|
|
|
| 1181 |
fallback = await self.llm.simple_call(req.message, temperature=req.temperature)
|
| 1182 |
except Exception as llm_error:
|
| 1183 |
error_msg = str(llm_error)
|
| 1184 |
+
if "Groq API key" in error_msg or "GROQ_API_KEY" in error_msg:
|
| 1185 |
fallback = (
|
| 1186 |
f"I encountered an error while processing your request: {str(e)}\n\n"
|
| 1187 |
+
f"Additionally, the AI service (Groq) is unavailable: {error_msg}\n\n"
|
| 1188 |
f"To fix:\n"
|
| 1189 |
+
f"1. Get a free Groq API key from https://console.groq.com\n"
|
| 1190 |
+
f"2. Set GROQ_API_KEY in your .env file or environment variables"
|
|
|
|
| 1191 |
)
|
| 1192 |
else:
|
| 1193 |
fallback = f"I encountered an error while processing your request: {str(e)}. Additionally, the AI service is unavailable: {error_msg}"
|
|
|
|
| 1326 |
except Exception as e:
|
| 1327 |
# If LLM fails, return a helpful error message
|
| 1328 |
error_msg = str(e)
|
| 1329 |
+
if "Groq API key" in error_msg or "GROQ_API_KEY" in error_msg:
|
| 1330 |
llm_out = (
|
| 1331 |
+
f"I couldn't connect to the AI service (Groq). "
|
| 1332 |
f"Error: {error_msg}\n\n"
|
| 1333 |
f"To fix this:\n"
|
| 1334 |
+
f"1. Get a free Groq API key from https://console.groq.com\n"
|
| 1335 |
+
f"2. Set GROQ_API_KEY in your .env file or environment variables\n"
|
| 1336 |
+
f"3. Optionally set GROQ_MODEL (default: llama-3.1-8b-instant)"
|
|
|
|
| 1337 |
)
|
| 1338 |
else:
|
| 1339 |
llm_out = f"I apologize, but I'm unable to process your request right now. The AI service is unavailable: {error_msg}"
|
|
|
|
| 2007 |
tool_traces.append({"tool": "llm", "error": str(e)})
|
| 2008 |
error_msg = str(e)
|
| 2009 |
# Provide helpful error message
|
| 2010 |
+
if "Groq API key" in error_msg or "GROQ_API_KEY" in error_msg:
|
| 2011 |
fallback = (
|
| 2012 |
+
f"I couldn't connect to the AI service (Groq). "
|
| 2013 |
f"Error: {error_msg}\n\n"
|
| 2014 |
f"To fix this:\n"
|
| 2015 |
+
f"1. Get a free Groq API key from https://console.groq.com\n"
|
| 2016 |
+
f"2. Set GROQ_API_KEY in your .env file or environment variables\n"
|
| 2017 |
+
f"3. Optionally set GROQ_MODEL (default: llama-3.1-8b-instant)"
|
|
|
|
| 2018 |
)
|
| 2019 |
else:
|
| 2020 |
fallback = f"I encountered an error while synthesizing the response: {error_msg}"
|
backend/api/services/document_ingestion.py
CHANGED
|
@@ -324,7 +324,7 @@ async def process_ingestion(
|
|
| 324 |
raise RuntimeError(
|
| 325 |
f"Failed to send document to RAG MCP server: {str(e)}\n\n"
|
| 326 |
f"Please check:\n"
|
| 327 |
-
f"1. RAG_MCP_URL is set correctly (default: http://localhost:
|
| 328 |
f"2. RAG MCP server is running\n"
|
| 329 |
f"3. Database connection (POSTGRESQL_URL) is configured in the RAG server"
|
| 330 |
) from e
|
|
|
|
| 324 |
raise RuntimeError(
|
| 325 |
f"Failed to send document to RAG MCP server: {str(e)}\n\n"
|
| 326 |
f"Please check:\n"
|
| 327 |
+
f"1. RAG_MCP_URL is set correctly (default: http://localhost:8900/rag)\n"
|
| 328 |
f"2. RAG MCP server is running\n"
|
| 329 |
f"3. Database connection (POSTGRESQL_URL) is configured in the RAG server"
|
| 330 |
) from e
|
backend/api/services/llm_client.py
CHANGED
|
@@ -5,68 +5,65 @@ from typing import AsyncGenerator
|
|
| 5 |
|
| 6 |
class LLMClient:
|
| 7 |
|
| 8 |
-
def __init__(self,
|
| 9 |
-
self.backend = backend
|
| 10 |
-
self.url = url or os.getenv("OLLAMA_URL", "http://localhost:11434")
|
| 11 |
self.api_key = api_key or os.getenv("GROQ_API_KEY")
|
| 12 |
-
|
| 13 |
-
if backend == "groq":
|
| 14 |
-
self.model = model or os.getenv("GROQ_MODEL", "llama-3.1-70b-versatile")
|
| 15 |
-
else:
|
| 16 |
-
self.model = model or os.getenv("OLLAMA_MODEL", "llama3.1:latest")
|
| 17 |
self.http = httpx.AsyncClient(timeout=30)
|
| 18 |
|
| 19 |
-
|
| 20 |
async def simple_call(self, prompt: str, temperature: float = 0.0) -> str:
|
| 21 |
-
if self.
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
except httpx.ConnectError:
|
| 52 |
-
raise RuntimeError(
|
| 53 |
-
f"Cannot connect to Ollama at {self.url}. "
|
| 54 |
-
f"Is Ollama running? Start it with: ollama serve"
|
| 55 |
-
)
|
| 56 |
-
except Exception as e:
|
| 57 |
-
raise RuntimeError(f"LLM call failed: {str(e)}")
|
| 58 |
-
elif self.backend == "groq":
|
| 59 |
-
if not self.api_key:
|
| 60 |
-
raise RuntimeError(
|
| 61 |
-
"Groq API key not configured. Set GROQ_API_KEY environment variable. "
|
| 62 |
-
"Get a free API key at https://console.groq.com"
|
| 63 |
-
)
|
| 64 |
-
if not self.model:
|
| 65 |
-
raise RuntimeError("Groq model not configured. Set GROQ_MODEL environment variable.")
|
| 66 |
-
|
| 67 |
try:
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
"https://api.groq.com/openai/v1/chat/completions",
|
| 71 |
headers={
|
| 72 |
"Authorization": f"Bearer {self.api_key}",
|
|
@@ -78,117 +75,32 @@ class LLMClient:
|
|
| 78 |
{"role": "user", "content": prompt}
|
| 79 |
],
|
| 80 |
"temperature": temperature,
|
| 81 |
-
"stream":
|
| 82 |
}
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
except:
|
| 93 |
-
error_detail = e.response.text
|
| 94 |
-
raise RuntimeError(f"Groq API error: HTTP {e.response.status_code} - {error_detail}")
|
| 95 |
-
except Exception as e:
|
| 96 |
-
raise RuntimeError(f"Groq API call failed: {str(e)}")
|
| 97 |
-
else:
|
| 98 |
-
raise RuntimeError(f"Unsupported backend: {self.backend}. Supported backends: 'ollama', 'groq'")
|
| 99 |
-
|
| 100 |
-
async def stream_call(self, prompt: str, temperature: float = 0.0) -> AsyncGenerator[str, None]:
|
| 101 |
-
"""Stream LLM response token by token."""
|
| 102 |
-
if self.backend == "ollama":
|
| 103 |
-
if not self.url or not self.model:
|
| 104 |
-
raise RuntimeError(f"LLM not configured: url={self.url}, model={self.model}")
|
| 105 |
-
|
| 106 |
-
try:
|
| 107 |
-
async with httpx.AsyncClient(timeout=300.0) as client:
|
| 108 |
-
async with client.stream(
|
| 109 |
-
"POST",
|
| 110 |
-
f"{self.url}/api/generate",
|
| 111 |
-
json={
|
| 112 |
-
"model": self.model,
|
| 113 |
-
"prompt": prompt,
|
| 114 |
-
"stream": True,
|
| 115 |
-
"options": {"temperature": temperature}
|
| 116 |
-
}
|
| 117 |
-
) as response:
|
| 118 |
-
response.raise_for_status()
|
| 119 |
-
async for line in response.aiter_lines():
|
| 120 |
-
if line:
|
| 121 |
try:
|
| 122 |
-
data = json.loads(
|
| 123 |
-
|
|
|
|
| 124 |
if token:
|
| 125 |
yield token
|
| 126 |
-
# Check if done
|
| 127 |
-
if data.get("done", False):
|
| 128 |
-
break
|
| 129 |
except json.JSONDecodeError:
|
| 130 |
continue
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
except httpx.ConnectError:
|
| 134 |
-
raise RuntimeError(
|
| 135 |
-
f"Cannot connect to Ollama at {self.url}. "
|
| 136 |
-
f"Is Ollama running? Start it with: ollama serve"
|
| 137 |
-
)
|
| 138 |
-
except Exception as e:
|
| 139 |
-
raise RuntimeError(f"LLM streaming failed: {str(e)}")
|
| 140 |
-
elif self.backend == "groq":
|
| 141 |
-
if not self.api_key:
|
| 142 |
-
raise RuntimeError(
|
| 143 |
-
"Groq API key not configured. Set GROQ_API_KEY environment variable. "
|
| 144 |
-
"Get a free API key at https://console.groq.com"
|
| 145 |
-
)
|
| 146 |
-
if not self.model:
|
| 147 |
-
raise RuntimeError("Groq model not configured. Set GROQ_MODEL environment variable.")
|
| 148 |
-
|
| 149 |
try:
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
},
|
| 158 |
-
json={
|
| 159 |
-
"model": self.model,
|
| 160 |
-
"messages": [
|
| 161 |
-
{"role": "user", "content": prompt}
|
| 162 |
-
],
|
| 163 |
-
"temperature": temperature,
|
| 164 |
-
"stream": True
|
| 165 |
-
}
|
| 166 |
-
) as response:
|
| 167 |
-
response.raise_for_status()
|
| 168 |
-
async for line in response.aiter_lines():
|
| 169 |
-
if line:
|
| 170 |
-
# Groq uses Server-Sent Events format
|
| 171 |
-
if line.startswith("data: "):
|
| 172 |
-
data_str = line[6:] # Remove "data: " prefix
|
| 173 |
-
if data_str.strip() == "[DONE]":
|
| 174 |
-
break
|
| 175 |
-
try:
|
| 176 |
-
data = json.loads(data_str)
|
| 177 |
-
delta = data.get("choices", [{}])[0].get("delta", {})
|
| 178 |
-
token = delta.get("content", "")
|
| 179 |
-
if token:
|
| 180 |
-
yield token
|
| 181 |
-
except json.JSONDecodeError:
|
| 182 |
-
continue
|
| 183 |
-
except httpx.HTTPStatusError as e:
|
| 184 |
-
error_detail = "Unknown error"
|
| 185 |
-
try:
|
| 186 |
-
error_json = e.response.json()
|
| 187 |
-
error_detail = error_json.get("error", {}).get("message", str(error_json))
|
| 188 |
-
except:
|
| 189 |
-
error_detail = e.response.text
|
| 190 |
-
raise RuntimeError(f"Groq API streaming error: HTTP {e.response.status_code} - {error_detail}")
|
| 191 |
-
except Exception as e:
|
| 192 |
-
raise RuntimeError(f"Groq API streaming failed: {str(e)}")
|
| 193 |
-
else:
|
| 194 |
-
raise RuntimeError(f"Streaming not supported for backend: {self.backend}")
|
|
|
|
| 5 |
|
| 6 |
class LLMClient:
|
| 7 |
|
| 8 |
+
def __init__(self, api_key=None, model=None):
|
|
|
|
|
|
|
| 9 |
self.api_key = api_key or os.getenv("GROQ_API_KEY")
|
| 10 |
+
self.model = model or os.getenv("GROQ_MODEL", "llama-3.1-8b-instant")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
self.http = httpx.AsyncClient(timeout=30)
|
| 12 |
|
|
|
|
| 13 |
async def simple_call(self, prompt: str, temperature: float = 0.0) -> str:
|
| 14 |
+
if not self.api_key:
|
| 15 |
+
raise RuntimeError(
|
| 16 |
+
"Groq API key not configured. Set GROQ_API_KEY environment variable. "
|
| 17 |
+
"Get a free API key at https://console.groq.com"
|
| 18 |
+
)
|
| 19 |
+
if not self.model:
|
| 20 |
+
raise RuntimeError("Groq model not configured. Set GROQ_MODEL environment variable.")
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
# Groq uses OpenAI-compatible API
|
| 24 |
+
r = await self.http.post(
|
| 25 |
+
"https://api.groq.com/openai/v1/chat/completions",
|
| 26 |
+
headers={
|
| 27 |
+
"Authorization": f"Bearer {self.api_key}",
|
| 28 |
+
"Content-Type": "application/json"
|
| 29 |
+
},
|
| 30 |
+
json={
|
| 31 |
+
"model": self.model,
|
| 32 |
+
"messages": [
|
| 33 |
+
{"role": "user", "content": prompt}
|
| 34 |
+
],
|
| 35 |
+
"temperature": temperature,
|
| 36 |
+
"stream": False
|
| 37 |
+
}
|
| 38 |
+
)
|
| 39 |
+
r.raise_for_status()
|
| 40 |
+
response_data = r.json()
|
| 41 |
+
return response_data["choices"][0]["message"]["content"]
|
| 42 |
+
except httpx.HTTPStatusError as e:
|
| 43 |
+
error_detail = "Unknown error"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
try:
|
| 45 |
+
error_json = e.response.json()
|
| 46 |
+
error_detail = error_json.get("error", {}).get("message", str(error_json))
|
| 47 |
+
except:
|
| 48 |
+
error_detail = e.response.text
|
| 49 |
+
raise RuntimeError(f"Groq API error: HTTP {e.response.status_code} - {error_detail}")
|
| 50 |
+
except Exception as e:
|
| 51 |
+
raise RuntimeError(f"Groq API call failed: {str(e)}")
|
| 52 |
+
|
| 53 |
+
async def stream_call(self, prompt: str, temperature: float = 0.0) -> AsyncGenerator[str, None]:
|
| 54 |
+
"""Stream LLM response token by token."""
|
| 55 |
+
if not self.api_key:
|
| 56 |
+
raise RuntimeError(
|
| 57 |
+
"Groq API key not configured. Set GROQ_API_KEY environment variable. "
|
| 58 |
+
"Get a free API key at https://console.groq.com"
|
| 59 |
+
)
|
| 60 |
+
if not self.model:
|
| 61 |
+
raise RuntimeError("Groq model not configured. Set GROQ_MODEL environment variable.")
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
async with httpx.AsyncClient(timeout=300.0) as client:
|
| 65 |
+
async with client.stream(
|
| 66 |
+
"POST",
|
| 67 |
"https://api.groq.com/openai/v1/chat/completions",
|
| 68 |
headers={
|
| 69 |
"Authorization": f"Bearer {self.api_key}",
|
|
|
|
| 75 |
{"role": "user", "content": prompt}
|
| 76 |
],
|
| 77 |
"temperature": temperature,
|
| 78 |
+
"stream": True
|
| 79 |
}
|
| 80 |
+
) as response:
|
| 81 |
+
response.raise_for_status()
|
| 82 |
+
async for line in response.aiter_lines():
|
| 83 |
+
if line:
|
| 84 |
+
# Groq uses Server-Sent Events format
|
| 85 |
+
if line.startswith("data: "):
|
| 86 |
+
data_str = line[6:] # Remove "data: " prefix
|
| 87 |
+
if data_str.strip() == "[DONE]":
|
| 88 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
try:
|
| 90 |
+
data = json.loads(data_str)
|
| 91 |
+
delta = data.get("choices", [{}])[0].get("delta", {})
|
| 92 |
+
token = delta.get("content", "")
|
| 93 |
if token:
|
| 94 |
yield token
|
|
|
|
|
|
|
|
|
|
| 95 |
except json.JSONDecodeError:
|
| 96 |
continue
|
| 97 |
+
except httpx.HTTPStatusError as e:
|
| 98 |
+
error_detail = "Unknown error"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
try:
|
| 100 |
+
error_json = e.response.json()
|
| 101 |
+
error_detail = error_json.get("error", {}).get("message", str(error_json))
|
| 102 |
+
except:
|
| 103 |
+
error_detail = e.response.text
|
| 104 |
+
raise RuntimeError(f"Groq API streaming error: HTTP {e.response.status_code} - {error_detail}")
|
| 105 |
+
except Exception as e:
|
| 106 |
+
raise RuntimeError(f"Groq API streaming failed: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/api/services/metadata_extractor.py
CHANGED
|
@@ -24,10 +24,8 @@ class MetadataExtractor:
|
|
| 24 |
|
| 25 |
def __init__(self, llm_client: Optional[LLMClient] = None):
|
| 26 |
self.llm = llm_client or LLMClient(
|
| 27 |
-
backend=os.getenv("LLM_BACKEND", "ollama"),
|
| 28 |
-
url=os.getenv("OLLAMA_URL"),
|
| 29 |
api_key=os.getenv("GROQ_API_KEY"),
|
| 30 |
-
model=os.getenv("
|
| 31 |
)
|
| 32 |
|
| 33 |
async def extract_metadata(
|
|
|
|
| 24 |
|
| 25 |
def __init__(self, llm_client: Optional[LLMClient] = None):
|
| 26 |
self.llm = llm_client or LLMClient(
|
|
|
|
|
|
|
| 27 |
api_key=os.getenv("GROQ_API_KEY"),
|
| 28 |
+
model=os.getenv("GROQ_MODEL")
|
| 29 |
)
|
| 30 |
|
| 31 |
async def extract_metadata(
|
backend/api/services/rule_enhancer.py
CHANGED
|
@@ -16,10 +16,8 @@ class RuleEnhancer:
|
|
| 16 |
|
| 17 |
def __init__(self, llm_client: Optional[LLMClient] = None):
|
| 18 |
self.llm = llm_client or LLMClient(
|
| 19 |
-
backend=os.getenv("LLM_BACKEND", "ollama"),
|
| 20 |
-
url=os.getenv("OLLAMA_URL"),
|
| 21 |
api_key=os.getenv("GROQ_API_KEY"),
|
| 22 |
-
model=os.getenv("
|
| 23 |
)
|
| 24 |
|
| 25 |
async def enhance_rule(
|
|
|
|
| 16 |
|
| 17 |
def __init__(self, llm_client: Optional[LLMClient] = None):
|
| 18 |
self.llm = llm_client or LLMClient(
|
|
|
|
|
|
|
| 19 |
api_key=os.getenv("GROQ_API_KEY"),
|
| 20 |
+
model=os.getenv("GROQ_MODEL")
|
| 21 |
)
|
| 22 |
|
| 23 |
async def enhance_rule(
|
backend/api/storage/analytics_store.py
CHANGED
|
@@ -21,9 +21,13 @@ try:
|
|
| 21 |
from supabase import Client, create_client
|
| 22 |
|
| 23 |
SUPABASE_AVAILABLE = True
|
| 24 |
-
except ImportError:
|
|
|
|
| 25 |
Client = None # type: ignore
|
| 26 |
SUPABASE_AVAILABLE = False
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
logger = logging.getLogger(__name__)
|
| 29 |
|
|
@@ -63,15 +67,12 @@ class AnalyticsStore:
|
|
| 63 |
|
| 64 |
if not SUPABASE_AVAILABLE:
|
| 65 |
raise RuntimeError(
|
| 66 |
-
"Supabase package not installed. Install with: pip install supabase
|
| 67 |
-
"AnalyticsStore requires Supabase - SQLite fallback has been removed."
|
| 68 |
)
|
| 69 |
|
| 70 |
if not supabase_url or not supabase_key:
|
| 71 |
raise RuntimeError(
|
| 72 |
-
"Supabase credentials
|
| 73 |
-
"Set SUPABASE_URL and SUPABASE_SERVICE_KEY in your .env file.\n"
|
| 74 |
-
"AnalyticsStore requires Supabase - SQLite fallback has been removed."
|
| 75 |
)
|
| 76 |
|
| 77 |
self.use_supabase = True # Always True - no fallback
|
|
@@ -110,9 +111,8 @@ class AnalyticsStore:
|
|
| 110 |
except Exception as exc:
|
| 111 |
logger.error(f"❌ Failed to initialize Supabase client for analytics: {exc}")
|
| 112 |
raise RuntimeError(
|
| 113 |
-
f"Failed to initialize Supabase client: {exc}
|
| 114 |
-
"
|
| 115 |
-
"AnalyticsStore requires Supabase - SQLite fallback has been removed."
|
| 116 |
) from exc
|
| 117 |
|
| 118 |
def _quick_table_check(self):
|
|
@@ -192,8 +192,7 @@ class AnalyticsStore:
|
|
| 192 |
)
|
| 193 |
# Re-raise - no SQLite fallback
|
| 194 |
raise RuntimeError(
|
| 195 |
-
f"Failed to insert into Supabase table '{table}': {error_msg}
|
| 196 |
-
"AnalyticsStore requires Supabase - SQLite fallback has been removed."
|
| 197 |
) from exc
|
| 198 |
|
| 199 |
def _supabase_simple_select(
|
|
|
|
| 21 |
from supabase import Client, create_client
|
| 22 |
|
| 23 |
SUPABASE_AVAILABLE = True
|
| 24 |
+
except (ImportError, Exception) as e:
|
| 25 |
+
# Handle both ImportError and other exceptions (e.g., websockets.asyncio issues)
|
| 26 |
Client = None # type: ignore
|
| 27 |
SUPABASE_AVAILABLE = False
|
| 28 |
+
# Only log at debug level to avoid noise - this is expected in some deployments
|
| 29 |
+
import logging
|
| 30 |
+
logging.getLogger(__name__).debug(f"Supabase import failed: {e}")
|
| 31 |
|
| 32 |
logger = logging.getLogger(__name__)
|
| 33 |
|
|
|
|
| 67 |
|
| 68 |
if not SUPABASE_AVAILABLE:
|
| 69 |
raise RuntimeError(
|
| 70 |
+
"Supabase package not installed. Install with: pip install supabase"
|
|
|
|
| 71 |
)
|
| 72 |
|
| 73 |
if not supabase_url or not supabase_key:
|
| 74 |
raise RuntimeError(
|
| 75 |
+
"Supabase credentials required. Set SUPABASE_URL and SUPABASE_SERVICE_KEY."
|
|
|
|
|
|
|
| 76 |
)
|
| 77 |
|
| 78 |
self.use_supabase = True # Always True - no fallback
|
|
|
|
| 111 |
except Exception as exc:
|
| 112 |
logger.error(f"❌ Failed to initialize Supabase client for analytics: {exc}")
|
| 113 |
raise RuntimeError(
|
| 114 |
+
f"Failed to initialize Supabase client: {exc}. "
|
| 115 |
+
"Verify SUPABASE_URL and SUPABASE_SERVICE_KEY are correct."
|
|
|
|
| 116 |
) from exc
|
| 117 |
|
| 118 |
def _quick_table_check(self):
|
|
|
|
| 192 |
)
|
| 193 |
# Re-raise - no SQLite fallback
|
| 194 |
raise RuntimeError(
|
| 195 |
+
f"Failed to insert into Supabase table '{table}': {error_msg}"
|
|
|
|
| 196 |
) from exc
|
| 197 |
|
| 198 |
def _supabase_simple_select(
|
backend/mcp_server/common/logging.py
CHANGED
|
@@ -51,7 +51,13 @@ def _get_analytics_store() -> Optional["AnalyticsStore"]:
|
|
| 51 |
try:
|
| 52 |
_analytics_store = AnalyticsStore()
|
| 53 |
except RuntimeError as exc:
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
_analytics_failed = True
|
| 56 |
_analytics_store = None
|
| 57 |
except Exception as exc: # pragma: no cover - unexpected failures
|
|
|
|
| 51 |
try:
|
| 52 |
_analytics_store = AnalyticsStore()
|
| 53 |
except RuntimeError as exc:
|
| 54 |
+
# Only log at warning level if credentials are configured (actual error)
|
| 55 |
+
# Otherwise log at debug level (expected when Supabase is not configured)
|
| 56 |
+
import os
|
| 57 |
+
if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_KEY"):
|
| 58 |
+
logger.warning("Analytics disabled: %s", str(exc).split('\n')[0]) # Only first line
|
| 59 |
+
else:
|
| 60 |
+
logger.debug("Analytics disabled: %s", str(exc).split('\n')[0])
|
| 61 |
_analytics_failed = True
|
| 62 |
_analytics_store = None
|
| 63 |
except Exception as exc: # pragma: no cover - unexpected failures
|
docker-commands.ps1
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IntegraChat Docker Helper Commands for PowerShell
|
| 2 |
+
|
| 3 |
+
# Function to start/restart the container
|
| 4 |
+
function Start-IntegraChat {
|
| 5 |
+
Write-Host "Starting IntegraChat container..." -ForegroundColor Green
|
| 6 |
+
|
| 7 |
+
# Check if container exists
|
| 8 |
+
$exists = docker ps -a --filter "name=integrachat" --format "{{.Names}}"
|
| 9 |
+
|
| 10 |
+
if ($exists -eq "integrachat") {
|
| 11 |
+
Write-Host "Container exists. Stopping and removing..." -ForegroundColor Yellow
|
| 12 |
+
docker stop integrachat 2>$null
|
| 13 |
+
docker rm integrachat 2>$null
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
# Run the container
|
| 17 |
+
docker run -d --name integrachat `
|
| 18 |
+
-p 7860:7860 `
|
| 19 |
+
-p 8000:8000 `
|
| 20 |
+
-p 8900:8900 `
|
| 21 |
+
-e DOCKER_CONTAINER=1 `
|
| 22 |
+
integrachat:latest
|
| 23 |
+
|
| 24 |
+
if ($LASTEXITCODE -eq 0) {
|
| 25 |
+
Write-Host "`n✅ Container started!" -ForegroundColor Green
|
| 26 |
+
Write-Host "`nAccess services:" -ForegroundColor Cyan
|
| 27 |
+
Write-Host " • Gradio UI: http://localhost:7860"
|
| 28 |
+
Write-Host " • FastAPI: http://localhost:8000"
|
| 29 |
+
Write-Host " • MCP Server: http://localhost:8900"
|
| 30 |
+
Write-Host "`nView logs: docker logs -f integrachat" -ForegroundColor Yellow
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
# Function to stop the container
|
| 35 |
+
function Stop-IntegraChat {
|
| 36 |
+
Write-Host "Stopping IntegraChat container..." -ForegroundColor Yellow
|
| 37 |
+
docker stop integrachat
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
# Function to view logs
|
| 41 |
+
function Show-IntegraChatLogs {
|
| 42 |
+
docker logs -f integrachat
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
# Function to rebuild
|
| 46 |
+
function Rebuild-IntegraChat {
|
| 47 |
+
Write-Host "Rebuilding IntegraChat..." -ForegroundColor Green
|
| 48 |
+
docker stop integrachat 2>$null
|
| 49 |
+
docker rm integrachat 2>$null
|
| 50 |
+
docker build -t integrachat:latest .
|
| 51 |
+
Start-IntegraChat
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
# Export functions
|
| 55 |
+
Export-ModuleMember -Function Start-IntegraChat, Stop-IntegraChat, Show-IntegraChatLogs, Rebuild-IntegraChat
|
| 56 |
+
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
integrachat:
|
| 3 |
+
build:
|
| 4 |
+
context: .
|
| 5 |
+
dockerfile: Dockerfile
|
| 6 |
+
container_name: integrachat
|
| 7 |
+
ports:
|
| 8 |
+
- "7860:7860" # Gradio UI
|
| 9 |
+
- "8000:8000" # FastAPI
|
| 10 |
+
- "8900:8900" # MCP Server
|
| 11 |
+
environment:
|
| 12 |
+
- API_PORT=8000
|
| 13 |
+
- MCP_PORT=8900
|
| 14 |
+
- GRADIO_PORT=7860
|
| 15 |
+
- DOCKER_CONTAINER=1
|
| 16 |
+
# Add your environment variables here or use env_file
|
| 17 |
+
# - SUPABASE_URL=${SUPABASE_URL}
|
| 18 |
+
# - SUPABASE_SERVICE_KEY=${SUPABASE_SERVICE_KEY}
|
| 19 |
+
# - GROQ_API_KEY=${GROQ_API_KEY}
|
| 20 |
+
# - OLLAMA_BASE_URL=${OLLAMA_BASE_URL}
|
| 21 |
+
env_file:
|
| 22 |
+
- .env # Optional: load from .env file if it exists
|
| 23 |
+
volumes:
|
| 24 |
+
# Optional: mount logs directory for persistence
|
| 25 |
+
- ./logs:/app/logs
|
| 26 |
+
restart: unless-stopped
|
| 27 |
+
healthcheck:
|
| 28 |
+
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
| 29 |
+
interval: 30s
|
| 30 |
+
timeout: 10s
|
| 31 |
+
retries: 3
|
| 32 |
+
start_period: 60s
|
| 33 |
+
|
docker-entrypoint.sh
CHANGED
|
@@ -19,8 +19,11 @@ log "API_PORT=${API_PORT}, MCP_PORT=${MCP_PORT}, GRADIO_PORT=${GRADIO_PORT}"
|
|
| 19 |
|
| 20 |
cleanup() {
|
| 21 |
log "Received termination signal. Stopping services..."
|
| 22 |
-
# Kill child processes
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
| 24 |
wait || true
|
| 25 |
log "All services stopped. Exiting."
|
| 26 |
}
|
|
|
|
| 19 |
|
| 20 |
cleanup() {
|
| 21 |
log "Received termination signal. Stopping services..."
|
| 22 |
+
# Kill child processes (only if they exist)
|
| 23 |
+
[ -n "${MCP_PID:-}" ] && kill "${MCP_PID}" 2>/dev/null || true
|
| 24 |
+
[ -n "${API_PID:-}" ] && kill "${API_PID}" 2>/dev/null || true
|
| 25 |
+
[ -n "${GRADIO_PID:-}" ] && kill "${GRADIO_PID}" 2>/dev/null || true
|
| 26 |
+
[ -n "${TAIL_PID:-}" ] && kill "${TAIL_PID}" 2>/dev/null || true
|
| 27 |
wait || true
|
| 28 |
log "All services stopped. Exiting."
|
| 29 |
}
|
env.example
CHANGED
|
@@ -11,28 +11,19 @@ SUPABASE_SERVICE_KEY=your_service_role_key_here
|
|
| 11 |
POSTGRESQL_URL=postgresql://user:password@host:port/database
|
| 12 |
|
| 13 |
# =============================================================
|
| 14 |
-
# LLM CONFIGURATION
|
| 15 |
# =============================================================
|
| 16 |
-
# Backend selection: "ollama" (local) or "groq" (cloud API)
|
| 17 |
-
# For Hugging Face Spaces, use "groq"
|
| 18 |
-
LLM_BACKEND=groq
|
| 19 |
-
|
| 20 |
-
# Option 1: Using Groq API (recommended for Hugging Face Spaces)
|
| 21 |
# Get free API key at https://console.groq.com
|
| 22 |
GROQ_API_KEY=your_groq_api_key_here
|
| 23 |
-
GROQ_MODEL=llama-3.1-
|
| 24 |
-
|
| 25 |
-
# Option 2: Using local Ollama (for local development)
|
| 26 |
-
# OLLAMA_URL=http://localhost:11434
|
| 27 |
-
# OLLAMA_MODEL=llama3.1:latest
|
| 28 |
|
| 29 |
# =============================================================
|
| 30 |
# MCP SERVER CONFIG
|
| 31 |
# =============================================================
|
| 32 |
-
#
|
| 33 |
-
RAG_MCP_URL=http://localhost:
|
| 34 |
-
WEB_MCP_URL=http://localhost:
|
| 35 |
-
ADMIN_MCP_URL=http://localhost:
|
| 36 |
|
| 37 |
# Unified MCP server identifier (namespaced tools)
|
| 38 |
MCP_SERVER_ID=integrachat
|
|
|
|
| 11 |
POSTGRESQL_URL=postgresql://user:password@host:port/database
|
| 12 |
|
| 13 |
# =============================================================
|
| 14 |
+
# LLM CONFIGURATION (Groq Only)
|
| 15 |
# =============================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
# Get free API key at https://console.groq.com
|
| 17 |
GROQ_API_KEY=your_groq_api_key_here
|
| 18 |
+
GROQ_MODEL=llama-3.1-8b-instant
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# =============================================================
|
| 21 |
# MCP SERVER CONFIG
|
| 22 |
# =============================================================
|
| 23 |
+
# Unified MCP server endpoints (running on port 8900)
|
| 24 |
+
RAG_MCP_URL=http://localhost:8900/rag
|
| 25 |
+
WEB_MCP_URL=http://localhost:8900/web
|
| 26 |
+
ADMIN_MCP_URL=http://localhost:8900/admin
|
| 27 |
|
| 28 |
# Unified MCP server identifier (namespaced tools)
|
| 29 |
MCP_SERVER_ID=integrachat
|
run-docker.ps1
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PowerShell script to run IntegraChat Docker container
|
| 2 |
+
|
| 3 |
+
# Stop and remove existing container if it exists
|
| 4 |
+
Write-Host "Checking for existing container..." -ForegroundColor Yellow
|
| 5 |
+
$existing = docker ps -a --filter "name=integrachat" --format "{{.Names}}" 2>&1
|
| 6 |
+
if ($existing -eq "integrachat") {
|
| 7 |
+
Write-Host "Removing existing container (force)..." -ForegroundColor Yellow
|
| 8 |
+
# Use -f to force remove (stops and removes in one command)
|
| 9 |
+
docker rm -f integrachat 2>&1 | Out-Null
|
| 10 |
+
Start-Sleep -Seconds 1
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
# Build the image
|
| 14 |
+
Write-Host "Building Docker image (this may take a few minutes)..." -ForegroundColor Green
|
| 15 |
+
Write-Host "Progress will be shown below..." -ForegroundColor Gray
|
| 16 |
+
docker build -t integrachat:latest .
|
| 17 |
+
|
| 18 |
+
if ($LASTEXITCODE -ne 0) {
|
| 19 |
+
Write-Host "Build failed! Check the error messages above." -ForegroundColor Red
|
| 20 |
+
exit 1
|
| 21 |
+
}
|
| 22 |
+
Write-Host "Build completed successfully!" -ForegroundColor Green
|
| 23 |
+
|
| 24 |
+
# Check if .env file exists
|
| 25 |
+
if (-not (Test-Path .env)) {
|
| 26 |
+
Write-Host "Warning: .env file not found. Creating from env.example..." -ForegroundColor Yellow
|
| 27 |
+
if (Test-Path env.example) {
|
| 28 |
+
Copy-Item env.example .env
|
| 29 |
+
Write-Host "Created .env file. Please update it with your configuration." -ForegroundColor Yellow
|
| 30 |
+
} else {
|
| 31 |
+
Write-Host "Error: env.example not found. Cannot create .env file." -ForegroundColor Red
|
| 32 |
+
exit 1
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
# Run the container
|
| 37 |
+
Write-Host "Starting container..." -ForegroundColor Green
|
| 38 |
+
docker run -d --name integrachat `
|
| 39 |
+
-p 7860:7860 `
|
| 40 |
+
-p 8000:8000 `
|
| 41 |
+
-p 8900:8900 `
|
| 42 |
+
--env-file .env `
|
| 43 |
+
-e DOCKER_CONTAINER=1 `
|
| 44 |
+
integrachat:latest
|
| 45 |
+
|
| 46 |
+
if ($LASTEXITCODE -eq 0) {
|
| 47 |
+
Write-Host "Container started successfully!" -ForegroundColor Green
|
| 48 |
+
Write-Host ""
|
| 49 |
+
Write-Host "Access services:" -ForegroundColor Cyan
|
| 50 |
+
Write-Host " - Gradio UI: http://localhost:7860" -ForegroundColor White
|
| 51 |
+
Write-Host " - FastAPI: http://localhost:8000" -ForegroundColor White
|
| 52 |
+
Write-Host " - MCP Server: http://localhost:8900" -ForegroundColor White
|
| 53 |
+
Write-Host ""
|
| 54 |
+
Write-Host "View logs: docker logs -f integrachat" -ForegroundColor Yellow
|
| 55 |
+
} else {
|
| 56 |
+
Write-Host "Failed to start container!" -ForegroundColor Red
|
| 57 |
+
exit 1
|
| 58 |
+
}
|
| 59 |
+
|