docs: add OpenClaw integration architecture
- OpenClawClient Python class - Agent model with status tracking - Task assignment flow - Callback endpoint
This commit is contained in:
parent
438a8aaa88
commit
bbbc65bc48
198
README.md
198
README.md
@ -9,7 +9,7 @@ API сервисы для Team Board. Python + FastAPI, микросервисн
|
|||||||
| gateway | 8000 | API Gateway, аутентификация |
|
| gateway | 8000 | API Gateway, аутентификация |
|
||||||
| projects | 8001 | Проекты, Git интеграция |
|
| projects | 8001 | Проекты, Git интеграция |
|
||||||
| tasks | 8002 | Задачи, канбан, подзадачи |
|
| tasks | 8002 | Задачи, канбан, подзадачи |
|
||||||
| agents | 8003 | AI агенты |
|
| agents | 8003 | AI агенты, OpenClaw интеграция |
|
||||||
| chat | 8004 | Чаты проектов |
|
| chat | 8004 | Чаты проектов |
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
@ -22,10 +22,196 @@ backend/
|
|||||||
│ ├── tasks/
|
│ ├── tasks/
|
||||||
│ ├── agents/
|
│ ├── agents/
|
||||||
│ └── chat/
|
│ └── chat/
|
||||||
|
├── shared/
|
||||||
|
│ └── openclaw_client.py
|
||||||
├── docker-compose.yml
|
├── docker-compose.yml
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## OpenClaw Integration
|
||||||
|
|
||||||
|
### Архитектура
|
||||||
|
|
||||||
|
```
|
||||||
|
Team Board (agents service)
|
||||||
|
│
|
||||||
|
│ HTTP POST /hooks/agent
|
||||||
|
▼
|
||||||
|
OpenClaw Gateway (localhost:18789)
|
||||||
|
│
|
||||||
|
│ sessions_spawn
|
||||||
|
▼
|
||||||
|
Субагенты (изолированные сессии)
|
||||||
|
│
|
||||||
|
│ результат
|
||||||
|
▼
|
||||||
|
Callback → Team Board
|
||||||
|
```
|
||||||
|
|
||||||
|
### Конфигурация
|
||||||
|
|
||||||
|
```env
|
||||||
|
# OpenClaw
|
||||||
|
OPENCLAW_URL=http://localhost:18789
|
||||||
|
OPENCLAW_TOKEN=team-board-secret-token
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenClaw Client
|
||||||
|
|
||||||
|
```python
|
||||||
|
# shared/openclaw_client.py
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class OpenClawClient:
|
||||||
|
def __init__(self, base_url: str, token: str):
|
||||||
|
self.base_url = base_url
|
||||||
|
self.headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def wake(self, text: str, mode: str = "now") -> dict:
|
||||||
|
"""Разбудить основную сессию"""
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{self.base_url}/hooks/wake",
|
||||||
|
headers=self.headers,
|
||||||
|
json={"text": text, "mode": mode}
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def run_agent(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
session_key: str,
|
||||||
|
agent_id: str = "main",
|
||||||
|
deliver: bool = False,
|
||||||
|
timeout: int = 300
|
||||||
|
) -> dict:
|
||||||
|
"""Запустить задачу в изолированной сессии"""
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{self.base_url}/hooks/agent",
|
||||||
|
headers=self.headers,
|
||||||
|
json={
|
||||||
|
"message": message,
|
||||||
|
"name": "TeamBoard",
|
||||||
|
"agentId": agent_id,
|
||||||
|
"sessionKey": session_key,
|
||||||
|
"deliver": deliver,
|
||||||
|
"timeoutSeconds": timeout
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def get_session_history(self, session_key: str) -> dict:
|
||||||
|
"""Получить историю сессии"""
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(
|
||||||
|
f"{self.base_url}/api/sessions/{session_key}/history",
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agents Service
|
||||||
|
|
||||||
|
### Модели
|
||||||
|
|
||||||
|
```python
|
||||||
|
# services/agents/models.py
|
||||||
|
|
||||||
|
from sqlalchemy import Column, String, Integer, Text, Boolean
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID, TIMESTAMPTZ
|
||||||
|
from shared.database import Base
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class Agent(Base):
|
||||||
|
__tablename__ = "agents"
|
||||||
|
|
||||||
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
name = Column(String(100), nullable=False)
|
||||||
|
slug = Column(String(50), unique=True, nullable=False)
|
||||||
|
|
||||||
|
# Настройки
|
||||||
|
model = Column(String(100), default="claude-opus-4-5")
|
||||||
|
system_prompt = Column(Text)
|
||||||
|
max_concurrent = Column(Integer, default=1)
|
||||||
|
|
||||||
|
# Статус
|
||||||
|
status = Column(String(20), default="idle")
|
||||||
|
current_tasks = Column(Integer, default=0)
|
||||||
|
is_enabled = Column(Boolean, default=True)
|
||||||
|
|
||||||
|
# Timestamps
|
||||||
|
created_at = Column(TIMESTAMPTZ, server_default="now()")
|
||||||
|
updated_at = Column(TIMESTAMPTZ, server_default="now()", onupdate="now()")
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
```python
|
||||||
|
# services/agents/routes.py
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from .models import Agent
|
||||||
|
from .schemas import AgentCreate, AgentUpdate, TaskAssignment
|
||||||
|
from shared.openclaw_client import OpenClawClient
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/agents", tags=["agents"])
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
async def list_agents():
|
||||||
|
"""Список всех агентов"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@router.post("/")
|
||||||
|
async def create_agent(data: AgentCreate):
|
||||||
|
"""Создать нового агента"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@router.put("/{agent_id}")
|
||||||
|
async def update_agent(agent_id: str, data: AgentUpdate):
|
||||||
|
"""Обновить агента"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@router.post("/{agent_id}/assign")
|
||||||
|
async def assign_task(agent_id: str, data: TaskAssignment):
|
||||||
|
"""Назначить задачу агенту"""
|
||||||
|
agent = await Agent.get(agent_id)
|
||||||
|
|
||||||
|
if not agent.is_enabled:
|
||||||
|
raise HTTPException(400, "Agent is disabled")
|
||||||
|
|
||||||
|
if agent.current_tasks >= agent.max_concurrent:
|
||||||
|
raise HTTPException(400, "Agent is busy")
|
||||||
|
|
||||||
|
# Запускаем через OpenClaw
|
||||||
|
client = OpenClawClient(settings.OPENCLAW_URL, settings.OPENCLAW_TOKEN)
|
||||||
|
|
||||||
|
result = await client.run_agent(
|
||||||
|
message=data.prompt,
|
||||||
|
session_key=f"task:{data.task_id}",
|
||||||
|
agent_id=agent.slug
|
||||||
|
)
|
||||||
|
|
||||||
|
# Обновляем статус
|
||||||
|
agent.current_tasks += 1
|
||||||
|
agent.status = "working"
|
||||||
|
await agent.save()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.post("/callback")
|
||||||
|
async def agent_callback(data: dict):
|
||||||
|
"""Callback от OpenClaw после выполнения задачи"""
|
||||||
|
session_key = data.get("sessionKey")
|
||||||
|
# Обновить задачу, уменьшить current_tasks агента
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -45,12 +231,22 @@ docker-compose up -d
|
|||||||
- PostgreSQL
|
- PostgreSQL
|
||||||
- Redis
|
- Redis
|
||||||
- SQLAlchemy
|
- SQLAlchemy
|
||||||
|
- httpx (async HTTP client)
|
||||||
|
|
||||||
## Переменные окружения
|
## Переменные окружения
|
||||||
|
|
||||||
```env
|
```env
|
||||||
|
# Database
|
||||||
DATABASE_URL=postgresql://team_board:password@localhost:5432/team_board
|
DATABASE_URL=postgresql://team_board:password@localhost:5432/team_board
|
||||||
|
|
||||||
|
# Redis
|
||||||
REDIS_URL=redis://localhost:6379
|
REDIS_URL=redis://localhost:6379
|
||||||
|
|
||||||
|
# Auth
|
||||||
AUTHENTIK_CLIENT_ID=...
|
AUTHENTIK_CLIENT_ID=...
|
||||||
AUTHENTIK_CLIENT_SECRET=...
|
AUTHENTIK_CLIENT_SECRET=...
|
||||||
|
|
||||||
|
# OpenClaw
|
||||||
|
OPENCLAW_URL=http://localhost:18789
|
||||||
|
OPENCLAW_TOKEN=team-board-secret-token
|
||||||
```
|
```
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user