Spaces:
Sleeping
Sleeping
feat(v8): KeyPool Multi-API Router — Gemini + SambaNova Primary LLMs
Browse filesGod Agent OS v8: KeyPool system with Gemini (6 keys) + SambaNova (9 keys), AIRouterV8, main_v8.py entry point, AI Router Panel UI
- .github/workflows/deploy.yml +22 -23
- .gitignore +48 -0
- README.md +59 -35
- backend/Dockerfile.hf +11 -1
- backend/__pycache__/main.cpython-312.pyc +0 -0
- backend/agents/debug_agent.py +21 -16
- backend/agents/reasoning_agent.py +114 -237
- backend/ai_router/__init__.py +4 -2
- backend/ai_router/key_pool.py +131 -0
- backend/ai_router/router_v8.py +407 -0
- backend/main_v8.py +326 -0
- frontend/app/page.tsx +5 -2
- frontend/components/layout/AIRouterPanel.tsx +268 -0
- frontend/components/layout/Sidebar.tsx +2 -1
- frontend/hooks/useAgentStore.ts +1 -1
.github/workflows/deploy.yml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
name: 🚀 God Agent OS
|
| 2 |
|
| 3 |
on:
|
| 4 |
push:
|
|
@@ -10,7 +10,7 @@ env:
|
|
| 10 |
VERCEL_PROJECT: god-agent-os
|
| 11 |
|
| 12 |
jobs:
|
| 13 |
-
# ── Build &
|
| 14 |
build-check:
|
| 15 |
name: ✅ Build Check
|
| 16 |
runs-on: ubuntu-latest
|
|
@@ -28,10 +28,12 @@ jobs:
|
|
| 28 |
cd backend
|
| 29 |
pip install -r requirements.txt
|
| 30 |
|
| 31 |
-
- name: Syntax check backend
|
| 32 |
run: |
|
| 33 |
cd backend
|
| 34 |
-
python -m py_compile
|
|
|
|
|
|
|
| 35 |
python -m py_compile agents/orchestrator_v7.py
|
| 36 |
python -m py_compile agents/browser_agent.py
|
| 37 |
python -m py_compile agents/file_agent.py
|
|
@@ -55,7 +57,7 @@ jobs:
|
|
| 55 |
env:
|
| 56 |
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL || 'https://PYAE1994-autonomous-coding-system.hf.space' }}
|
| 57 |
|
| 58 |
-
# ── Deploy to Hugging Face Spaces ────────────────────────────────────────
|
| 59 |
deploy-huggingface:
|
| 60 |
name: 🤗 Deploy to HF Spaces
|
| 61 |
runs-on: ubuntu-latest
|
|
@@ -72,23 +74,24 @@ jobs:
|
|
| 72 |
git config --global user.email "action@github.com"
|
| 73 |
git config --global user.name "God Agent CI"
|
| 74 |
|
| 75 |
-
- name: Deploy backend to HF Space
|
| 76 |
env:
|
| 77 |
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 78 |
run: |
|
| 79 |
-
# Install HF CLI
|
| 80 |
pip install huggingface_hub -q
|
| 81 |
|
| 82 |
-
# Clone HF space
|
| 83 |
git clone https://pyae1994:$HF_TOKEN@huggingface.co/spaces/PYAE1994/autonomous-coding-system /tmp/hf-space
|
| 84 |
-
|
| 85 |
# Copy backend files
|
| 86 |
cp -r backend/. /tmp/hf-space/
|
| 87 |
-
|
| 88 |
-
#
|
|
|
|
|
|
|
|
|
|
| 89 |
cat > /tmp/hf-space/README.md << 'EOF'
|
| 90 |
---
|
| 91 |
-
title: God Agent OS
|
| 92 |
emoji: 🤖
|
| 93 |
colorFrom: indigo
|
| 94 |
colorTo: purple
|
|
@@ -96,27 +99,23 @@ jobs:
|
|
| 96 |
app_port: 7860
|
| 97 |
pinned: true
|
| 98 |
license: mit
|
| 99 |
-
short_description: Autonomous Engineering OS —
|
| 100 |
---
|
| 101 |
|
| 102 |
-
# 🤖 God Agent OS
|
| 103 |
-
**
|
| 104 |
|
| 105 |
-
16-agent
|
| 106 |
EOF
|
| 107 |
|
| 108 |
-
# Use HF Dockerfile
|
| 109 |
-
cp backend/Dockerfile.hf /tmp/hf-space/Dockerfile
|
| 110 |
-
|
| 111 |
-
# Commit and push
|
| 112 |
cd /tmp/hf-space
|
| 113 |
git add -A
|
| 114 |
-
git diff --cached --quiet || git commit -m "🚀 God Agent OS
|
| 115 |
git push origin main || git push origin master || true
|
| 116 |
-
|
| 117 |
echo "✅ Deployed to HF Space: https://huggingface.co/spaces/PYAE1994/autonomous-coding-system"
|
| 118 |
|
| 119 |
-
# ── Deploy to Vercel ─────────────────────────────────────────────────────
|
| 120 |
deploy-vercel:
|
| 121 |
name: ▲ Deploy to Vercel
|
| 122 |
runs-on: ubuntu-latest
|
|
|
|
| 1 |
+
name: 🚀 God Agent OS v8 — Auto Deploy
|
| 2 |
|
| 3 |
on:
|
| 4 |
push:
|
|
|
|
| 10 |
VERCEL_PROJECT: god-agent-os
|
| 11 |
|
| 12 |
jobs:
|
| 13 |
+
# ── Build & Syntax Check ──────────────────────────────────────────────────
|
| 14 |
build-check:
|
| 15 |
name: ✅ Build Check
|
| 16 |
runs-on: ubuntu-latest
|
|
|
|
| 28 |
cd backend
|
| 29 |
pip install -r requirements.txt
|
| 30 |
|
| 31 |
+
- name: Syntax check backend (v8)
|
| 32 |
run: |
|
| 33 |
cd backend
|
| 34 |
+
python -m py_compile main_v8.py
|
| 35 |
+
python -m py_compile ai_router/router_v8.py
|
| 36 |
+
python -m py_compile ai_router/key_pool.py
|
| 37 |
python -m py_compile agents/orchestrator_v7.py
|
| 38 |
python -m py_compile agents/browser_agent.py
|
| 39 |
python -m py_compile agents/file_agent.py
|
|
|
|
| 57 |
env:
|
| 58 |
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL || 'https://PYAE1994-autonomous-coding-system.hf.space' }}
|
| 59 |
|
| 60 |
+
# ── Deploy to Hugging Face Spaces ────────────────────────────────────────
|
| 61 |
deploy-huggingface:
|
| 62 |
name: 🤗 Deploy to HF Spaces
|
| 63 |
runs-on: ubuntu-latest
|
|
|
|
| 74 |
git config --global user.email "action@github.com"
|
| 75 |
git config --global user.name "God Agent CI"
|
| 76 |
|
| 77 |
+
- name: Deploy backend to HF Space (v8)
|
| 78 |
env:
|
| 79 |
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 80 |
run: |
|
|
|
|
| 81 |
pip install huggingface_hub -q
|
| 82 |
|
|
|
|
| 83 |
git clone https://pyae1994:$HF_TOKEN@huggingface.co/spaces/PYAE1994/autonomous-coding-system /tmp/hf-space
|
| 84 |
+
|
| 85 |
# Copy backend files
|
| 86 |
cp -r backend/. /tmp/hf-space/
|
| 87 |
+
|
| 88 |
+
# Use v8 Dockerfile
|
| 89 |
+
cp backend/Dockerfile.hf /tmp/hf-space/Dockerfile
|
| 90 |
+
|
| 91 |
+
# Create HF README with v8 metadata
|
| 92 |
cat > /tmp/hf-space/README.md << 'EOF'
|
| 93 |
---
|
| 94 |
+
title: God Agent OS v8
|
| 95 |
emoji: 🤖
|
| 96 |
colorFrom: indigo
|
| 97 |
colorTo: purple
|
|
|
|
| 99 |
app_port: 7860
|
| 100 |
pinned: true
|
| 101 |
license: mit
|
| 102 |
+
short_description: Autonomous Engineering OS v8 — KeyPool Multi-API (Gemini + SambaNova)
|
| 103 |
---
|
| 104 |
|
| 105 |
+
# 🤖 God Agent OS v8
|
| 106 |
+
**KeyPool Multi-API Routing — Gemini + SambaNova Primary LLMs**
|
| 107 |
|
| 108 |
+
16-agent autonomous engineering OS with intelligent key pooling.
|
| 109 |
EOF
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
cd /tmp/hf-space
|
| 112 |
git add -A
|
| 113 |
+
git diff --cached --quiet || git commit -m "🚀 God Agent OS v8 deploy $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
| 114 |
git push origin main || git push origin master || true
|
| 115 |
+
|
| 116 |
echo "✅ Deployed to HF Space: https://huggingface.co/spaces/PYAE1994/autonomous-coding-system"
|
| 117 |
|
| 118 |
+
# ── Deploy to Vercel ─────────────────────────────────────────────────────
|
| 119 |
deploy-vercel:
|
| 120 |
name: ▲ Deploy to Vercel
|
| 121 |
runs-on: ubuntu-latest
|
.gitignore
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependencies
|
| 2 |
+
node_modules/
|
| 3 |
+
frontend/node_modules/
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.pyc
|
| 6 |
+
*.pyo
|
| 7 |
+
*.pyd
|
| 8 |
+
.Python
|
| 9 |
+
|
| 10 |
+
# Build outputs
|
| 11 |
+
frontend/.next/
|
| 12 |
+
frontend/out/
|
| 13 |
+
dist/
|
| 14 |
+
build/
|
| 15 |
+
*.egg-info/
|
| 16 |
+
|
| 17 |
+
# Environment
|
| 18 |
+
.env
|
| 19 |
+
.env.local
|
| 20 |
+
.env.production
|
| 21 |
+
*.env
|
| 22 |
+
|
| 23 |
+
# Logs
|
| 24 |
+
*.log
|
| 25 |
+
logs/
|
| 26 |
+
|
| 27 |
+
# Database
|
| 28 |
+
*.db
|
| 29 |
+
*.sqlite
|
| 30 |
+
|
| 31 |
+
# OS
|
| 32 |
+
.DS_Store
|
| 33 |
+
Thumbs.db
|
| 34 |
+
|
| 35 |
+
# IDE
|
| 36 |
+
.vscode/
|
| 37 |
+
.idea/
|
| 38 |
+
*.swp
|
| 39 |
+
|
| 40 |
+
# Temporary
|
| 41 |
+
tmp/
|
| 42 |
+
temp/
|
| 43 |
+
/tmp/
|
| 44 |
+
|
| 45 |
+
# Python cache
|
| 46 |
+
backend/__pycache__/
|
| 47 |
+
backend/**/__pycache__/
|
| 48 |
+
backend/ai_router/__pycache__/
|
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: God Agent OS
|
| 3 |
emoji: 🤖
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: purple
|
|
@@ -7,24 +7,39 @@ sdk: docker
|
|
| 7 |
app_port: 7860
|
| 8 |
pinned: true
|
| 9 |
license: mit
|
| 10 |
-
short_description: Autonomous Engineering OS —
|
| 11 |
---
|
| 12 |
|
| 13 |
-
# 🤖 GOD AGENT OS
|
| 14 |
**Autonomous Engineering Operating System**
|
| 15 |
-
*Manus + Genspark + Devin (OneHand) —
|
| 16 |
|
| 17 |
[](https://github.com/pyaesonegtckglay-dotcom/god-agent-os)
|
| 18 |
-
[.
|
| 59 |
|
| 60 |
## 🌐 API Documentation
|
| 61 |
|
| 62 |
- Interactive docs: `/api/docs`
|
| 63 |
- Health check: `/health`
|
|
|
|
|
|
|
| 64 |
|
| 65 |
## 📦 Architecture
|
| 66 |
|
| 67 |
```
|
| 68 |
god-agent-os/
|
| 69 |
-
├── backend/
|
| 70 |
-
│ ├─
|
| 71 |
-
│ ├── ai_router/
|
| 72 |
-
│ ├──
|
| 73 |
-
│ ├──
|
| 74 |
-
│
|
| 75 |
-
│
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
```
|
| 78 |
|
| 79 |
## 🔄 Auto-Deploy Pipeline
|
| 80 |
|
| 81 |
GitHub Push → Build Check → HF Space Deploy + Vercel Deploy
|
| 82 |
-
|
| 83 |
-
All automatic via GitHub Actions!
|
|
|
|
| 1 |
---
|
| 2 |
+
title: God Agent OS v8
|
| 3 |
emoji: 🤖
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: purple
|
|
|
|
| 7 |
app_port: 7860
|
| 8 |
pinned: true
|
| 9 |
license: mit
|
| 10 |
+
short_description: Autonomous Engineering OS v8 — KeyPool Multi-API (Gemini + SambaNova)
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# 🤖 GOD AGENT OS v8
|
| 14 |
**Autonomous Engineering Operating System**
|
| 15 |
+
*Manus + Genspark + Devin (OneHand) — KeyPool Multi-API Edition*
|
| 16 |
|
| 17 |
[](https://github.com/pyaesonegtckglay-dotcom/god-agent-os)
|
| 18 |
+
[](https://github.com/pyaesonegtckglay-dotcom/god-agent-os)
|
| 19 |
+
[](https://huggingface.co/spaces/PYAE1994/autonomous-coding-system)
|
| 20 |
|
| 21 |
+
## 🚀 What is God Agent OS v8?
|
| 22 |
|
| 23 |
+
God Agent OS v8 is a fully autonomous AI engineering platform with **KeyPool Multi-API Routing**:
|
| 24 |
+
- **Gemini** (6 keys) — Google Gemini 1.5 Flash primary LLM
|
| 25 |
+
- **SambaNova** (9 keys) — Meta Llama 3.3 70B primary LLM
|
| 26 |
+
- **GitHub API** (9 keys) — Pooled Git operations
|
| 27 |
+
- **Automatic failover** across 7 providers
|
| 28 |
|
| 29 |
+
## 🔑 v8 KeyPool System
|
| 30 |
+
|
| 31 |
+
```
|
| 32 |
+
Priority Chain:
|
| 33 |
+
SambaNova (9 keys) → Gemini (6 keys) → OpenAI → Groq → Cerebras → OpenRouter → Anthropic
|
| 34 |
+
|
| 35 |
+
Each key pool:
|
| 36 |
+
- Round-robin key selection
|
| 37 |
+
- Failure tracking per key
|
| 38 |
+
- Automatic cooldown (60s after 3 failures)
|
| 39 |
+
- Real-time status dashboard in UI
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
## 🤖 16-Agent Fleet
|
| 43 |
|
| 44 |
| Agent | Capability | Status |
|
| 45 |
|-------|-----------|--------|
|
|
|
|
| 47 |
| 📋 Planner | Task decomposition & planning | Core |
|
| 48 |
| 💻 Coding | Production code generation | Core |
|
| 49 |
| 🐛 Debug | Self-healing error resolution | Core |
|
| 50 |
+
| 🌐 Browser | Web research & scraping | v7 |
|
| 51 |
+
| 📁 File | File system & project scaffold | v7 |
|
| 52 |
+
| 🔀 Git | Git ops & GitHub PR creation | v7 |
|
| 53 |
+
| 🧪 Test | Auto test generation & execution | v7 |
|
| 54 |
+
| 🎨 Vision | Design-to-code UI generation | v7 |
|
| 55 |
| 🖥️ Sandbox | Isolated code execution | Core |
|
| 56 |
| 🚀 Deploy | Auto-deploy to cloud | Core |
|
| 57 |
| 🔌 Connector | External integrations | Core |
|
| 58 |
| 🧠 Memory | Long-term context | Core |
|
| 59 |
| ⚙️ Workflow | n8n automation | Core |
|
| 60 |
+
| 🔍 Reasoning | Deep reasoning & analysis | Core |
|
| 61 |
+
| 🎨 UI | Real-time UI state | Core |
|
| 62 |
|
| 63 |
+
## 🔑 API Keys Configuration
|
| 64 |
|
| 65 |
+
Set these in Hugging Face Space → Settings → Variables:
|
| 66 |
|
| 67 |
+
| Variable | Description | Keys |
|
| 68 |
+
|----------|-------------|------|
|
| 69 |
+
| `GEMINI_API_KEYS` | Google Gemini (comma-separated) | 6 keys |
|
| 70 |
+
| `SAMBANOVA_API_KEYS` | SambaNova (comma-separated) | 9 keys |
|
| 71 |
+
| `GITHUB_API_KEYS` | GitHub API (comma-separated) | 9 keys |
|
| 72 |
| `OPENAI_API_KEY` | GPT-4o | Optional |
|
| 73 |
+
| `GROQ_API_KEY` | Llama 3.3 70B (Free) | Optional |
|
|
|
|
| 74 |
| `ANTHROPIC_API_KEY` | Claude 3.5 | Optional |
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
## 🌐 API Documentation
|
| 77 |
|
| 78 |
- Interactive docs: `/api/docs`
|
| 79 |
- Health check: `/health`
|
| 80 |
+
- AI Router stats: `/api/v1/ai/stats`
|
| 81 |
+
- Key pool status: `/api/v1/ai/pool-status`
|
| 82 |
|
| 83 |
## 📦 Architecture
|
| 84 |
|
| 85 |
```
|
| 86 |
god-agent-os/
|
| 87 |
+
├── backend/
|
| 88 |
+
│ ├─��� agents/ # 16 specialized agents
|
| 89 |
+
│ ├── ai_router/
|
| 90 |
+
│ │ ├── key_pool.py # KeyPool multi-key manager (NEW v8)
|
| 91 |
+
│ │ ├── router_v8.py # AIRouterV8 with KeyPool (NEW v8)
|
| 92 |
+
│ │ └── router.py # Legacy router (retained)
|
| 93 |
+
│ ├── api/ # REST + WebSocket endpoints
|
| 94 |
+
│ ├── core/ # Task engine & models
|
| 95 |
+
│ ├── memory/ # SQLite persistent memory
|
| 96 |
+
│ ├── connectors/ # External service connectors
|
| 97 |
+
│ ├── main_v8.py # v8 Entry point (NEW)
|
| 98 |
+
│ └── Dockerfile.hf # HF Spaces Docker
|
| 99 |
+
└── frontend/ # Next.js 14 UI
|
| 100 |
+
└── components/
|
| 101 |
+
└── layout/
|
| 102 |
+
└── AIRouterPanel.tsx # v8 Key pool status UI (NEW)
|
| 103 |
```
|
| 104 |
|
| 105 |
## 🔄 Auto-Deploy Pipeline
|
| 106 |
|
| 107 |
GitHub Push → Build Check → HF Space Deploy + Vercel Deploy
|
|
|
|
|
|
backend/Dockerfile.hf
CHANGED
|
@@ -22,6 +22,16 @@ ENV PORT=7860
|
|
| 22 |
ENV WORKSPACE_DIR=/tmp/god_workspace
|
| 23 |
ENV PYTHONPATH=/app
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
EXPOSE 7860
|
| 26 |
|
| 27 |
-
CMD ["uvicorn", "
|
|
|
|
| 22 |
ENV WORKSPACE_DIR=/tmp/god_workspace
|
| 23 |
ENV PYTHONPATH=/app
|
| 24 |
|
| 25 |
+
# API Keys are configured via HF Space Secrets (Settings → Variables and Secrets)
|
| 26 |
+
# Set these in your HF Space:
|
| 27 |
+
# GEMINI_API_KEYS — comma-separated Gemini API keys
|
| 28 |
+
# SAMBANOVA_API_KEYS — comma-separated SambaNova API keys
|
| 29 |
+
# GITHUB_API_KEYS — comma-separated GitHub tokens
|
| 30 |
+
# GITHUB_TOKEN — primary GitHub token
|
| 31 |
+
# OPENAI_API_KEY — OpenAI API key (optional)
|
| 32 |
+
# GROQ_API_KEY — Groq API key (optional, free)
|
| 33 |
+
# ANTHROPIC_API_KEY — Anthropic API key (optional)
|
| 34 |
+
|
| 35 |
EXPOSE 7860
|
| 36 |
|
| 37 |
+
CMD ["uvicorn", "main_v8:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
|
backend/__pycache__/main.cpython-312.pyc
DELETED
|
Binary file (9.42 kB)
|
|
|
backend/agents/debug_agent.py
CHANGED
|
@@ -44,12 +44,12 @@ class DebugAgent(BaseAgent):
|
|
| 44 |
messages = [
|
| 45 |
{"role": "system", "content": DEBUG_SYSTEM},
|
| 46 |
{"role": "user", "content": (
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
)},
|
| 54 |
]
|
| 55 |
|
|
@@ -71,14 +71,19 @@ class DebugAgent(BaseAgent):
|
|
| 71 |
|
| 72 |
async def analyze_error(self, error_output: str, source_code: str = "", task_id: str = "", session_id: str = "") -> Dict:
|
| 73 |
"""Deep error analysis with structured output."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
messages = [
|
| 75 |
{"role": "system", "content": DEBUG_SYSTEM},
|
| 76 |
{"role": "user", "content": (
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
)},
|
| 83 |
]
|
| 84 |
raw = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1, max_tokens=1000)
|
|
@@ -111,10 +116,10 @@ class DebugAgent(BaseAgent):
|
|
| 111 |
messages = [
|
| 112 |
{"role": "system", "content": DEBUG_SYSTEM},
|
| 113 |
{"role": "user", "content": (
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
)},
|
| 119 |
]
|
| 120 |
|
|
@@ -143,7 +148,7 @@ class DebugAgent(BaseAgent):
|
|
| 143 |
compile(code, "<string>", "exec")
|
| 144 |
return {"valid": True, "error": ""}
|
| 145 |
except SyntaxError as e:
|
| 146 |
-
return {"valid": False, "error":
|
| 147 |
except Exception as e:
|
| 148 |
return {"valid": False, "error": str(e)}
|
| 149 |
|
|
|
|
| 44 |
messages = [
|
| 45 |
{"role": "system", "content": DEBUG_SYSTEM},
|
| 46 |
{"role": "user", "content": (
|
| 47 |
+
"Debug and fix this issue (attempt " + str(attempt) + "):\n\n"
|
| 48 |
+
+ task + "\n\n"
|
| 49 |
+
"Provide:\n"
|
| 50 |
+
"1. Root cause analysis\n"
|
| 51 |
+
"2. Exact fix (code/config)\n"
|
| 52 |
+
"3. Prevention strategy"
|
| 53 |
)},
|
| 54 |
]
|
| 55 |
|
|
|
|
| 71 |
|
| 72 |
async def analyze_error(self, error_output: str, source_code: str = "", task_id: str = "", session_id: str = "") -> Dict:
|
| 73 |
"""Deep error analysis with structured output."""
|
| 74 |
+
if source_code:
|
| 75 |
+
source_section = "Source Code:\n```\n" + source_code[:1000] + "\n```"
|
| 76 |
+
else:
|
| 77 |
+
source_section = ""
|
| 78 |
+
|
| 79 |
messages = [
|
| 80 |
{"role": "system", "content": DEBUG_SYSTEM},
|
| 81 |
{"role": "user", "content": (
|
| 82 |
+
"Analyze this error and provide structured diagnosis:\n\n"
|
| 83 |
+
"Error:\n" + error_output[:2000] + "\n\n"
|
| 84 |
+
+ source_section + "\n\n"
|
| 85 |
+
"Respond with JSON:\n"
|
| 86 |
+
'{"error_type": "...", "root_cause": "...", "fix": "...", "prevention": "...", "severity": "low|medium|high|critical"}'
|
| 87 |
)},
|
| 88 |
]
|
| 89 |
raw = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.1, max_tokens=1000)
|
|
|
|
| 116 |
messages = [
|
| 117 |
{"role": "system", "content": DEBUG_SYSTEM},
|
| 118 |
{"role": "user", "content": (
|
| 119 |
+
"Fix attempt " + str(attempt) + "/" + str(max_retries) + ":\n\n"
|
| 120 |
+
"Error: " + current_error + "\n\n"
|
| 121 |
+
"Code:\n```\n" + current_code[:3000] + "\n```\n\n"
|
| 122 |
+
"Return ONLY the fixed code."
|
| 123 |
)},
|
| 124 |
]
|
| 125 |
|
|
|
|
| 148 |
compile(code, "<string>", "exec")
|
| 149 |
return {"valid": True, "error": ""}
|
| 150 |
except SyntaxError as e:
|
| 151 |
+
return {"valid": False, "error": "SyntaxError: " + str(e)}
|
| 152 |
except Exception as e:
|
| 153 |
return {"valid": False, "error": str(e)}
|
| 154 |
|
backend/agents/reasoning_agent.py
CHANGED
|
@@ -1,24 +1,34 @@
|
|
| 1 |
"""
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
Version: 3.0.0
|
| 5 |
"""
|
| 6 |
|
| 7 |
import asyncio
|
| 8 |
import json
|
| 9 |
-
from typing import Dict, Any, Optional
|
| 10 |
|
| 11 |
import structlog
|
| 12 |
|
| 13 |
-
from
|
| 14 |
|
| 15 |
log = structlog.get_logger()
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
class ReasoningAgent(BaseAgent):
|
| 19 |
"""
|
| 20 |
Specialized agent for complex reasoning, analysis, and problem-solving tasks.
|
| 21 |
-
|
| 22 |
Capabilities:
|
| 23 |
- Multi-step reasoning with chain-of-thought
|
| 24 |
- Complex problem decomposition
|
|
@@ -27,259 +37,127 @@ class ReasoningAgent(BaseAgent):
|
|
| 27 |
- Strategic planning
|
| 28 |
"""
|
| 29 |
|
| 30 |
-
def __init__(self, ws_manager, ai_router):
|
| 31 |
-
""
|
| 32 |
-
|
| 33 |
-
name="ReasoningAgent",
|
| 34 |
-
color="🟦",
|
| 35 |
-
description="Complex reasoning and analysis",
|
| 36 |
-
ws_manager=ws_manager,
|
| 37 |
-
ai_router=ai_router,
|
| 38 |
-
)
|
| 39 |
-
self.reasoning_depth = 3 # Number of reasoning steps
|
| 40 |
self.max_reasoning_tokens = 16000
|
| 41 |
|
| 42 |
-
async def
|
| 43 |
-
"""
|
| 44 |
-
|
| 45 |
-
"""
|
| 46 |
-
user_message = task.get("content", "")
|
| 47 |
-
session_id = task.get("session_id", "")
|
| 48 |
-
context = task.get("context", {})
|
| 49 |
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
try:
|
| 53 |
# Step 1: Analyze the problem
|
| 54 |
-
analysis = await self._analyze_problem(
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
# Step 3: Solve each sub-problem
|
| 70 |
-
solutions = []
|
| 71 |
-
for i, sub_problem in enumerate(sub_problems):
|
| 72 |
-
solution = await self._solve_sub_problem(sub_problem, context)
|
| 73 |
-
solutions.append(solution)
|
| 74 |
-
await self._broadcast(session_id, {
|
| 75 |
-
"type": "reasoning_step",
|
| 76 |
-
"step": f"solution_{i+1}",
|
| 77 |
-
"data": solution,
|
| 78 |
-
})
|
| 79 |
-
|
| 80 |
-
# Step 4: Synthesize final answer
|
| 81 |
-
final_answer = await self._synthesize_answer(
|
| 82 |
-
user_message,
|
| 83 |
-
analysis,
|
| 84 |
-
sub_problems,
|
| 85 |
-
solutions
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
await self._broadcast(session_id, {
|
| 89 |
-
"type": "reasoning_complete",
|
| 90 |
-
"answer": final_answer,
|
| 91 |
"reasoning_depth": self.reasoning_depth,
|
| 92 |
-
})
|
| 93 |
|
| 94 |
-
return
|
| 95 |
-
"success": True,
|
| 96 |
-
"agent": self.name,
|
| 97 |
-
"answer": final_answer,
|
| 98 |
-
"reasoning_steps": {
|
| 99 |
-
"analysis": analysis,
|
| 100 |
-
"sub_problems": sub_problems,
|
| 101 |
-
"solutions": solutions,
|
| 102 |
-
},
|
| 103 |
-
}
|
| 104 |
|
| 105 |
except Exception as e:
|
| 106 |
-
log.error("
|
| 107 |
-
return
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
context={"task_type": "reasoning"},
|
| 130 |
-
optimize_for="quality"
|
| 131 |
-
)
|
| 132 |
-
|
| 133 |
-
return {
|
| 134 |
-
"problem_type": self._classify_problem(problem),
|
| 135 |
-
"complexity": self._estimate_complexity(problem),
|
| 136 |
-
"analysis": response.get("response", ""),
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
async def _decompose_problem(
|
| 140 |
-
self,
|
| 141 |
-
problem: str,
|
| 142 |
-
analysis: Dict[str, Any]
|
| 143 |
-
) -> list:
|
| 144 |
-
"""
|
| 145 |
-
Break down complex problem into manageable sub-problems.
|
| 146 |
-
"""
|
| 147 |
-
prompt = f"""Based on this analysis, break down the problem into 3-5 specific sub-problems:
|
| 148 |
-
|
| 149 |
-
Problem: {problem}
|
| 150 |
-
Analysis: {json.dumps(analysis, indent=2)}
|
| 151 |
-
|
| 152 |
-
List each sub-problem clearly and explain the dependencies."""
|
| 153 |
-
|
| 154 |
-
response = await self.ai_router.route(
|
| 155 |
-
prompt,
|
| 156 |
-
context={"task_type": "reasoning"},
|
| 157 |
-
optimize_for="quality"
|
| 158 |
)
|
| 159 |
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
response = await self.ai_router.route(
|
| 182 |
-
prompt,
|
| 183 |
-
context={"task_type": "reasoning"},
|
| 184 |
-
optimize_for="quality"
|
| 185 |
-
)
|
| 186 |
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
"
|
| 191 |
-
|
|
|
|
|
|
|
| 192 |
|
| 193 |
async def _synthesize_answer(
|
| 194 |
self,
|
| 195 |
-
|
| 196 |
-
analysis:
|
| 197 |
-
sub_problems:
|
| 198 |
-
solutions:
|
|
|
|
|
|
|
| 199 |
) -> str:
|
| 200 |
-
"""
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
Original Problem: {original_problem}
|
| 206 |
-
|
| 207 |
-
Analysis: {json.dumps(analysis, indent=2)}
|
| 208 |
-
|
| 209 |
-
Solutions:
|
| 210 |
-
{json.dumps(solutions, indent=2)}
|
| 211 |
-
|
| 212 |
-
Provide a clear, well-reasoned final answer that:
|
| 213 |
-
1. Directly addresses the original problem
|
| 214 |
-
2. Integrates insights from all sub-problems
|
| 215 |
-
3. Explains the reasoning clearly
|
| 216 |
-
4. Suggests any follow-up actions if needed"""
|
| 217 |
-
|
| 218 |
-
response = await self.ai_router.route(
|
| 219 |
-
synthesis_prompt,
|
| 220 |
-
context={"task_type": "reasoning"},
|
| 221 |
-
optimize_for="quality"
|
| 222 |
)
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
return "logical"
|
| 234 |
-
elif any(word in problem_lower for word in ["plan", "strategy", "approach"]):
|
| 235 |
-
return "strategic"
|
| 236 |
-
elif any(word in problem_lower for word in ["analyze", "compare", "evaluate"]):
|
| 237 |
-
return "analytical"
|
| 238 |
-
else:
|
| 239 |
-
return "general"
|
| 240 |
-
|
| 241 |
-
def _estimate_complexity(self, problem: str) -> str:
|
| 242 |
-
"""Estimate problem complexity."""
|
| 243 |
-
word_count = len(problem.split())
|
| 244 |
-
|
| 245 |
-
if word_count < 20:
|
| 246 |
-
return "simple"
|
| 247 |
-
elif word_count < 100:
|
| 248 |
-
return "moderate"
|
| 249 |
-
else:
|
| 250 |
-
return "complex"
|
| 251 |
-
|
| 252 |
-
def _parse_sub_problems(self, response: str) -> list:
|
| 253 |
-
"""Parse sub-problems from model response."""
|
| 254 |
-
# Simple parsing - can be enhanced
|
| 255 |
-
lines = response.split("\n")
|
| 256 |
-
sub_problems = []
|
| 257 |
-
|
| 258 |
-
for line in lines:
|
| 259 |
-
line = line.strip()
|
| 260 |
-
if line and any(line.startswith(f"{i}.") for i in range(1, 10)):
|
| 261 |
-
sub_problems.append(line)
|
| 262 |
-
|
| 263 |
-
return sub_problems if sub_problems else [response]
|
| 264 |
-
|
| 265 |
-
async def _broadcast(self, session_id: str, data: Dict[str, Any]):
|
| 266 |
-
"""Broadcast reasoning progress to client."""
|
| 267 |
-
if self.ws_manager:
|
| 268 |
-
await self.ws_manager.broadcast(
|
| 269 |
-
room=f"chat:{session_id}",
|
| 270 |
-
message={
|
| 271 |
-
"type": "agent_message",
|
| 272 |
-
"agent": self.name,
|
| 273 |
-
"color": self.color,
|
| 274 |
-
**data,
|
| 275 |
-
}
|
| 276 |
-
)
|
| 277 |
|
| 278 |
def get_status(self) -> Dict[str, Any]:
|
| 279 |
"""Get agent status."""
|
| 280 |
return {
|
| 281 |
"name": self.name,
|
| 282 |
-
"color": self.color,
|
| 283 |
"status": "ready",
|
| 284 |
"capabilities": [
|
| 285 |
"Multi-step reasoning",
|
|
@@ -289,7 +167,6 @@ Provide a clear, well-reasoned final answer that:
|
|
| 289 |
"Strategic planning",
|
| 290 |
],
|
| 291 |
"reasoning_depth": self.reasoning_depth,
|
| 292 |
-
"max_reasoning_tokens": self.max_reasoning_tokens,
|
| 293 |
}
|
| 294 |
|
| 295 |
|
|
|
|
| 1 |
"""
|
| 2 |
+
ReasoningAgent v7 — Complex reasoning, analysis, and multi-step problem solving
|
| 3 |
+
GOD AGENT OS — Using DeepSeek R1, Qwen QwQ, o1-mini style reasoning
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import asyncio
|
| 7 |
import json
|
| 8 |
+
from typing import Dict, Any, List, Optional
|
| 9 |
|
| 10 |
import structlog
|
| 11 |
|
| 12 |
+
from .base_agent import BaseAgent
|
| 13 |
|
| 14 |
log = structlog.get_logger()
|
| 15 |
|
| 16 |
+
REASONING_SYSTEM = """You are an elite autonomous reasoning and analysis agent.
|
| 17 |
+
You excel at:
|
| 18 |
+
- Multi-step reasoning with chain-of-thought
|
| 19 |
+
- Complex problem decomposition and analysis
|
| 20 |
+
- Mathematical and logical reasoning
|
| 21 |
+
- Strategic planning and decision making
|
| 22 |
+
- Root cause analysis
|
| 23 |
+
|
| 24 |
+
Always think step by step, show your reasoning, and provide confident conclusions.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
|
| 28 |
class ReasoningAgent(BaseAgent):
|
| 29 |
"""
|
| 30 |
Specialized agent for complex reasoning, analysis, and problem-solving tasks.
|
| 31 |
+
|
| 32 |
Capabilities:
|
| 33 |
- Multi-step reasoning with chain-of-thought
|
| 34 |
- Complex problem decomposition
|
|
|
|
| 37 |
- Strategic planning
|
| 38 |
"""
|
| 39 |
|
| 40 |
+
def __init__(self, ws_manager=None, ai_router=None):
|
| 41 |
+
super().__init__("ReasoningAgent", ws_manager, ai_router)
|
| 42 |
+
self.reasoning_depth = 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
self.max_reasoning_tokens = 16000
|
| 44 |
|
| 45 |
+
async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
|
| 46 |
+
"""Execute reasoning task."""
|
| 47 |
+
session_id = kwargs.get("session_id", "")
|
| 48 |
+
task_id = kwargs.get("task_id", "")
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
await self.emit(task_id, "agent_start", {
|
| 51 |
+
"agent": "ReasoningAgent",
|
| 52 |
+
"task": task[:80],
|
| 53 |
+
}, session_id)
|
| 54 |
|
| 55 |
try:
|
| 56 |
# Step 1: Analyze the problem
|
| 57 |
+
analysis = await self._analyze_problem(task, context, task_id, session_id)
|
| 58 |
+
|
| 59 |
+
# Step 2: Decompose if complex
|
| 60 |
+
if len(task.split()) > 30:
|
| 61 |
+
sub_problems = await self._decompose_problem(task, analysis, task_id, session_id)
|
| 62 |
+
solutions = []
|
| 63 |
+
for sub in sub_problems[:3]:
|
| 64 |
+
sol = await self._solve_sub_problem(sub, context, task_id, session_id)
|
| 65 |
+
solutions.append(sol)
|
| 66 |
+
result = await self._synthesize_answer(task, analysis, sub_problems, solutions, task_id, session_id)
|
| 67 |
+
else:
|
| 68 |
+
result = analysis
|
| 69 |
+
|
| 70 |
+
await self.emit(task_id, "reasoning_complete", {
|
| 71 |
+
"agent": "ReasoningAgent",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
"reasoning_depth": self.reasoning_depth,
|
| 73 |
+
}, session_id)
|
| 74 |
|
| 75 |
+
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
except Exception as e:
|
| 78 |
+
log.error("ReasoningAgent failed", error=str(e))
|
| 79 |
+
return "Reasoning agent encountered an error: " + str(e)
|
| 80 |
+
|
| 81 |
+
async def _analyze_problem(self, problem: str, context: Dict, task_id: str, session_id: str) -> str:
|
| 82 |
+
"""Analyze the problem using reasoning model."""
|
| 83 |
+
messages = [
|
| 84 |
+
{"role": "system", "content": REASONING_SYSTEM},
|
| 85 |
+
{"role": "user", "content": (
|
| 86 |
+
"Analyze this problem step by step:\n\n"
|
| 87 |
+
+ problem + "\n\n"
|
| 88 |
+
"Provide:\n"
|
| 89 |
+
"1. Problem type and complexity\n"
|
| 90 |
+
"2. Key constraints and requirements\n"
|
| 91 |
+
"3. Step-by-step solution\n"
|
| 92 |
+
"4. Final answer/recommendation"
|
| 93 |
+
)},
|
| 94 |
+
]
|
| 95 |
+
return await self.llm(
|
| 96 |
+
messages,
|
| 97 |
+
task_id=task_id,
|
| 98 |
+
session_id=session_id,
|
| 99 |
+
temperature=0.2,
|
| 100 |
+
max_tokens=self.max_reasoning_tokens,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
)
|
| 102 |
|
| 103 |
+
async def _decompose_problem(self, problem: str, analysis: str, task_id: str, session_id: str) -> List[str]:
|
| 104 |
+
"""Break down complex problem into sub-problems."""
|
| 105 |
+
messages = [
|
| 106 |
+
{"role": "system", "content": REASONING_SYSTEM},
|
| 107 |
+
{"role": "user", "content": (
|
| 108 |
+
"Break this complex problem into 3 specific sub-problems:\n\n"
|
| 109 |
+
+ problem + "\n\n"
|
| 110 |
+
"Initial analysis: " + analysis[:500] + "\n\n"
|
| 111 |
+
"List each sub-problem on a new line starting with '1.', '2.', '3.'"
|
| 112 |
+
)},
|
| 113 |
+
]
|
| 114 |
+
raw = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=2000)
|
| 115 |
+
# Parse numbered sub-problems
|
| 116 |
+
lines = raw.split("\n")
|
| 117 |
+
sub_problems = []
|
| 118 |
+
for line in lines:
|
| 119 |
+
line = line.strip()
|
| 120 |
+
if line and any(line.startswith(str(i) + ".") for i in range(1, 10)):
|
| 121 |
+
sub_problems.append(line)
|
| 122 |
+
return sub_problems if sub_problems else [problem]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
async def _solve_sub_problem(self, sub_problem: str, context: Dict, task_id: str, session_id: str) -> str:
|
| 125 |
+
"""Solve individual sub-problem."""
|
| 126 |
+
messages = [
|
| 127 |
+
{"role": "system", "content": REASONING_SYSTEM},
|
| 128 |
+
{"role": "user", "content": "Solve this specific problem:\n\n" + sub_problem},
|
| 129 |
+
]
|
| 130 |
+
return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=4096)
|
| 131 |
|
| 132 |
async def _synthesize_answer(
|
| 133 |
self,
|
| 134 |
+
original: str,
|
| 135 |
+
analysis: str,
|
| 136 |
+
sub_problems: List[str],
|
| 137 |
+
solutions: List[str],
|
| 138 |
+
task_id: str,
|
| 139 |
+
session_id: str,
|
| 140 |
) -> str:
|
| 141 |
+
"""Synthesize final answer from all reasoning steps."""
|
| 142 |
+
solutions_text = "\n".join(
|
| 143 |
+
"Sub-problem " + str(i + 1) + ": " + sol[:300]
|
| 144 |
+
for i, sol in enumerate(solutions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
)
|
| 146 |
+
messages = [
|
| 147 |
+
{"role": "system", "content": REASONING_SYSTEM},
|
| 148 |
+
{"role": "user", "content": (
|
| 149 |
+
"Original problem: " + original + "\n\n"
|
| 150 |
+
"Analysis: " + analysis[:800] + "\n\n"
|
| 151 |
+
"Solutions to sub-problems:\n" + solutions_text + "\n\n"
|
| 152 |
+
"Provide a comprehensive final answer that integrates all insights."
|
| 153 |
+
)},
|
| 154 |
+
]
|
| 155 |
+
return await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.3, max_tokens=8192)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
def get_status(self) -> Dict[str, Any]:
|
| 158 |
"""Get agent status."""
|
| 159 |
return {
|
| 160 |
"name": self.name,
|
|
|
|
| 161 |
"status": "ready",
|
| 162 |
"capabilities": [
|
| 163 |
"Multi-step reasoning",
|
|
|
|
| 167 |
"Strategic planning",
|
| 168 |
],
|
| 169 |
"reasoning_depth": self.reasoning_depth,
|
|
|
|
| 170 |
}
|
| 171 |
|
| 172 |
|
backend/ai_router/__init__.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
| 1 |
-
# Multi-Model AI Router
|
| 2 |
from .router import AIRouter
|
|
|
|
|
|
|
| 3 |
|
| 4 |
-
__all__ = ["AIRouter"]
|
|
|
|
| 1 |
+
# Multi-Model AI Router — God Agent OS v8
|
| 2 |
from .router import AIRouter
|
| 3 |
+
from .router_v8 import AIRouterV8
|
| 4 |
+
from .key_pool import KeyPool, KeyPoolRegistry
|
| 5 |
|
| 6 |
+
__all__ = ["AIRouter", "AIRouterV8", "KeyPool", "KeyPoolRegistry"]
|
backend/ai_router/key_pool.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
KeyPool — Multi-API Key Manager with Failover & Cooldown
|
| 3 |
+
God Agent OS v8 — Supports Gemini, SambaNova, GitHub, OpenAI, Groq, etc.
|
| 4 |
+
Each provider can have multiple comma-separated keys.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import asyncio
|
| 8 |
+
import time
|
| 9 |
+
from collections import defaultdict
|
| 10 |
+
from typing import Dict, List, Optional
|
| 11 |
+
import structlog
|
| 12 |
+
|
| 13 |
+
log = structlog.get_logger()
|
| 14 |
+
|
| 15 |
+
COOLDOWN_SECONDS = 60 # Key cooling time after max failures
|
| 16 |
+
MAX_FAILURES = 3 # Max fails before cooldown
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class KeyEntry:
|
| 20 |
+
def __init__(self, key: str):
|
| 21 |
+
self.key = key
|
| 22 |
+
self.failures = 0
|
| 23 |
+
self.cooldown_until = 0.0
|
| 24 |
+
self.calls = 0
|
| 25 |
+
self.last_used = 0.0
|
| 26 |
+
|
| 27 |
+
def is_available(self) -> bool:
|
| 28 |
+
if self.cooldown_until > time.time():
|
| 29 |
+
return False
|
| 30 |
+
return True
|
| 31 |
+
|
| 32 |
+
def mark_fail(self):
|
| 33 |
+
self.failures += 1
|
| 34 |
+
if self.failures >= MAX_FAILURES:
|
| 35 |
+
self.cooldown_until = time.time() + COOLDOWN_SECONDS
|
| 36 |
+
log.warning("Key cooldown activated", failures=self.failures)
|
| 37 |
+
|
| 38 |
+
def mark_success(self):
|
| 39 |
+
self.failures = max(0, self.failures - 1)
|
| 40 |
+
self.cooldown_until = 0.0
|
| 41 |
+
self.calls += 1
|
| 42 |
+
self.last_used = time.time()
|
| 43 |
+
|
| 44 |
+
def status(self) -> dict:
|
| 45 |
+
remaining = max(0, self.cooldown_until - time.time())
|
| 46 |
+
return {
|
| 47 |
+
"key_preview": self.key[:8] + "..." + self.key[-4:] if len(self.key) > 12 else "***",
|
| 48 |
+
"available": self.is_available(),
|
| 49 |
+
"failures": self.failures,
|
| 50 |
+
"calls": self.calls,
|
| 51 |
+
"cooldown_remaining_s": round(remaining, 1),
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class KeyPool:
|
| 56 |
+
"""
|
| 57 |
+
Pool of API keys for a single provider.
|
| 58 |
+
Round-robins through available keys with failure tracking.
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
def __init__(self, provider: str, keys: List[str]):
|
| 62 |
+
self.provider = provider
|
| 63 |
+
self._keys: List[KeyEntry] = [KeyEntry(k.strip()) for k in keys if k.strip()]
|
| 64 |
+
self._index = 0
|
| 65 |
+
|
| 66 |
+
def __len__(self) -> int:
|
| 67 |
+
return len(self._keys)
|
| 68 |
+
|
| 69 |
+
def pick(self) -> Optional[str]:
|
| 70 |
+
"""Pick the next available key (round-robin, skip cooling down keys)."""
|
| 71 |
+
if not self._keys:
|
| 72 |
+
return None
|
| 73 |
+
n = len(self._keys)
|
| 74 |
+
for _ in range(n):
|
| 75 |
+
entry = self._keys[self._index % n]
|
| 76 |
+
self._index = (self._index + 1) % n
|
| 77 |
+
if entry.is_available():
|
| 78 |
+
return entry.key
|
| 79 |
+
# All keys in cooldown — try the one with shortest cooldown
|
| 80 |
+
soonest = min(self._keys, key=lambda e: e.cooldown_until)
|
| 81 |
+
log.warning(
|
| 82 |
+
"All keys in cooldown, using soonest",
|
| 83 |
+
provider=self.provider,
|
| 84 |
+
cooldown_remaining=round(soonest.cooldown_until - time.time(), 1),
|
| 85 |
+
)
|
| 86 |
+
return soonest.key
|
| 87 |
+
|
| 88 |
+
def mark_fail(self, key: str):
|
| 89 |
+
for e in self._keys:
|
| 90 |
+
if e.key == key:
|
| 91 |
+
e.mark_fail()
|
| 92 |
+
return
|
| 93 |
+
|
| 94 |
+
def mark_success(self, key: str):
|
| 95 |
+
for e in self._keys:
|
| 96 |
+
if e.key == key:
|
| 97 |
+
e.mark_success()
|
| 98 |
+
return
|
| 99 |
+
|
| 100 |
+
def available_count(self) -> int:
|
| 101 |
+
return sum(1 for e in self._keys if e.is_available())
|
| 102 |
+
|
| 103 |
+
def status(self) -> dict:
|
| 104 |
+
return {
|
| 105 |
+
"provider": self.provider,
|
| 106 |
+
"total_keys": len(self._keys),
|
| 107 |
+
"available_keys": self.available_count(),
|
| 108 |
+
"keys": [e.status() for e in self._keys],
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
# ─── Global Key Pool Registry ─────────────────────────────────────────────────
|
| 113 |
+
|
| 114 |
+
class KeyPoolRegistry:
|
| 115 |
+
"""Central registry for all provider key pools."""
|
| 116 |
+
|
| 117 |
+
def __init__(self):
|
| 118 |
+
self._pools: Dict[str, KeyPool] = {}
|
| 119 |
+
|
| 120 |
+
def register(self, provider: str, keys_csv: str):
|
| 121 |
+
"""Register comma-separated keys for a provider."""
|
| 122 |
+
keys = [k.strip() for k in keys_csv.split(",") if k.strip()]
|
| 123 |
+
if keys:
|
| 124 |
+
self._pools[provider] = KeyPool(provider, keys)
|
| 125 |
+
log.info("KeyPool registered", provider=provider, count=len(keys))
|
| 126 |
+
|
| 127 |
+
def get(self, provider: str) -> Optional[KeyPool]:
|
| 128 |
+
return self._pools.get(provider)
|
| 129 |
+
|
| 130 |
+
def all_status(self) -> dict:
|
| 131 |
+
return {name: pool.status() for name, pool in self._pools.items()}
|
backend/ai_router/router_v8.py
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
AIRouter v8 — God Agent OS
|
| 3 |
+
Multi-provider AI router with KeyPool failover.
|
| 4 |
+
Supports: Gemini, SambaNova, OpenAI, Groq, Cerebras, OpenRouter, Anthropic
|
| 5 |
+
Primary LLMs: Gemini (6 keys) + SambaNova (9 keys) — fully pooled
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import asyncio
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import time
|
| 12 |
+
from typing import Any, Dict, List, Optional
|
| 13 |
+
|
| 14 |
+
import httpx
|
| 15 |
+
import structlog
|
| 16 |
+
|
| 17 |
+
from ai_router.key_pool import KeyPoolRegistry
|
| 18 |
+
|
| 19 |
+
log = structlog.get_logger()
|
| 20 |
+
|
| 21 |
+
# ─── Provider Definitions ─────────────────────────────────────────────────────
|
| 22 |
+
|
| 23 |
+
PROVIDER_CONFIG = [
|
| 24 |
+
# Priority 1 — SambaNova (fast, free tier)
|
| 25 |
+
{
|
| 26 |
+
"name": "sambanova",
|
| 27 |
+
"key_env": "SAMBANOVA_API_KEYS",
|
| 28 |
+
"base_url": "https://api.sambanova.ai/v1",
|
| 29 |
+
"default_model": "Meta-Llama-3.3-70B-Instruct",
|
| 30 |
+
"type": "openai_compat",
|
| 31 |
+
"max_tokens": 4096,
|
| 32 |
+
"priority": 1,
|
| 33 |
+
},
|
| 34 |
+
# Priority 2 — Gemini (Google AI)
|
| 35 |
+
{
|
| 36 |
+
"name": "gemini",
|
| 37 |
+
"key_env": "GEMINI_API_KEYS",
|
| 38 |
+
"base_url": "https://generativelanguage.googleapis.com",
|
| 39 |
+
"default_model": "gemini-1.5-flash",
|
| 40 |
+
"type": "gemini",
|
| 41 |
+
"max_tokens": 8192,
|
| 42 |
+
"priority": 2,
|
| 43 |
+
},
|
| 44 |
+
# Priority 3 — OpenAI
|
| 45 |
+
{
|
| 46 |
+
"name": "openai",
|
| 47 |
+
"key_env": "OPENAI_API_KEY",
|
| 48 |
+
"base_url": os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"),
|
| 49 |
+
"default_model": os.environ.get("DEFAULT_MODEL", "gpt-4o"),
|
| 50 |
+
"type": "openai_compat",
|
| 51 |
+
"max_tokens": 4096,
|
| 52 |
+
"priority": 3,
|
| 53 |
+
},
|
| 54 |
+
# Priority 4 — Groq
|
| 55 |
+
{
|
| 56 |
+
"name": "groq",
|
| 57 |
+
"key_env": "GROQ_API_KEY",
|
| 58 |
+
"base_url": "https://api.groq.com/openai/v1",
|
| 59 |
+
"default_model": "llama-3.3-70b-versatile",
|
| 60 |
+
"type": "openai_compat",
|
| 61 |
+
"max_tokens": 4096,
|
| 62 |
+
"priority": 4,
|
| 63 |
+
},
|
| 64 |
+
# Priority 5 — Cerebras
|
| 65 |
+
{
|
| 66 |
+
"name": "cerebras",
|
| 67 |
+
"key_env": "CEREBRAS_API_KEY",
|
| 68 |
+
"base_url": "https://api.cerebras.ai/v1",
|
| 69 |
+
"default_model": "llama3.1-70b",
|
| 70 |
+
"type": "openai_compat",
|
| 71 |
+
"max_tokens": 4096,
|
| 72 |
+
"priority": 5,
|
| 73 |
+
},
|
| 74 |
+
# Priority 6 — OpenRouter
|
| 75 |
+
{
|
| 76 |
+
"name": "openrouter",
|
| 77 |
+
"key_env": "OPENROUTER_API_KEY",
|
| 78 |
+
"base_url": "https://openrouter.ai/api/v1",
|
| 79 |
+
"default_model": "meta-llama/llama-3.3-70b-instruct:free",
|
| 80 |
+
"type": "openai_compat",
|
| 81 |
+
"max_tokens": 4096,
|
| 82 |
+
"priority": 6,
|
| 83 |
+
},
|
| 84 |
+
# Priority 7 — Anthropic
|
| 85 |
+
{
|
| 86 |
+
"name": "anthropic",
|
| 87 |
+
"key_env": "ANTHROPIC_API_KEY",
|
| 88 |
+
"base_url": "https://api.anthropic.com/v1",
|
| 89 |
+
"default_model": "claude-3-5-sonnet-20241022",
|
| 90 |
+
"type": "anthropic",
|
| 91 |
+
"max_tokens": 4096,
|
| 92 |
+
"priority": 7,
|
| 93 |
+
},
|
| 94 |
+
]
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class AIRouterV8:
|
| 98 |
+
"""
|
| 99 |
+
God Agent OS v8 AI Router.
|
| 100 |
+
- KeyPool-based multi-key management per provider
|
| 101 |
+
- Gemini + SambaNova as primary LLMs
|
| 102 |
+
- Full failover chain
|
| 103 |
+
- Streaming support via WebSocket
|
| 104 |
+
"""
|
| 105 |
+
|
| 106 |
+
def __init__(self, ws_manager=None):
|
| 107 |
+
self.ws = ws_manager
|
| 108 |
+
self._registry = KeyPoolRegistry()
|
| 109 |
+
self._stats: Dict[str, Dict] = {}
|
| 110 |
+
self._setup_pools()
|
| 111 |
+
|
| 112 |
+
def _setup_pools(self):
|
| 113 |
+
"""Initialize key pools from environment variables."""
|
| 114 |
+
for cfg in PROVIDER_CONFIG:
|
| 115 |
+
env_val = os.environ.get(cfg["key_env"], "")
|
| 116 |
+
if env_val:
|
| 117 |
+
self._registry.register(cfg["name"], env_val)
|
| 118 |
+
self._stats[cfg["name"]] = {"calls": 0, "errors": 0, "latency": []}
|
| 119 |
+
elif cfg["name"] not in self._stats:
|
| 120 |
+
self._stats[cfg["name"]] = {"calls": 0, "errors": 0, "latency": []}
|
| 121 |
+
|
| 122 |
+
def _get_available_providers(self) -> List[Dict]:
|
| 123 |
+
"""Return providers with at least one available key, sorted by priority."""
|
| 124 |
+
available = []
|
| 125 |
+
for cfg in sorted(PROVIDER_CONFIG, key=lambda x: x["priority"]):
|
| 126 |
+
pool = self._registry.get(cfg["name"])
|
| 127 |
+
# Also check single-key env vars (for backward compat)
|
| 128 |
+
env_val = os.environ.get(cfg["key_env"], "")
|
| 129 |
+
if (pool and pool.available_count() > 0) or (env_val and not pool):
|
| 130 |
+
# Register single keys on-the-fly if not pooled
|
| 131 |
+
if not pool and env_val:
|
| 132 |
+
self._registry.register(cfg["name"], env_val)
|
| 133 |
+
if cfg["name"] not in self._stats:
|
| 134 |
+
self._stats[cfg["name"]] = {"calls": 0, "errors": 0, "latency": []}
|
| 135 |
+
available.append(cfg)
|
| 136 |
+
return available
|
| 137 |
+
|
| 138 |
+
# ─── Main Entry Point ──────────────────────────────────────────────────────
|
| 139 |
+
|
| 140 |
+
async def complete(
|
| 141 |
+
self,
|
| 142 |
+
messages: List[Dict],
|
| 143 |
+
task_id: str = "",
|
| 144 |
+
session_id: str = "",
|
| 145 |
+
temperature: float = 0.7,
|
| 146 |
+
max_tokens: int = 4096,
|
| 147 |
+
preferred_model: str = "",
|
| 148 |
+
stream: bool = True,
|
| 149 |
+
) -> str:
|
| 150 |
+
"""Route request through available providers with KeyPool failover."""
|
| 151 |
+
providers = self._get_available_providers()
|
| 152 |
+
|
| 153 |
+
if not providers:
|
| 154 |
+
return await self._demo_stream(messages, task_id, session_id)
|
| 155 |
+
|
| 156 |
+
last_error = None
|
| 157 |
+
for cfg in providers:
|
| 158 |
+
pool = self._registry.get(cfg["name"])
|
| 159 |
+
if not pool:
|
| 160 |
+
continue
|
| 161 |
+
|
| 162 |
+
key = pool.pick()
|
| 163 |
+
if not key:
|
| 164 |
+
continue
|
| 165 |
+
|
| 166 |
+
try:
|
| 167 |
+
start = time.time()
|
| 168 |
+
result = await self._call_provider(
|
| 169 |
+
cfg, key, messages, task_id, session_id,
|
| 170 |
+
temperature, max_tokens, preferred_model
|
| 171 |
+
)
|
| 172 |
+
elapsed = time.time() - start
|
| 173 |
+
pool.mark_success(key)
|
| 174 |
+
self._stats[cfg["name"]]["calls"] += 1
|
| 175 |
+
self._stats[cfg["name"]]["latency"].append(elapsed)
|
| 176 |
+
log.info("AIRouter v8 success", provider=cfg["name"], ms=round(elapsed * 1000))
|
| 177 |
+
return result
|
| 178 |
+
except Exception as e:
|
| 179 |
+
last_error = e
|
| 180 |
+
pool.mark_fail(key)
|
| 181 |
+
self._stats[cfg["name"]]["errors"] += 1
|
| 182 |
+
log.warning("AIRouter v8 failover", provider=cfg["name"], error=str(e)[:200])
|
| 183 |
+
continue
|
| 184 |
+
|
| 185 |
+
log.error("All AI providers failed", error=str(last_error))
|
| 186 |
+
return await self._demo_stream(messages, task_id, session_id)
|
| 187 |
+
|
| 188 |
+
async def _call_provider(
|
| 189 |
+
self, cfg: Dict, key: str, messages: List[Dict],
|
| 190 |
+
task_id: str, session_id: str, temperature: float,
|
| 191 |
+
max_tokens: int, preferred_model: str
|
| 192 |
+
) -> str:
|
| 193 |
+
"""Dispatch to the correct call method based on provider type."""
|
| 194 |
+
ptype = cfg["type"]
|
| 195 |
+
if ptype == "gemini":
|
| 196 |
+
return await self._gemini_call(cfg, key, messages, task_id, session_id, temperature, max_tokens, preferred_model)
|
| 197 |
+
elif ptype == "anthropic":
|
| 198 |
+
return await self._anthropic_call(cfg, key, messages, task_id, session_id, temperature, max_tokens)
|
| 199 |
+
else:
|
| 200 |
+
return await self._openai_compat_call(cfg, key, messages, task_id, session_id, temperature, max_tokens, preferred_model)
|
| 201 |
+
|
| 202 |
+
# ─── Gemini API ────────────────────────────────────────────────────────────
|
| 203 |
+
|
| 204 |
+
async def _gemini_call(
|
| 205 |
+
self, cfg: Dict, key: str, messages: List[Dict],
|
| 206 |
+
task_id: str, session_id: str, temperature: float,
|
| 207 |
+
max_tokens: int, preferred_model: str
|
| 208 |
+
) -> str:
|
| 209 |
+
model = preferred_model or cfg["default_model"]
|
| 210 |
+
url = f"{cfg['base_url']}/v1beta/models/{model}:streamGenerateContent?key={key}&alt=sse"
|
| 211 |
+
|
| 212 |
+
# Convert messages to Gemini format
|
| 213 |
+
system_instruction = None
|
| 214 |
+
contents = []
|
| 215 |
+
for m in messages:
|
| 216 |
+
if m["role"] == "system":
|
| 217 |
+
system_instruction = {"parts": [{"text": m["content"]}]}
|
| 218 |
+
else:
|
| 219 |
+
role = "user" if m["role"] == "user" else "model"
|
| 220 |
+
contents.append({"role": role, "parts": [{"text": m["content"]}]})
|
| 221 |
+
|
| 222 |
+
payload: Dict[str, Any] = {
|
| 223 |
+
"contents": contents,
|
| 224 |
+
"generationConfig": {
|
| 225 |
+
"temperature": temperature,
|
| 226 |
+
"maxOutputTokens": max_tokens,
|
| 227 |
+
},
|
| 228 |
+
}
|
| 229 |
+
if system_instruction:
|
| 230 |
+
payload["systemInstruction"] = system_instruction
|
| 231 |
+
|
| 232 |
+
full_text = ""
|
| 233 |
+
async with httpx.AsyncClient(timeout=120) as client:
|
| 234 |
+
async with client.stream("POST", url, json=payload, headers={"Content-Type": "application/json"}) as resp:
|
| 235 |
+
resp.raise_for_status()
|
| 236 |
+
async for line in resp.aiter_lines():
|
| 237 |
+
if not line.startswith("data:"):
|
| 238 |
+
continue
|
| 239 |
+
chunk_str = line[5:].strip()
|
| 240 |
+
if not chunk_str or chunk_str == "[DONE]":
|
| 241 |
+
continue
|
| 242 |
+
try:
|
| 243 |
+
data = json.loads(chunk_str)
|
| 244 |
+
for cand in data.get("candidates", []):
|
| 245 |
+
for part in cand.get("content", {}).get("parts", []):
|
| 246 |
+
delta = part.get("text", "")
|
| 247 |
+
if delta:
|
| 248 |
+
full_text += delta
|
| 249 |
+
await self._emit_chunk(delta, task_id, session_id)
|
| 250 |
+
except Exception:
|
| 251 |
+
pass
|
| 252 |
+
return full_text
|
| 253 |
+
|
| 254 |
+
# ─── OpenAI-compatible ─────────────────────────────────────────────────────
|
| 255 |
+
|
| 256 |
+
async def _openai_compat_call(
|
| 257 |
+
self, cfg: Dict, key: str, messages: List[Dict],
|
| 258 |
+
task_id: str, session_id: str, temperature: float,
|
| 259 |
+
max_tokens: int, preferred_model: str
|
| 260 |
+
) -> str:
|
| 261 |
+
model = preferred_model or cfg["default_model"]
|
| 262 |
+
headers = {
|
| 263 |
+
"Authorization": f"Bearer {key}",
|
| 264 |
+
"Content-Type": "application/json",
|
| 265 |
+
}
|
| 266 |
+
if cfg["name"] == "openrouter":
|
| 267 |
+
headers["HTTP-Referer"] = "https://god-agent.ai"
|
| 268 |
+
headers["X-Title"] = "God Agent OS"
|
| 269 |
+
|
| 270 |
+
payload = {
|
| 271 |
+
"model": model,
|
| 272 |
+
"messages": messages,
|
| 273 |
+
"stream": True,
|
| 274 |
+
"temperature": temperature,
|
| 275 |
+
"max_tokens": max_tokens,
|
| 276 |
+
}
|
| 277 |
+
full_text = ""
|
| 278 |
+
async with httpx.AsyncClient(timeout=120) as client:
|
| 279 |
+
async with client.stream(
|
| 280 |
+
"POST", f"{cfg['base_url']}/chat/completions",
|
| 281 |
+
headers=headers, json=payload
|
| 282 |
+
) as resp:
|
| 283 |
+
resp.raise_for_status()
|
| 284 |
+
async for line in resp.aiter_lines():
|
| 285 |
+
if not line.startswith("data:"):
|
| 286 |
+
continue
|
| 287 |
+
chunk = line[6:].strip()
|
| 288 |
+
if chunk == "[DONE]":
|
| 289 |
+
break
|
| 290 |
+
try:
|
| 291 |
+
data = json.loads(chunk)
|
| 292 |
+
delta = data["choices"][0]["delta"].get("content", "")
|
| 293 |
+
if delta:
|
| 294 |
+
full_text += delta
|
| 295 |
+
await self._emit_chunk(delta, task_id, session_id)
|
| 296 |
+
except Exception:
|
| 297 |
+
pass
|
| 298 |
+
return full_text
|
| 299 |
+
|
| 300 |
+
# ─── Anthropic ─────────────────────────────────────────────────────────────
|
| 301 |
+
|
| 302 |
+
async def _anthropic_call(
|
| 303 |
+
self, cfg: Dict, key: str, messages: List[Dict],
|
| 304 |
+
task_id: str, session_id: str, temperature: float, max_tokens: int
|
| 305 |
+
) -> str:
|
| 306 |
+
headers = {
|
| 307 |
+
"x-api-key": key,
|
| 308 |
+
"anthropic-version": "2023-06-01",
|
| 309 |
+
"Content-Type": "application/json",
|
| 310 |
+
}
|
| 311 |
+
system = ""
|
| 312 |
+
filtered = []
|
| 313 |
+
for m in messages:
|
| 314 |
+
if m["role"] == "system":
|
| 315 |
+
system = m["content"]
|
| 316 |
+
else:
|
| 317 |
+
filtered.append(m)
|
| 318 |
+
payload: Dict[str, Any] = {
|
| 319 |
+
"model": cfg["default_model"],
|
| 320 |
+
"max_tokens": max_tokens,
|
| 321 |
+
"messages": filtered,
|
| 322 |
+
"stream": True,
|
| 323 |
+
"temperature": temperature,
|
| 324 |
+
}
|
| 325 |
+
if system:
|
| 326 |
+
payload["system"] = system
|
| 327 |
+
full_text = ""
|
| 328 |
+
async with httpx.AsyncClient(timeout=120) as client:
|
| 329 |
+
async with client.stream(
|
| 330 |
+
"POST", f"{cfg['base_url']}/messages",
|
| 331 |
+
headers=headers, json=payload
|
| 332 |
+
) as resp:
|
| 333 |
+
resp.raise_for_status()
|
| 334 |
+
async for line in resp.aiter_lines():
|
| 335 |
+
if not line.startswith("data:"):
|
| 336 |
+
continue
|
| 337 |
+
try:
|
| 338 |
+
data = json.loads(line[5:].strip())
|
| 339 |
+
if data.get("type") == "content_block_delta":
|
| 340 |
+
delta = data["delta"].get("text", "")
|
| 341 |
+
if delta:
|
| 342 |
+
full_text += delta
|
| 343 |
+
await self._emit_chunk(delta, task_id, session_id)
|
| 344 |
+
except Exception:
|
| 345 |
+
pass
|
| 346 |
+
return full_text
|
| 347 |
+
|
| 348 |
+
# ─── Demo Stream ───────────────────────────────────────────────────────────
|
| 349 |
+
|
| 350 |
+
async def _demo_stream(self, messages: List[Dict], task_id: str, session_id: str) -> str:
|
| 351 |
+
last_user = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "Hello")
|
| 352 |
+
response = (
|
| 353 |
+
"🤖 **God Agent OS v8** (Demo Mode)\n\n"
|
| 354 |
+
f"Received: *{last_user[:100]}*\n\n"
|
| 355 |
+
"To enable full AI power, set API keys in environment variables:\n"
|
| 356 |
+
"- `GEMINI_API_KEYS` (Google Gemini — multiple keys supported)\n"
|
| 357 |
+
"- `SAMBANOVA_API_KEYS` (SambaNova — multiple keys supported)\n"
|
| 358 |
+
"- `OPENAI_API_KEY`, `GROQ_API_KEY`, `ANTHROPIC_API_KEY`\n\n"
|
| 359 |
+
"**Active Capabilities:**\n"
|
| 360 |
+
"- ⚡ 16-agent autonomous orchestration\n"
|
| 361 |
+
"- 🔑 Multi-key pool with automatic failover\n"
|
| 362 |
+
"- 🧠 Persistent memory system\n"
|
| 363 |
+
"- 🔌 Connector ecosystem\n"
|
| 364 |
+
"- 📡 Real-time WebSocket streaming\n"
|
| 365 |
+
)
|
| 366 |
+
full_text = ""
|
| 367 |
+
for word in response.split():
|
| 368 |
+
chunk = word + " "
|
| 369 |
+
full_text += chunk
|
| 370 |
+
await asyncio.sleep(0.02)
|
| 371 |
+
await self._emit_chunk(chunk, task_id, session_id, demo=True)
|
| 372 |
+
return full_text
|
| 373 |
+
|
| 374 |
+
# ─── Emit Helper ───────────────────────────────────────────────────────────
|
| 375 |
+
|
| 376 |
+
async def _emit_chunk(self, chunk: str, task_id: str, session_id: str, demo: bool = False):
|
| 377 |
+
if not self.ws:
|
| 378 |
+
return
|
| 379 |
+
payload = {"chunk": chunk, "demo": demo}
|
| 380 |
+
if task_id:
|
| 381 |
+
await self.ws.emit(task_id, "llm_chunk", payload, session_id=session_id)
|
| 382 |
+
elif session_id:
|
| 383 |
+
await self.ws.emit_chat(session_id, "llm_chunk", payload)
|
| 384 |
+
|
| 385 |
+
# ─── Stats ─────────────────────────────────────────────────────────────────
|
| 386 |
+
|
| 387 |
+
def get_stats(self) -> Dict:
|
| 388 |
+
result = {}
|
| 389 |
+
for cfg in PROVIDER_CONFIG:
|
| 390 |
+
name = cfg["name"]
|
| 391 |
+
s = self._stats.get(name, {"calls": 0, "errors": 0, "latency": []})
|
| 392 |
+
pool = self._registry.get(name)
|
| 393 |
+
lat = s["latency"][-20:]
|
| 394 |
+
avg_lat = round(sum(lat) / max(len(lat), 1) * 1000, 1) if lat else 0
|
| 395 |
+
result[name] = {
|
| 396 |
+
"calls": s["calls"],
|
| 397 |
+
"errors": s["errors"],
|
| 398 |
+
"avg_latency_ms": avg_lat,
|
| 399 |
+
"available": bool(os.environ.get(cfg["key_env"], "")),
|
| 400 |
+
"key_count": len(pool) if pool else 0,
|
| 401 |
+
"available_keys": pool.available_count() if pool else 0,
|
| 402 |
+
"priority": cfg["priority"],
|
| 403 |
+
}
|
| 404 |
+
return result
|
| 405 |
+
|
| 406 |
+
def get_pool_status(self) -> Dict:
|
| 407 |
+
return self._registry.all_status()
|
backend/main_v8.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
🚀 GOD AGENT OS v8 — Autonomous Engineering Operating System
|
| 3 |
+
Manus + Genspark + Devin (OneHand) Combined
|
| 4 |
+
Version: 8.0.0 — KeyPool Multi-API Routing (Gemini + SambaNova Primary LLMs)
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import asyncio
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
import time
|
| 11 |
+
import uuid
|
| 12 |
+
from contextlib import asynccontextmanager
|
| 13 |
+
from typing import Optional
|
| 14 |
+
|
| 15 |
+
import structlog
|
| 16 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
|
| 17 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 18 |
+
from fastapi.middleware.gzip import GZipMiddleware
|
| 19 |
+
from fastapi.responses import JSONResponse
|
| 20 |
+
from slowapi import Limiter, _rate_limit_exceeded_handler
|
| 21 |
+
from slowapi.util import get_remote_address
|
| 22 |
+
from slowapi.errors import RateLimitExceeded
|
| 23 |
+
|
| 24 |
+
from api.routes import tasks, chat, memory, github, health
|
| 25 |
+
from api.routes import connectors, agents as agents_router
|
| 26 |
+
from api.websocket_manager import WebSocketManager
|
| 27 |
+
from core.task_engine import TaskEngine
|
| 28 |
+
from memory.db import init_db
|
| 29 |
+
|
| 30 |
+
# ─── v8 AI Router (KeyPool-based) ─────────────────────────────────────────────
|
| 31 |
+
from ai_router.router_v8 import AIRouterV8
|
| 32 |
+
|
| 33 |
+
# ─── Agent Ecosystem ──────────────────────────────────────────────────────────
|
| 34 |
+
from agents.orchestrator_v7 import GodAgentOrchestratorV7
|
| 35 |
+
from agents.chat_agent import ChatAgent
|
| 36 |
+
from agents.planner_agent import PlannerAgent
|
| 37 |
+
from agents.coding_agent import CodingAgent
|
| 38 |
+
from agents.debug_agent import DebugAgent
|
| 39 |
+
from agents.memory_agent import MemoryAgent
|
| 40 |
+
from agents.connector_agent import ConnectorAgent
|
| 41 |
+
from agents.deploy_agent import DeployAgent
|
| 42 |
+
from agents.workflow_agent import WorkflowAgent
|
| 43 |
+
from agents.sandbox_agent import SandboxAgent
|
| 44 |
+
from agents.ui_agent import UIAgent
|
| 45 |
+
from agents.reasoning_agent import ReasoningAgent
|
| 46 |
+
from agents.browser_agent import BrowserAgent
|
| 47 |
+
from agents.file_agent import FileAgent
|
| 48 |
+
from agents.git_agent import GitAgent
|
| 49 |
+
from agents.test_agent import TestAgent
|
| 50 |
+
from agents.vision_agent import VisionAgent
|
| 51 |
+
from connectors.manager import ConnectorManager
|
| 52 |
+
|
| 53 |
+
# ─── Structured Logging ───────────────────────────────────────────────────────
|
| 54 |
+
structlog.configure(
|
| 55 |
+
processors=[
|
| 56 |
+
structlog.processors.TimeStamper(fmt="iso"),
|
| 57 |
+
structlog.stdlib.add_log_level,
|
| 58 |
+
structlog.processors.StackInfoRenderer(),
|
| 59 |
+
structlog.dev.ConsoleRenderer(),
|
| 60 |
+
]
|
| 61 |
+
)
|
| 62 |
+
log = structlog.get_logger()
|
| 63 |
+
|
| 64 |
+
limiter = Limiter(key_func=get_remote_address)
|
| 65 |
+
|
| 66 |
+
# ─── Global Managers ──────────────────────────────────────────────────────────
|
| 67 |
+
ws_manager = WebSocketManager()
|
| 68 |
+
task_engine = TaskEngine(ws_manager)
|
| 69 |
+
ai_router = AIRouterV8(ws_manager)
|
| 70 |
+
connector_manager = ConnectorManager()
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def build_orchestrator() -> GodAgentOrchestratorV7:
|
| 74 |
+
orchestrator = GodAgentOrchestratorV7(ws_manager=ws_manager, ai_router=ai_router)
|
| 75 |
+
orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
|
| 76 |
+
orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
|
| 77 |
+
orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
|
| 78 |
+
orchestrator.register_agent("debug", DebugAgent(ws_manager, ai_router))
|
| 79 |
+
orchestrator.register_agent("memory", MemoryAgent(ws_manager, ai_router))
|
| 80 |
+
orchestrator.register_agent("connector", ConnectorAgent(ws_manager, ai_router))
|
| 81 |
+
orchestrator.register_agent("deploy", DeployAgent(ws_manager, ai_router))
|
| 82 |
+
orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
|
| 83 |
+
orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
|
| 84 |
+
orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
|
| 85 |
+
orchestrator.register_agent("reasoning", ReasoningAgent(ws_manager, ai_router))
|
| 86 |
+
orchestrator.register_agent("browser", BrowserAgent(ws_manager, ai_router))
|
| 87 |
+
orchestrator.register_agent("file", FileAgent(ws_manager, ai_router))
|
| 88 |
+
orchestrator.register_agent("git", GitAgent(ws_manager, ai_router))
|
| 89 |
+
orchestrator.register_agent("test", TestAgent(ws_manager, ai_router))
|
| 90 |
+
orchestrator.register_agent("vision", VisionAgent(ws_manager, ai_router))
|
| 91 |
+
log.info("🤖 GOD AGENT v8 Ecosystem initialized", agents=16)
|
| 92 |
+
return orchestrator
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
orchestrator = build_orchestrator()
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
@asynccontextmanager
|
| 99 |
+
async def lifespan(app: FastAPI):
|
| 100 |
+
log.info("🚀 Starting GOD AGENT OS v8 — KeyPool Multi-API Edition...")
|
| 101 |
+
await init_db()
|
| 102 |
+
await task_engine.start()
|
| 103 |
+
asyncio.create_task(ws_manager.heartbeat_loop())
|
| 104 |
+
# Print router status
|
| 105 |
+
stats = ai_router.get_stats()
|
| 106 |
+
active = [name for name, s in stats.items() if s["available"]]
|
| 107 |
+
log.info("✅ GOD AGENT v8 — 16 agents online")
|
| 108 |
+
log.info(f"🔑 Active AI providers: {active}")
|
| 109 |
+
log.info("🌐 AI Routing: SambaNova → Gemini → OpenAI → Groq → Cerebras → OpenRouter → Anthropic")
|
| 110 |
+
yield
|
| 111 |
+
log.info("🛑 Shutting down GOD AGENT v8...")
|
| 112 |
+
await task_engine.stop()
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
app = FastAPI(
|
| 116 |
+
title="🤖 GOD AGENT OS v8",
|
| 117 |
+
description="Autonomous Engineering OS — Gemini + SambaNova KeyPool Multi-API Routing",
|
| 118 |
+
version="8.0.0",
|
| 119 |
+
lifespan=lifespan,
|
| 120 |
+
docs_url="/api/docs",
|
| 121 |
+
redoc_url="/api/redoc",
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
app.state.limiter = limiter
|
| 125 |
+
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
| 126 |
+
app.state.ws_manager = ws_manager
|
| 127 |
+
app.state.task_engine = task_engine
|
| 128 |
+
app.state.ai_router = ai_router
|
| 129 |
+
app.state.orchestrator = orchestrator
|
| 130 |
+
app.state.connector_manager = connector_manager
|
| 131 |
+
|
| 132 |
+
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
|
| 133 |
+
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
@app.middleware("http")
|
| 137 |
+
async def log_requests(request: Request, call_next):
|
| 138 |
+
start = time.time()
|
| 139 |
+
response = await call_next(request)
|
| 140 |
+
ms = round((time.time() - start) * 1000, 2)
|
| 141 |
+
log.info("HTTP", method=request.method, path=request.url.path, status=response.status_code, ms=ms)
|
| 142 |
+
return response
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
# ─── REST Routers ─────────────────────────────────────────────────────────────
|
| 146 |
+
app.include_router(health.router, prefix="/api/v1", tags=["health"])
|
| 147 |
+
app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
|
| 148 |
+
app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
|
| 149 |
+
app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
|
| 150 |
+
app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
|
| 151 |
+
app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
|
| 152 |
+
app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# ─── WebSocket Endpoints ──────────────────────────────────────────────────────
|
| 156 |
+
@app.websocket("/ws/tasks/{task_id}")
|
| 157 |
+
async def ws_task(websocket: WebSocket, task_id: str):
|
| 158 |
+
await ws_manager.connect(websocket, room=f"task:{task_id}")
|
| 159 |
+
try:
|
| 160 |
+
while True:
|
| 161 |
+
data = await websocket.receive_text()
|
| 162 |
+
msg = json.loads(data)
|
| 163 |
+
if msg.get("type") == "ping":
|
| 164 |
+
await websocket.send_json({"type": "pong", "timestamp": time.time()})
|
| 165 |
+
except WebSocketDisconnect:
|
| 166 |
+
ws_manager.disconnect(websocket, room=f"task:{task_id}")
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
@app.websocket("/ws/logs")
|
| 170 |
+
async def ws_logs(websocket: WebSocket):
|
| 171 |
+
await ws_manager.connect(websocket, room="logs")
|
| 172 |
+
try:
|
| 173 |
+
while True:
|
| 174 |
+
data = await websocket.receive_text()
|
| 175 |
+
msg = json.loads(data)
|
| 176 |
+
if msg.get("type") == "ping":
|
| 177 |
+
await websocket.send_json({"type": "pong", "timestamp": time.time()})
|
| 178 |
+
except WebSocketDisconnect:
|
| 179 |
+
ws_manager.disconnect(websocket, room="logs")
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
@app.websocket("/ws/chat/{session_id}")
|
| 183 |
+
async def ws_chat(websocket: WebSocket, session_id: str):
|
| 184 |
+
await ws_manager.connect(websocket, room=f"chat:{session_id}")
|
| 185 |
+
try:
|
| 186 |
+
while True:
|
| 187 |
+
data = await websocket.receive_text()
|
| 188 |
+
msg = json.loads(data)
|
| 189 |
+
if msg.get("type") == "ping":
|
| 190 |
+
await websocket.send_json({"type": "pong", "timestamp": time.time()})
|
| 191 |
+
elif msg.get("type") == "chat_message":
|
| 192 |
+
asyncio.create_task(orchestrator.orchestrate(
|
| 193 |
+
user_message=msg.get("content", ""),
|
| 194 |
+
session_id=session_id,
|
| 195 |
+
context=msg.get("context", {}),
|
| 196 |
+
))
|
| 197 |
+
elif msg.get("type") == "task_message":
|
| 198 |
+
from core.models import TaskCreateRequest
|
| 199 |
+
req = TaskCreateRequest(goal=msg.get("content", ""), session_id=session_id)
|
| 200 |
+
asyncio.create_task(task_engine.submit(req))
|
| 201 |
+
except WebSocketDisconnect:
|
| 202 |
+
ws_manager.disconnect(websocket, room=f"chat:{session_id}")
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
@app.websocket("/ws/agent/status")
|
| 206 |
+
async def ws_agent_status(websocket: WebSocket):
|
| 207 |
+
await ws_manager.connect(websocket, room="agent_status")
|
| 208 |
+
try:
|
| 209 |
+
while True:
|
| 210 |
+
data = await websocket.receive_text()
|
| 211 |
+
msg = json.loads(data)
|
| 212 |
+
if msg.get("type") == "ping":
|
| 213 |
+
await websocket.send_json({"type": "pong", "timestamp": time.time()})
|
| 214 |
+
elif msg.get("type") == "get_status":
|
| 215 |
+
await websocket.send_json({"type": "agent_status", "data": orchestrator.get_status()})
|
| 216 |
+
except WebSocketDisconnect:
|
| 217 |
+
ws_manager.disconnect(websocket, room="agent_status")
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
@app.websocket("/ws/sandbox/{session_id}")
|
| 221 |
+
async def ws_sandbox(websocket: WebSocket, session_id: str):
|
| 222 |
+
await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
|
| 223 |
+
sandbox = orchestrator.get_agent("sandbox")
|
| 224 |
+
try:
|
| 225 |
+
while True:
|
| 226 |
+
data = await websocket.receive_text()
|
| 227 |
+
msg = json.loads(data)
|
| 228 |
+
if msg.get("type") == "ping":
|
| 229 |
+
await websocket.send_json({"type": "pong", "timestamp": time.time()})
|
| 230 |
+
elif msg.get("type") == "execute" and sandbox:
|
| 231 |
+
cmd = msg.get("command", "")
|
| 232 |
+
result = await sandbox.execute(cmd, session_id=session_id)
|
| 233 |
+
await websocket.send_json({"type": "terminal_output", "command": cmd, "output": result, "timestamp": time.time()})
|
| 234 |
+
except WebSocketDisconnect:
|
| 235 |
+
ws_manager.disconnect(websocket, room=f"sandbox:{session_id}")
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
# ─── v8 Key Pool & AI Router Status Endpoints ─────────────────────────────────
|
| 239 |
+
@app.get("/api/v1/ai/stats")
|
| 240 |
+
async def get_ai_stats():
|
| 241 |
+
return {"stats": ai_router.get_stats()}
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
@app.get("/api/v1/ai/pool-status")
|
| 245 |
+
async def get_pool_status():
|
| 246 |
+
return {"pools": ai_router.get_pool_status()}
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
@app.post("/api/v1/browser/research")
|
| 250 |
+
async def browser_research(request: Request):
|
| 251 |
+
body = await request.json()
|
| 252 |
+
browser = orchestrator.get_agent("browser")
|
| 253 |
+
if not browser:
|
| 254 |
+
raise HTTPException(status_code=503, detail="BrowserAgent not available")
|
| 255 |
+
result = await browser.run(body.get("query", ""), session_id=body.get("session_id", ""))
|
| 256 |
+
return {"result": result}
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
@app.get("/api/v1/files/workspace")
|
| 260 |
+
async def list_workspace():
|
| 261 |
+
file_agent = orchestrator.get_agent("file")
|
| 262 |
+
if not file_agent:
|
| 263 |
+
return {"workspace": "/tmp/god_workspace", "files": [], "total": 0}
|
| 264 |
+
return file_agent.list_workspace()
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
@app.post("/api/v1/git/pr")
|
| 268 |
+
async def create_pr(request: Request):
|
| 269 |
+
body = await request.json()
|
| 270 |
+
git_agent = orchestrator.get_agent("git")
|
| 271 |
+
if not git_agent:
|
| 272 |
+
raise HTTPException(status_code=503, detail="GitAgent not available")
|
| 273 |
+
result = await git_agent.create_github_pr(
|
| 274 |
+
repo_owner=body.get("owner", ""),
|
| 275 |
+
repo_name=body.get("repo", ""),
|
| 276 |
+
title=body.get("title", ""),
|
| 277 |
+
body=body.get("body", ""),
|
| 278 |
+
head_branch=body.get("head_branch", "main"),
|
| 279 |
+
base_branch=body.get("base_branch", "main"),
|
| 280 |
+
)
|
| 281 |
+
return result
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
@app.post("/api/v1/vision/generate")
|
| 285 |
+
async def generate_ui(request: Request):
|
| 286 |
+
body = await request.json()
|
| 287 |
+
vision = orchestrator.get_agent("vision")
|
| 288 |
+
if not vision:
|
| 289 |
+
raise HTTPException(status_code=503, detail="VisionAgent not available")
|
| 290 |
+
result = await vision.run(body.get("prompt", ""), context=body.get("context", {}), session_id=body.get("session_id", ""))
|
| 291 |
+
return {"result": result}
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# ─── Root ─────────────────────────────────────────────────────────────────────
|
| 295 |
+
@app.get("/")
|
| 296 |
+
async def root():
|
| 297 |
+
cs = connector_manager.get_summary()
|
| 298 |
+
status = orchestrator.get_status()
|
| 299 |
+
stats = ai_router.get_stats()
|
| 300 |
+
active_providers = [name for name, s in stats.items() if s["available"]]
|
| 301 |
+
return {
|
| 302 |
+
"name": "🤖 GOD AGENT OS v8",
|
| 303 |
+
"version": "8.0.0",
|
| 304 |
+
"status": "operational",
|
| 305 |
+
"mode": "autonomous_engineering_os",
|
| 306 |
+
"description": "KeyPool Multi-API Routing — Gemini + SambaNova Primary LLMs",
|
| 307 |
+
"agents": status["agents"],
|
| 308 |
+
"total_agents": status["total_agents"],
|
| 309 |
+
"ai_providers": active_providers,
|
| 310 |
+
"connectors": {"connected": cs["connected"], "total": cs["total"]},
|
| 311 |
+
"docs": "/api/docs",
|
| 312 |
+
"v8_features": [
|
| 313 |
+
"🔑 KeyPool multi-key management (Gemini 6 keys + SambaNova 9 keys)",
|
| 314 |
+
"🔄 Automatic key failover with cooldown tracking",
|
| 315 |
+
"⚡ SambaNova → Gemini → OpenAI → Groq → Cerebras chain",
|
| 316 |
+
"📊 Per-key usage stats & health monitoring",
|
| 317 |
+
"🤖 16-agent autonomous fleet",
|
| 318 |
+
"🌐 Real-time streaming via WebSocket",
|
| 319 |
+
],
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
if __name__ == "__main__":
|
| 324 |
+
import uvicorn
|
| 325 |
+
port = int(os.environ.get("PORT", 8000))
|
| 326 |
+
uvicorn.run("main_v8:app", host="0.0.0.0", port=port, reload=False, workers=1)
|
frontend/app/page.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import ConnectorsPanel from '@/components/layout/ConnectorsPanel'
|
|
| 13 |
import SandboxPanel from '@/components/layout/SandboxPanel'
|
| 14 |
import FileExplorer from '@/components/layout/FileExplorer'
|
| 15 |
import BrowserPanel from '@/components/layout/BrowserPanel'
|
|
|
|
| 16 |
import { Zap } from 'lucide-react'
|
| 17 |
|
| 18 |
export default function HomePage() {
|
|
@@ -39,8 +40,9 @@ export default function HomePage() {
|
|
| 39 |
<Zap size={28} className="text-indigo-400" />
|
| 40 |
</div>
|
| 41 |
<h2 className="text-lg font-bold mb-1" style={{ color: 'var(--text-primary)' }}>GOD AGENT OS</h2>
|
| 42 |
-
<p className="text-sm mb-1" style={{ color: 'var(--text-muted)' }}>Autonomous Engineering Platform
|
| 43 |
-
<p className="text-xs mb-
|
|
|
|
| 44 |
<div className="flex gap-1.5 justify-center">
|
| 45 |
{[0, 1, 2].map(i => (
|
| 46 |
<div key={i} className="typing-dot" style={{ animationDelay: `${i * 0.16}s` }} />
|
|
@@ -59,6 +61,7 @@ export default function HomePage() {
|
|
| 59 |
case 'sandbox': return <SandboxPanel />
|
| 60 |
case 'files': return <FileExplorer />
|
| 61 |
case 'browser': return <BrowserPanel />
|
|
|
|
| 62 |
default: return <ExecutionTimeline />
|
| 63 |
}
|
| 64 |
}
|
|
|
|
| 13 |
import SandboxPanel from '@/components/layout/SandboxPanel'
|
| 14 |
import FileExplorer from '@/components/layout/FileExplorer'
|
| 15 |
import BrowserPanel from '@/components/layout/BrowserPanel'
|
| 16 |
+
import AIRouterPanel from '@/components/layout/AIRouterPanel'
|
| 17 |
import { Zap } from 'lucide-react'
|
| 18 |
|
| 19 |
export default function HomePage() {
|
|
|
|
| 40 |
<Zap size={28} className="text-indigo-400" />
|
| 41 |
</div>
|
| 42 |
<h2 className="text-lg font-bold mb-1" style={{ color: 'var(--text-primary)' }}>GOD AGENT OS</h2>
|
| 43 |
+
<p className="text-sm mb-1" style={{ color: 'var(--text-muted)' }}>Autonomous Engineering Platform v8.0</p>
|
| 44 |
+
<p className="text-xs mb-1" style={{ color: 'var(--text-muted)' }}>Manus + Genspark + Devin</p>
|
| 45 |
+
<p className="text-xs mb-4" style={{ color: '#6366f1' }}>KeyPool: Gemini × 6 + SambaNova × 9</p>
|
| 46 |
<div className="flex gap-1.5 justify-center">
|
| 47 |
{[0, 1, 2].map(i => (
|
| 48 |
<div key={i} className="typing-dot" style={{ animationDelay: `${i * 0.16}s` }} />
|
|
|
|
| 61 |
case 'sandbox': return <SandboxPanel />
|
| 62 |
case 'files': return <FileExplorer />
|
| 63 |
case 'browser': return <BrowserPanel />
|
| 64 |
+
case 'ai_router': return <AIRouterPanel />
|
| 65 |
default: return <ExecutionTimeline />
|
| 66 |
}
|
| 67 |
}
|
frontend/components/layout/AIRouterPanel.tsx
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client'
|
| 2 |
+
|
| 3 |
+
import { useEffect, useState } from 'react'
|
| 4 |
+
import { useAgentStore } from '@/hooks/useAgentStore'
|
| 5 |
+
import { Cpu, Key, RefreshCw, CheckCircle2, XCircle, AlertCircle, Zap, Activity } from 'lucide-react'
|
| 6 |
+
|
| 7 |
+
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:7860'
|
| 8 |
+
|
| 9 |
+
interface ProviderStat {
|
| 10 |
+
calls: number
|
| 11 |
+
errors: number
|
| 12 |
+
avg_latency_ms: number
|
| 13 |
+
available: boolean
|
| 14 |
+
key_count: number
|
| 15 |
+
available_keys: number
|
| 16 |
+
priority: number
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
interface KeyInfo {
|
| 20 |
+
key_preview: string
|
| 21 |
+
available: boolean
|
| 22 |
+
failures: number
|
| 23 |
+
calls: number
|
| 24 |
+
cooldown_remaining_s: number
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
interface PoolStatus {
|
| 28 |
+
provider: string
|
| 29 |
+
total_keys: number
|
| 30 |
+
available_keys: number
|
| 31 |
+
keys: KeyInfo[]
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
const PROVIDER_ICONS: Record<string, string> = {
|
| 35 |
+
sambanova: '⚡',
|
| 36 |
+
gemini: '✨',
|
| 37 |
+
openai: '🤖',
|
| 38 |
+
groq: '🦙',
|
| 39 |
+
cerebras: '🧠',
|
| 40 |
+
openrouter: '🔀',
|
| 41 |
+
anthropic: '🪄',
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
const PROVIDER_COLORS: Record<string, string> = {
|
| 45 |
+
sambanova: '#f97316',
|
| 46 |
+
gemini: '#4285f4',
|
| 47 |
+
openai: '#10a37f',
|
| 48 |
+
groq: '#f59e0b',
|
| 49 |
+
cerebras: '#8b5cf6',
|
| 50 |
+
openrouter: '#6366f1',
|
| 51 |
+
anthropic: '#d97706',
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
export default function AIRouterPanel() {
|
| 55 |
+
const { locale } = useAgentStore()
|
| 56 |
+
const [stats, setStats] = useState<Record<string, ProviderStat>>({})
|
| 57 |
+
const [pools, setPools] = useState<Record<string, PoolStatus>>({})
|
| 58 |
+
const [loading, setLoading] = useState(true)
|
| 59 |
+
const [expandedProvider, setExpandedProvider] = useState<string | null>(null)
|
| 60 |
+
const [lastRefresh, setLastRefresh] = useState<Date | null>(null)
|
| 61 |
+
|
| 62 |
+
const load = async () => {
|
| 63 |
+
setLoading(true)
|
| 64 |
+
try {
|
| 65 |
+
const [statsRes, poolRes] = await Promise.all([
|
| 66 |
+
fetch(`${API_URL}/api/v1/ai/stats`).then(r => r.json()).catch(() => ({})),
|
| 67 |
+
fetch(`${API_URL}/api/v1/ai/pool-status`).then(r => r.json()).catch(() => ({})),
|
| 68 |
+
])
|
| 69 |
+
setStats(statsRes.stats || {})
|
| 70 |
+
setPools(poolRes.pools || {})
|
| 71 |
+
setLastRefresh(new Date())
|
| 72 |
+
} catch {}
|
| 73 |
+
setLoading(false)
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
useEffect(() => {
|
| 77 |
+
load()
|
| 78 |
+
const interval = setInterval(load, 30000)
|
| 79 |
+
return () => clearInterval(interval)
|
| 80 |
+
}, [])
|
| 81 |
+
|
| 82 |
+
const sortedProviders = Object.entries(stats).sort(
|
| 83 |
+
([, a], [, b]) => (a.priority || 99) - (b.priority || 99)
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
const activeCount = sortedProviders.filter(([, s]) => s.available).length
|
| 87 |
+
const totalCalls = sortedProviders.reduce((sum, [, s]) => sum + s.calls, 0)
|
| 88 |
+
|
| 89 |
+
return (
|
| 90 |
+
<div className="flex flex-col h-full" style={{ background: 'var(--bg-2)' }}>
|
| 91 |
+
{/* Header */}
|
| 92 |
+
<div className="flex items-center justify-between px-4 py-2.5 border-b shrink-0"
|
| 93 |
+
style={{ borderColor: 'var(--border)', background: 'var(--bg-3)' }}>
|
| 94 |
+
<div className="flex items-center gap-2">
|
| 95 |
+
<Cpu size={14} className="text-indigo-400" />
|
| 96 |
+
<span className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}>
|
| 97 |
+
{locale === 'my' ? 'AI Router v8' : 'AI Router v8'}
|
| 98 |
+
</span>
|
| 99 |
+
<span className="text-[10px] px-1.5 py-0.5 rounded-full"
|
| 100 |
+
style={{
|
| 101 |
+
background: activeCount > 0 ? 'rgba(34,197,94,0.15)' : 'rgba(239,68,68,0.15)',
|
| 102 |
+
color: activeCount > 0 ? '#4ade80' : '#f87171',
|
| 103 |
+
border: `1px solid ${activeCount > 0 ? 'rgba(34,197,94,0.3)' : 'rgba(239,68,68,0.3)'}`,
|
| 104 |
+
}}>
|
| 105 |
+
{activeCount} active
|
| 106 |
+
</span>
|
| 107 |
+
</div>
|
| 108 |
+
<button onClick={load} disabled={loading}
|
| 109 |
+
className="p-1.5 rounded-lg hover:bg-white/5 transition-colors" title="Refresh">
|
| 110 |
+
<RefreshCw size={12} style={{ color: 'var(--text-muted)' }}
|
| 111 |
+
className={loading ? 'animate-spin' : ''} />
|
| 112 |
+
</button>
|
| 113 |
+
</div>
|
| 114 |
+
|
| 115 |
+
{/* Summary */}
|
| 116 |
+
<div className="px-3 py-2 border-b flex gap-3 text-xs"
|
| 117 |
+
style={{ borderColor: 'var(--border)', background: 'rgba(99,102,241,0.05)' }}>
|
| 118 |
+
<div className="flex items-center gap-1">
|
| 119 |
+
<Activity size={10} className="text-indigo-400" />
|
| 120 |
+
<span style={{ color: 'var(--text-muted)' }}>Total calls:</span>
|
| 121 |
+
<span className="font-mono font-semibold" style={{ color: 'var(--text-primary)' }}>{totalCalls}</span>
|
| 122 |
+
</div>
|
| 123 |
+
<div className="flex items-center gap-1">
|
| 124 |
+
<Key size={10} className="text-indigo-400" />
|
| 125 |
+
<span style={{ color: 'var(--text-muted)' }}>Providers:</span>
|
| 126 |
+
<span className="font-mono font-semibold" style={{ color: 'var(--text-primary)' }}>
|
| 127 |
+
{activeCount}/{sortedProviders.length}
|
| 128 |
+
</span>
|
| 129 |
+
</div>
|
| 130 |
+
{lastRefresh && (
|
| 131 |
+
<div className="ml-auto text-[10px]" style={{ color: 'var(--text-muted)' }}>
|
| 132 |
+
{lastRefresh.toLocaleTimeString()}
|
| 133 |
+
</div>
|
| 134 |
+
)}
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
{/* Provider List */}
|
| 138 |
+
<div className="flex-1 overflow-y-auto">
|
| 139 |
+
{loading && sortedProviders.length === 0 ? (
|
| 140 |
+
<div className="flex items-center justify-center h-24">
|
| 141 |
+
<div className="flex gap-1">
|
| 142 |
+
{[0, 1, 2].map(i => (
|
| 143 |
+
<div key={i} className="w-1.5 h-1.5 rounded-full animate-bounce"
|
| 144 |
+
style={{ background: 'var(--brand)', animationDelay: `${i * 0.15}s` }} />
|
| 145 |
+
))}
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
) : sortedProviders.length === 0 ? (
|
| 149 |
+
<div className="p-4 text-center text-xs" style={{ color: 'var(--text-muted)' }}>
|
| 150 |
+
No providers configured
|
| 151 |
+
</div>
|
| 152 |
+
) : (
|
| 153 |
+
<div className="p-2 flex flex-col gap-1.5">
|
| 154 |
+
{sortedProviders.map(([name, stat]) => {
|
| 155 |
+
const pool = pools[name]
|
| 156 |
+
const icon = PROVIDER_ICONS[name] || '🔌'
|
| 157 |
+
const color = PROVIDER_COLORS[name] || '#6366f1'
|
| 158 |
+
const isExpanded = expandedProvider === name
|
| 159 |
+
const successRate = stat.calls > 0
|
| 160 |
+
? Math.round(((stat.calls - stat.errors) / stat.calls) * 100)
|
| 161 |
+
: 100
|
| 162 |
+
|
| 163 |
+
return (
|
| 164 |
+
<div key={name}
|
| 165 |
+
className="rounded-xl border overflow-hidden cursor-pointer hover:border-opacity-60 transition-all"
|
| 166 |
+
style={{
|
| 167 |
+
borderColor: stat.available ? `${color}40` : 'var(--border)',
|
| 168 |
+
background: stat.available ? `${color}08` : 'var(--bg-3)',
|
| 169 |
+
}}
|
| 170 |
+
onClick={() => setExpandedProvider(isExpanded ? null : name)}>
|
| 171 |
+
|
| 172 |
+
{/* Provider Header */}
|
| 173 |
+
<div className="flex items-center gap-2 px-3 py-2">
|
| 174 |
+
<span className="text-sm">{icon}</span>
|
| 175 |
+
<div className="flex-1 min-w-0">
|
| 176 |
+
<div className="flex items-center gap-2">
|
| 177 |
+
<span className="text-xs font-semibold capitalize" style={{ color: 'var(--text-primary)' }}>
|
| 178 |
+
{name}
|
| 179 |
+
</span>
|
| 180 |
+
{stat.priority <= 2 && (
|
| 181 |
+
<span className="text-[9px] px-1 py-0.5 rounded font-bold"
|
| 182 |
+
style={{ background: `${color}25`, color }}>
|
| 183 |
+
PRIMARY
|
| 184 |
+
</span>
|
| 185 |
+
)}
|
| 186 |
+
</div>
|
| 187 |
+
<div className="flex items-center gap-2 mt-0.5">
|
| 188 |
+
<span className="text-[10px]" style={{ color: 'var(--text-muted)' }}>
|
| 189 |
+
{stat.calls} calls · {stat.avg_latency_ms}ms avg
|
| 190 |
+
</span>
|
| 191 |
+
{pool && (
|
| 192 |
+
<span className="text-[10px]" style={{ color: 'var(--text-muted)' }}>
|
| 193 |
+
· {pool.available_keys}/{pool.total_keys} keys
|
| 194 |
+
</span>
|
| 195 |
+
)}
|
| 196 |
+
</div>
|
| 197 |
+
</div>
|
| 198 |
+
<div className="flex items-center gap-1.5">
|
| 199 |
+
{/* Success Rate Bar */}
|
| 200 |
+
{stat.calls > 0 && (
|
| 201 |
+
<div className="w-12 h-1.5 rounded-full overflow-hidden"
|
| 202 |
+
style={{ background: 'var(--bg-0)' }}>
|
| 203 |
+
<div className="h-full rounded-full transition-all"
|
| 204 |
+
style={{
|
| 205 |
+
width: `${successRate}%`,
|
| 206 |
+
background: successRate > 80 ? '#4ade80' : successRate > 50 ? '#fbbf24' : '#f87171',
|
| 207 |
+
}} />
|
| 208 |
+
</div>
|
| 209 |
+
)}
|
| 210 |
+
{stat.available ? (
|
| 211 |
+
<CheckCircle2 size={12} style={{ color: '#4ade80' }} />
|
| 212 |
+
) : (
|
| 213 |
+
<XCircle size={12} style={{ color: '#f87171' }} />
|
| 214 |
+
)}
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
{/* Expanded Key Pool */}
|
| 219 |
+
{isExpanded && pool && pool.keys.length > 0 && (
|
| 220 |
+
<div className="px-3 pb-3 border-t" style={{ borderColor: 'var(--border)' }}>
|
| 221 |
+
<div className="pt-2 text-[10px] font-semibold mb-2" style={{ color: 'var(--text-muted)' }}>
|
| 222 |
+
KEY POOL ({pool.available_keys}/{pool.total_keys} available)
|
| 223 |
+
</div>
|
| 224 |
+
<div className="flex flex-col gap-1">
|
| 225 |
+
{pool.keys.map((k, i) => (
|
| 226 |
+
<div key={i} className="flex items-center gap-2 px-2 py-1 rounded-lg"
|
| 227 |
+
style={{ background: k.available ? 'rgba(34,197,94,0.05)' : 'rgba(239,68,68,0.05)' }}>
|
| 228 |
+
<Key size={9} style={{ color: k.available ? '#4ade80' : '#f87171' }} />
|
| 229 |
+
<span className="font-mono text-[10px] flex-1" style={{ color: 'var(--text-secondary)' }}>
|
| 230 |
+
{k.key_preview}
|
| 231 |
+
</span>
|
| 232 |
+
<span className="text-[9px]" style={{ color: 'var(--text-muted)' }}>
|
| 233 |
+
{k.calls} calls
|
| 234 |
+
</span>
|
| 235 |
+
{k.failures > 0 && (
|
| 236 |
+
<span className="text-[9px]" style={{ color: '#fbbf24' }}>
|
| 237 |
+
{k.failures} fails
|
| 238 |
+
</span>
|
| 239 |
+
)}
|
| 240 |
+
{!k.available && k.cooldown_remaining_s > 0 && (
|
| 241 |
+
<span className="text-[9px] px-1 rounded"
|
| 242 |
+
style={{ background: 'rgba(239,68,68,0.15)', color: '#f87171' }}>
|
| 243 |
+
{Math.round(k.cooldown_remaining_s)}s
|
| 244 |
+
</span>
|
| 245 |
+
)}
|
| 246 |
+
</div>
|
| 247 |
+
))}
|
| 248 |
+
</div>
|
| 249 |
+
</div>
|
| 250 |
+
)}
|
| 251 |
+
</div>
|
| 252 |
+
)
|
| 253 |
+
})}
|
| 254 |
+
</div>
|
| 255 |
+
)}
|
| 256 |
+
</div>
|
| 257 |
+
|
| 258 |
+
{/* Footer Info */}
|
| 259 |
+
<div className="px-3 py-2 border-t text-[10px]"
|
| 260 |
+
style={{ borderColor: 'var(--border)', color: 'var(--text-muted)', background: 'var(--bg-3)' }}>
|
| 261 |
+
<div className="flex items-center gap-1">
|
| 262 |
+
<Zap size={9} className="text-indigo-400" />
|
| 263 |
+
<span>Priority: SambaNova → Gemini → OpenAI → Groq → Cerebras → Anthropic</span>
|
| 264 |
+
</div>
|
| 265 |
+
</div>
|
| 266 |
+
</div>
|
| 267 |
+
)
|
| 268 |
+
}
|
frontend/components/layout/Sidebar.tsx
CHANGED
|
@@ -17,6 +17,7 @@ const PANELS: { id: ActivePanel; icon: React.ElementType; labelEn: string; label
|
|
| 17 |
{ id: 'browser', icon: Globe, labelEn: 'Browser', labelMy: 'ဘရောင်ဇာ', badge: 'v7' },
|
| 18 |
{ id: 'memory', icon: Brain, labelEn: 'Memory', labelMy: 'မှတ်ဉာဏ်' },
|
| 19 |
{ id: 'connectors', icon: Plug, labelEn: 'Connectors', labelMy: 'ချိတ်ဆက်မှု' },
|
|
|
|
| 20 |
]
|
| 21 |
|
| 22 |
const AGENT_META: Record<string, { icon: React.ElementType; color: string; label: string; isNew?: boolean }> = {
|
|
@@ -128,7 +129,7 @@ export default function Sidebar() {
|
|
| 128 |
<div className="flex items-center gap-2 px-2 py-1.5 rounded-lg text-[10px]"
|
| 129 |
style={{ background: 'rgba(99,102,241,0.08)', border: '1px solid rgba(99,102,241,0.2)' }}>
|
| 130 |
<Bot size={10} className="text-indigo-400" />
|
| 131 |
-
<span style={{ color: 'var(--text-muted)' }}>God Agent OS
|
| 132 |
<div className="ml-auto w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
|
| 133 |
</div>
|
| 134 |
</div>
|
|
|
|
| 17 |
{ id: 'browser', icon: Globe, labelEn: 'Browser', labelMy: 'ဘရောင်ဇာ', badge: 'v7' },
|
| 18 |
{ id: 'memory', icon: Brain, labelEn: 'Memory', labelMy: 'မှတ်ဉာဏ်' },
|
| 19 |
{ id: 'connectors', icon: Plug, labelEn: 'Connectors', labelMy: 'ချိတ်ဆက်မှု' },
|
| 20 |
+
{ id: 'ai_router', icon: Cpu, labelEn: 'AI Router', labelMy: 'AI Router', badge: 'v8' },
|
| 21 |
]
|
| 22 |
|
| 23 |
const AGENT_META: Record<string, { icon: React.ElementType; color: string; label: string; isNew?: boolean }> = {
|
|
|
|
| 129 |
<div className="flex items-center gap-2 px-2 py-1.5 rounded-lg text-[10px]"
|
| 130 |
style={{ background: 'rgba(99,102,241,0.08)', border: '1px solid rgba(99,102,241,0.2)' }}>
|
| 131 |
<Bot size={10} className="text-indigo-400" />
|
| 132 |
+
<span style={{ color: 'var(--text-muted)' }}>God Agent OS v8.0</span>
|
| 133 |
<div className="ml-auto w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
|
| 134 |
</div>
|
| 135 |
</div>
|
frontend/hooks/useAgentStore.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { nanoid } from './nanoid'
|
|
| 9 |
export type Theme = 'dark' | 'light' | 'amoled' | 'neon' | 'glass'
|
| 10 |
export type Locale = 'en' | 'my'
|
| 11 |
export type AgentName = 'chat' | 'planner' | 'coding' | 'debug' | 'memory' | 'connector' | 'deploy' | 'workflow' | 'sandbox' | 'ui' | 'browser' | 'file' | 'git' | 'test' | 'vision' | 'reasoning'
|
| 12 |
-
export type ActivePanel = 'timeline' | 'tasks' | 'memory' | 'connectors' | 'sandbox' | 'files' | 'browser'
|
| 13 |
|
| 14 |
export interface Message {
|
| 15 |
id: string
|
|
|
|
| 9 |
export type Theme = 'dark' | 'light' | 'amoled' | 'neon' | 'glass'
|
| 10 |
export type Locale = 'en' | 'my'
|
| 11 |
export type AgentName = 'chat' | 'planner' | 'coding' | 'debug' | 'memory' | 'connector' | 'deploy' | 'workflow' | 'sandbox' | 'ui' | 'browser' | 'file' | 'git' | 'test' | 'vision' | 'reasoning'
|
| 12 |
+
export type ActivePanel = 'timeline' | 'tasks' | 'memory' | 'connectors' | 'sandbox' | 'files' | 'browser' | 'ai_router'
|
| 13 |
|
| 14 |
export interface Message {
|
| 15 |
id: string
|