Remove standalone swarm mode surface
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
# Agent Manager Scope
|
||||||
|
|
||||||
|
This repository only owns **Heicode sub-mode runtime** behavior.
|
||||||
|
|
||||||
|
Rules for all files under this repository:
|
||||||
|
|
||||||
|
- Do not add or restore standalone swarm-mode product features.
|
||||||
|
- Treat `/api/swarms` as a **sub-mode compatibility API**, not a generic swarm product API.
|
||||||
|
- Do not add `/api/swarm/*` endpoints, swarm-only docs, or swarm-only tests.
|
||||||
|
- When refactoring, prefer names and comments that reflect **sub-mode runtime** ownership.
|
||||||
|
- If a feature belongs to the separate swarm system, remove or reject it here instead of integrating it.
|
||||||
-150
@@ -1,150 +0,0 @@
|
|||||||
# 蜂群模式快速开始
|
|
||||||
|
|
||||||
## 一句话方案
|
|
||||||
|
|
||||||
**将蜂群的去中心化任务分发、动态角色分配和自组织协作机制,通过在agent-manager中引入任务广播队列、agent能力注册表和竞价响应机制来实现,让agents像蜂群一样根据自身能力主动认领任务而非被动分配。**
|
|
||||||
|
|
||||||
## 实现方式
|
|
||||||
|
|
||||||
传统方式:Claude Code在本地启动子agent
|
|
||||||
新方案:Claude Code调用agent-manager API,在K8s上动态创建多个specialized agents协作编码
|
|
||||||
|
|
||||||
## 快速测试
|
|
||||||
|
|
||||||
### 1. 启动服务
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda activate data && python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 创建蜂群
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:8000/api/swarm/create \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"task_description": "实现简单的TODO API",
|
|
||||||
"agents": [
|
|
||||||
{"role": "architect", "template": "a2a_litellm_agent", "replicas": 1},
|
|
||||||
{"role": "coder", "template": "code_manager_agent", "replicas": 2},
|
|
||||||
{"role": "reviewer", "template": "a2a_litellm_agent", "replicas": 1}
|
|
||||||
],
|
|
||||||
"orchestration": {"strategy": "sequential"}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 查询状态
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl http://localhost:8000/api/swarm/{swarm_id}/status
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 监听结果(SSE)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -N http://localhost:8000/api/swarm/{swarm_id}/results
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 运行测试脚本
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python test_swarm_api.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心组件
|
|
||||||
|
|
||||||
### 数据库模型
|
|
||||||
- `Swarm`: 蜂群主表
|
|
||||||
- `SwarmAgent`: Agent实例
|
|
||||||
- `SwarmMessage`: 通信消息
|
|
||||||
|
|
||||||
### API端点
|
|
||||||
- `POST /api/swarm/create` - 创建蜂群
|
|
||||||
- `GET /api/swarm/{id}/status` - 查询状态
|
|
||||||
- `GET /api/swarm/{id}/results` - SSE流式结果
|
|
||||||
- `POST /api/swarm/{id}/stop` - 停止蜂群
|
|
||||||
- `GET /api/swarm/{id}/logs` - 获取日志
|
|
||||||
|
|
||||||
### 编排策略
|
|
||||||
- **Sequential**: architect → coder → reviewer(顺序执行)
|
|
||||||
- **Parallel**: 所有agents并行工作
|
|
||||||
- **Hybrid**: 混合模式
|
|
||||||
|
|
||||||
## 架构特点
|
|
||||||
|
|
||||||
1. **K8s原生**: 每个agent独立namespace和pod
|
|
||||||
2. **A2A通信**: 基于HTTP的agent间通信协议
|
|
||||||
3. **实时追踪**: SSE流式推送进度和结果
|
|
||||||
4. **灵活编排**: 支持多种协作策略
|
|
||||||
5. **资源隔离**: 独立的K8s资源和配额管理
|
|
||||||
|
|
||||||
## 下一步集成
|
|
||||||
|
|
||||||
### Claude Code MCP工具
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "create_code_swarm",
|
|
||||||
"description": "在K8s上创建Agent蜂群协作编码",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"task": {"type": "string"},
|
|
||||||
"agents": {"type": "array"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Claude Code调用
|
|
||||||
result = await mcp.call_tool("create_code_swarm", {
|
|
||||||
"task": "实现用户认证模块",
|
|
||||||
"agents": [
|
|
||||||
{"role": "architect", "model": "gpt-4"},
|
|
||||||
{"role": "coder", "model": "gpt-4", "replicas": 2}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
# 监听结果
|
|
||||||
async for event in mcp.stream_results(result["swarm_id"]):
|
|
||||||
if event["type"] == "code_diff":
|
|
||||||
apply_diff(event["diff"])
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术债务和改进
|
|
||||||
|
|
||||||
### 当前限制
|
|
||||||
1. Agent Pods实际部署需要K8s集群(当前为模拟)
|
|
||||||
2. 后台任务使用FastAPI BackgroundTasks(生产建议Celery)
|
|
||||||
3. 无认证授权机制
|
|
||||||
4. 无自动资源清理TTL
|
|
||||||
|
|
||||||
### 建议改进
|
|
||||||
1. 实现真实的K8s pod部署和监控
|
|
||||||
2. 添加认证和授权机制
|
|
||||||
3. 实现自动资源清理和TTL
|
|
||||||
4. 添加更多编排策略(如基于依赖的DAG执行)
|
|
||||||
5. 实现agent能力注册和任务竞价机制
|
|
||||||
|
|
||||||
## 文档
|
|
||||||
|
|
||||||
详细文档请参考:
|
|
||||||
- [SWARM_README.md](SWARM_README.md) - 完整文档
|
|
||||||
- [test_swarm_api.py](test_swarm_api.py) - API测试示例
|
|
||||||
|
|
||||||
## Git提交
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline feature/swarm-mode
|
|
||||||
# b4b20f0 feat: implement swarm mode for multi-agent collaboration
|
|
||||||
# 2657ef2 docs: add swarm mode documentation and test script
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**实现完成!** 🎉
|
|
||||||
|
|
||||||
核心功能已就绪,可以开始集成到Claude Code或进行进一步的测试和优化。
|
|
||||||
-247
@@ -1,247 +0,0 @@
|
|||||||
# Swarm Mode - 多Agent协作编码
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
Swarm Mode是agent-manager的多Agent协作功能,允许Claude Code通过API在K8s上创建多个specialized agents协作完成编码任务。
|
|
||||||
|
|
||||||
## 架构
|
|
||||||
|
|
||||||
```
|
|
||||||
Claude Code (本地)
|
|
||||||
↓ HTTP/MCP
|
|
||||||
Agent Manager (/api/swarm/*)
|
|
||||||
↓
|
|
||||||
Swarm Orchestrator (任务分解、分配、聚合)
|
|
||||||
↓
|
|
||||||
K8s Cluster (多个独立Namespace)
|
|
||||||
├─ architect-agent (设计)
|
|
||||||
├─ coder-agent-1 (实现)
|
|
||||||
├─ coder-agent-2 (实现)
|
|
||||||
└─ reviewer-agent (审查)
|
|
||||||
↓ A2A协议通信
|
|
||||||
结果聚合 → SSE Stream → Claude Code
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心功能
|
|
||||||
|
|
||||||
### 1. 数据库模型
|
|
||||||
|
|
||||||
- **Swarm**: 蜂群主表,存储任务描述、编排策略、状态等
|
|
||||||
- **SwarmAgent**: 蜂群中的Agent实例,存储角色、配置、K8s资源信息
|
|
||||||
- **SwarmMessage**: Agent间通信消息记录
|
|
||||||
|
|
||||||
### 2. API端点
|
|
||||||
|
|
||||||
#### POST /api/swarm/create
|
|
||||||
创建新的蜂群。
|
|
||||||
|
|
||||||
**请求示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"task_description": "实现用户认证模块",
|
|
||||||
"project_context": {
|
|
||||||
"repo_url": "https://github.com/user/project",
|
|
||||||
"branch": "feature/auth",
|
|
||||||
"language": "python",
|
|
||||||
"framework": "fastapi"
|
|
||||||
},
|
|
||||||
"agents": [
|
|
||||||
{
|
|
||||||
"role": "architect",
|
|
||||||
"template": "a2a_litellm_agent",
|
|
||||||
"model": "gpt-4",
|
|
||||||
"capabilities": ["design", "planning"],
|
|
||||||
"replicas": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "coder",
|
|
||||||
"template": "code_manager_agent",
|
|
||||||
"model": "gpt-4",
|
|
||||||
"capabilities": ["coding", "git"],
|
|
||||||
"replicas": 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"orchestration": {
|
|
||||||
"strategy": "sequential",
|
|
||||||
"max_iterations": 3,
|
|
||||||
"timeout_minutes": 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"swarm_id": "swm_a1b2c3d4e5f6",
|
|
||||||
"status": "initializing",
|
|
||||||
"agents": [
|
|
||||||
{
|
|
||||||
"agent_id": "agi_architect_001",
|
|
||||||
"role": "architect",
|
|
||||||
"status": "pending",
|
|
||||||
"namespace": "swarm-swm-a1b2c3-architect"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"created_at": "2026-05-17T10:30:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### GET /api/swarm/{swarm_id}/status
|
|
||||||
查询蜂群状态。
|
|
||||||
|
|
||||||
**响应示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"swarm_id": "swm_a1b2c3d4e5f6",
|
|
||||||
"status": "running",
|
|
||||||
"phase": "coding",
|
|
||||||
"progress": 65,
|
|
||||||
"agents": [...],
|
|
||||||
"metrics": {
|
|
||||||
"total_messages": 45,
|
|
||||||
"tokens_used": 125000,
|
|
||||||
"elapsed_seconds": 180
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### GET /api/swarm/{swarm_id}/results
|
|
||||||
SSE流式获取实时结果。
|
|
||||||
|
|
||||||
**事件类型:**
|
|
||||||
- `phase_change`: 阶段变更
|
|
||||||
- `agent_message`: Agent消息
|
|
||||||
- `artifact`: 生成的工件(代码、文档等)
|
|
||||||
- `code_diff`: 代码变更
|
|
||||||
- `result`: 最终结果
|
|
||||||
|
|
||||||
#### POST /api/swarm/{swarm_id}/stop
|
|
||||||
停止蜂群执行。
|
|
||||||
|
|
||||||
**请求示例:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"reason": "用户取消",
|
|
||||||
"cleanup": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### GET /api/swarm/{swarm_id}/logs
|
|
||||||
获取所有Agent的日志聚合。
|
|
||||||
|
|
||||||
### 3. 编排策略
|
|
||||||
|
|
||||||
- **sequential**: 顺序执行(architect → coder → reviewer)
|
|
||||||
- **parallel**: 并行执行(所有agents同时工作)
|
|
||||||
- **hybrid**: 混合模式(部分顺序、部分并行)
|
|
||||||
|
|
||||||
### 4. K8s资源管理
|
|
||||||
|
|
||||||
每个Agent运行在独立的K8s namespace中:
|
|
||||||
- Namespace命名:`swarm-{swarm_id_prefix}-{role}`
|
|
||||||
- Pod命名:`agent-{agent_id}`
|
|
||||||
- Service类型:ClusterIP(内部通信)
|
|
||||||
- 资源限制:CPU 100m-500m, Memory 256Mi-512Mi
|
|
||||||
|
|
||||||
### 5. Agent通信
|
|
||||||
|
|
||||||
Agents通过A2A协议(HTTP)直接通信:
|
|
||||||
- 消息格式:JSON-RPC 2.0
|
|
||||||
- 传输方式:HTTP POST
|
|
||||||
- 支持流式:SSE (Server-Sent Events)
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### Python客户端
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# 创建蜂群
|
|
||||||
response = requests.post("http://localhost:8000/api/swarm/create", json={
|
|
||||||
"task_description": "实现TODO API",
|
|
||||||
"agents": [
|
|
||||||
{"role": "architect", "template": "a2a_litellm_agent"},
|
|
||||||
{"role": "coder", "template": "code_manager_agent"}
|
|
||||||
],
|
|
||||||
"orchestration": {"strategy": "sequential"}
|
|
||||||
})
|
|
||||||
|
|
||||||
swarm_id = response.json()["swarm_id"]
|
|
||||||
|
|
||||||
# 监听结果
|
|
||||||
response = requests.get(
|
|
||||||
f"http://localhost:8000/api/swarm/{swarm_id}/results",
|
|
||||||
stream=True
|
|
||||||
)
|
|
||||||
|
|
||||||
for line in response.iter_lines():
|
|
||||||
if line.startswith(b'data: '):
|
|
||||||
event = json.loads(line[6:])
|
|
||||||
print(f"Event: {event['type']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试脚本
|
|
||||||
|
|
||||||
运行测试脚本验证API:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda activate data && python test_swarm_api.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
- **FastAPI**: REST API框架
|
|
||||||
- **SQLAlchemy**: ORM数据库访问
|
|
||||||
- **Kubernetes Python Client**: K8s资源管理
|
|
||||||
- **aiohttp**: 异步HTTP客户端
|
|
||||||
- **PostgreSQL**: 数据持久化
|
|
||||||
|
|
||||||
## 文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
agent-manager/
|
|
||||||
├── database.py # 新增Swarm相关模型
|
|
||||||
├── k8s_manager.py # 新增swarm方法
|
|
||||||
├── app.py # 注册swarm router
|
|
||||||
├── api/swarm/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── models.py # Pydantic请求/响应模型
|
|
||||||
│ ├── router.py # API端点
|
|
||||||
│ ├── orchestrator.py # 核心编排逻辑
|
|
||||||
│ └── agent_client.py # A2A客户端
|
|
||||||
└── test_swarm_api.py # 测试脚本
|
|
||||||
```
|
|
||||||
|
|
||||||
## 下一步
|
|
||||||
|
|
||||||
### Phase 5: Agent模板优化
|
|
||||||
- 优化a2a_litellm_agent支持swarm环境变量
|
|
||||||
- 添加状态上报机制
|
|
||||||
- 实现Agent间消息路由
|
|
||||||
|
|
||||||
### Phase 6: 集成测试
|
|
||||||
- 端到端测试完整流程
|
|
||||||
- 测试错误处理和重试
|
|
||||||
- 性能和资源测试
|
|
||||||
|
|
||||||
### Phase 7: Claude Code MCP集成
|
|
||||||
- 创建MCP server
|
|
||||||
- 定义swarm工具
|
|
||||||
- 实现流式结果返回
|
|
||||||
|
|
||||||
## 限制和注意事项
|
|
||||||
|
|
||||||
1. **当前实现为MVP版本**,Agent Pods实际部署需要K8s集群环境
|
|
||||||
2. **后台任务执行**使用FastAPI BackgroundTasks,生产环境建议使用Celery
|
|
||||||
3. **Agent间通信**假设所有agents在同一K8s集群内
|
|
||||||
4. **资源清理**需要手动调用stop接口,未实现自动TTL
|
|
||||||
5. **认证授权**未实现,需要在生产环境添加
|
|
||||||
|
|
||||||
## 贡献
|
|
||||||
|
|
||||||
欢迎提交Issue和Pull Request!
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
"""
|
"""Sub-mode runtime compatibility module."""
|
||||||
Swarm API module for multi-agent collaboration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .router import router
|
from .router import swarms_router
|
||||||
|
|
||||||
__all__ = ["router"]
|
__all__ = ["swarms_router"]
|
||||||
|
|||||||
+53
-12
@@ -1,14 +1,12 @@
|
|||||||
"""
|
"""Pydantic models for sub-mode runtime compatibility requests and responses."""
|
||||||
Pydantic models for Swarm API requests and responses.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional, Dict, Any
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class AgentConfig(BaseModel):
|
class AgentConfig(BaseModel):
|
||||||
"""Agent configuration for swarm"""
|
"""Agent configuration for sub-mode runtime execution."""
|
||||||
role: str = Field(..., description="Agent role (architect/coder/reviewer/tester)")
|
role: str = Field(..., description="Agent role (architect/coder/reviewer/tester)")
|
||||||
template: str = Field(default="a2a_litellm_agent", description="Agent template name")
|
template: str = Field(default="a2a_litellm_agent", description="Agent template name")
|
||||||
model: str = Field(default="gpt-4", description="Model name")
|
model: str = Field(default="gpt-4", description="Model name")
|
||||||
@@ -16,6 +14,19 @@ class AgentConfig(BaseModel):
|
|||||||
system_prompt: Optional[str] = Field(None, description="Custom system prompt")
|
system_prompt: Optional[str] = Field(None, description="Custom system prompt")
|
||||||
replicas: int = Field(default=1, description="Number of agent replicas")
|
replicas: int = Field(default=1, description="Number of agent replicas")
|
||||||
|
|
||||||
|
@model_validator(mode="before")
|
||||||
|
@classmethod
|
||||||
|
def normalize_sub_mode_agent(cls, data: Any) -> Any:
|
||||||
|
"""Accept Manager sub-mode agent fields when /api/swarms is used."""
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return data
|
||||||
|
data = dict(data)
|
||||||
|
if not data.get("role"):
|
||||||
|
data["role"] = data.get("role_template") or data.get("target_role") or "worker"
|
||||||
|
if not data.get("model"):
|
||||||
|
data["model"] = data.get("default_model_id") or data.get("model_ref") or "gpt-4"
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class OrchestrationConfig(BaseModel):
|
class OrchestrationConfig(BaseModel):
|
||||||
"""Orchestration configuration"""
|
"""Orchestration configuration"""
|
||||||
@@ -26,20 +37,29 @@ class OrchestrationConfig(BaseModel):
|
|||||||
|
|
||||||
class ProjectContext(BaseModel):
|
class ProjectContext(BaseModel):
|
||||||
"""Project context information"""
|
"""Project context information"""
|
||||||
|
model_config = ConfigDict(extra="allow")
|
||||||
|
|
||||||
repo_url: Optional[str] = Field(None, description="Repository URL")
|
repo_url: Optional[str] = Field(None, description="Repository URL")
|
||||||
branch: Optional[str] = Field(None, description="Git branch")
|
branch: Optional[str] = Field(None, description="Git branch")
|
||||||
language: Optional[str] = Field(None, description="Programming language")
|
language: Optional[str] = Field(None, description="Programming language")
|
||||||
framework: Optional[str] = Field(None, description="Framework")
|
framework: Optional[str] = Field(None, description="Framework")
|
||||||
|
intent_id: Optional[str] = None
|
||||||
|
template_hint: Optional[str] = None
|
||||||
|
sub_mode: Optional[str] = None
|
||||||
|
agile_context: Optional[Dict[str, Any]] = None
|
||||||
|
correlation_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class CallbackConfig(BaseModel):
|
class CallbackConfig(BaseModel):
|
||||||
"""Callback configuration"""
|
"""Callback configuration"""
|
||||||
url: str = Field(..., description="Callback URL")
|
url: str = Field(..., description="Callback URL")
|
||||||
method: str = Field(default="POST", description="HTTP method")
|
method: str = Field(default="POST", description="HTTP method")
|
||||||
|
signing_secret_ref: Optional[str] = Field(None, description="Azure Key Vault reference for callback HMAC signing")
|
||||||
|
subscribed_events: Optional[List[str]] = Field(None, description="Runtime event types subscribed by Manager")
|
||||||
|
|
||||||
|
|
||||||
class SwarmCreateRequest(BaseModel):
|
class SwarmCreateRequest(BaseModel):
|
||||||
"""Request model for creating a swarm"""
|
"""Request model for creating a sub-mode runtime run."""
|
||||||
task_description: str = Field(..., description="Task description")
|
task_description: str = Field(..., description="Task description")
|
||||||
project_context: Optional[ProjectContext] = Field(None, description="Project context")
|
project_context: Optional[ProjectContext] = Field(None, description="Project context")
|
||||||
agents: List[AgentConfig] = Field(..., description="Agent configurations")
|
agents: List[AgentConfig] = Field(..., description="Agent configurations")
|
||||||
@@ -49,7 +69,7 @@ class SwarmCreateRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class SwarmAgentInfo(BaseModel):
|
class SwarmAgentInfo(BaseModel):
|
||||||
"""Swarm agent information"""
|
"""Sub-mode runtime agent information."""
|
||||||
agent_id: str
|
agent_id: str
|
||||||
role: str
|
role: str
|
||||||
status: str
|
status: str
|
||||||
@@ -60,7 +80,8 @@ class SwarmAgentInfo(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class SwarmCreateResponse(BaseModel):
|
class SwarmCreateResponse(BaseModel):
|
||||||
"""Response model for swarm creation"""
|
"""Response model for sub-mode runtime creation."""
|
||||||
|
deployment_id: Optional[str] = None
|
||||||
swarm_id: str
|
swarm_id: str
|
||||||
status: str
|
status: str
|
||||||
agents: List[SwarmAgentInfo]
|
agents: List[SwarmAgentInfo]
|
||||||
@@ -69,14 +90,15 @@ class SwarmCreateResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class SwarmMetrics(BaseModel):
|
class SwarmMetrics(BaseModel):
|
||||||
"""Swarm metrics"""
|
"""Sub-mode runtime metrics."""
|
||||||
total_messages: int
|
total_messages: int
|
||||||
tokens_used: int
|
tokens_used: int
|
||||||
elapsed_seconds: int
|
elapsed_seconds: int
|
||||||
|
|
||||||
|
|
||||||
class SwarmStatusResponse(BaseModel):
|
class SwarmStatusResponse(BaseModel):
|
||||||
"""Response model for swarm status"""
|
"""Response model for sub-mode runtime status."""
|
||||||
|
deployment_id: Optional[str] = None
|
||||||
swarm_id: str
|
swarm_id: str
|
||||||
status: str
|
status: str
|
||||||
phase: Optional[str] = None
|
phase: Optional[str] = None
|
||||||
@@ -85,16 +107,35 @@ class SwarmStatusResponse(BaseModel):
|
|||||||
metrics: SwarmMetrics
|
metrics: SwarmMetrics
|
||||||
artifacts: List[Dict[str, Any]] = []
|
artifacts: List[Dict[str, Any]] = []
|
||||||
error_message: Optional[str] = None
|
error_message: Optional[str] = None
|
||||||
|
created_at: Optional[datetime] = None
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
class SwarmStopRequest(BaseModel):
|
class SwarmStopRequest(BaseModel):
|
||||||
"""Request model for stopping a swarm"""
|
"""Request model for stopping a sub-mode runtime run."""
|
||||||
reason: Optional[str] = Field(None, description="Reason for stopping")
|
reason: Optional[str] = Field(None, description="Reason for stopping")
|
||||||
cleanup: bool = Field(default=True, description="Whether to cleanup K8s resources")
|
cleanup: bool = Field(default=True, description="Whether to cleanup K8s resources")
|
||||||
|
|
||||||
|
|
||||||
class SwarmStopResponse(BaseModel):
|
class SwarmStopResponse(BaseModel):
|
||||||
"""Response model for swarm stop"""
|
"""Response model for stopping a sub-mode runtime run."""
|
||||||
|
deployment_id: Optional[str] = None
|
||||||
swarm_id: str
|
swarm_id: str
|
||||||
status: str
|
status: str
|
||||||
stopped_at: datetime
|
stopped_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class ApprovalDecisionRequest(BaseModel):
|
||||||
|
"""Manager approval decision for a paused high-risk Runtime action."""
|
||||||
|
approval_id: str
|
||||||
|
decision: str = Field(..., description="approved or rejected")
|
||||||
|
manager_deployment_id: Optional[str] = None
|
||||||
|
runtime_deployment_id: Optional[str] = None
|
||||||
|
operation: Optional[str] = None
|
||||||
|
resource_id: Optional[str] = None
|
||||||
|
resource_type: Optional[str] = None
|
||||||
|
target_role: Optional[str] = None
|
||||||
|
requires_credential: bool = False
|
||||||
|
credential_ref: Optional[str] = None
|
||||||
|
lease_id: Optional[str] = None
|
||||||
|
lease_expires_at: Optional[int] = None
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Sub-mode runtime orchestrator."""
|
||||||
Swarm Orchestrator - Core logic for multi-agent collaboration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
@@ -29,7 +27,7 @@ PHASE_MAP = {
|
|||||||
|
|
||||||
|
|
||||||
class SwarmOrchestrator:
|
class SwarmOrchestrator:
|
||||||
"""Swarm orchestrator for managing multi-agent collaboration"""
|
"""Runtime orchestrator for Heicode sub-mode execution."""
|
||||||
|
|
||||||
def __init__(self, swarm_id: str, db: Session):
|
def __init__(self, swarm_id: str, db: Session):
|
||||||
"""
|
"""
|
||||||
|
|||||||
+7
-161
@@ -1,14 +1,10 @@
|
|||||||
"""
|
"""Sub-mode runtime compatibility router."""
|
||||||
Swarm API router - REST endpoints for swarm management.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import AsyncGenerator, Dict, Any
|
from typing import Dict, Any
|
||||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Request
|
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Request
|
||||||
from fastapi.responses import StreamingResponse
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from database import (
|
from database import (
|
||||||
@@ -22,7 +18,6 @@ from .models import (
|
|||||||
)
|
)
|
||||||
from .orchestrator import SwarmOrchestrator
|
from .orchestrator import SwarmOrchestrator
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/swarm", tags=["swarm"])
|
|
||||||
swarms_router = APIRouter(prefix="/api/swarms", tags=["swarms"])
|
swarms_router = APIRouter(prefix="/api/swarms", tags=["swarms"])
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +49,7 @@ def _agent_infos_for_swarm(db: Session, swarm_id: str) -> list[SwarmAgentInfo]:
|
|||||||
|
|
||||||
|
|
||||||
def _build_swarm_status_response(db: Session, swarm: Swarm) -> SwarmStatusResponse:
|
def _build_swarm_status_response(db: Session, swarm: Swarm) -> SwarmStatusResponse:
|
||||||
"""Return a standard status payload for /api/swarm and /api/swarms."""
|
"""Return a standard status payload for the sub-mode runtime compatibility API."""
|
||||||
elapsed_seconds = int((datetime.utcnow() - swarm.created_at).total_seconds())
|
elapsed_seconds = int((datetime.utcnow() - swarm.created_at).total_seconds())
|
||||||
return SwarmStatusResponse(
|
return SwarmStatusResponse(
|
||||||
deployment_id=swarm.swarm_id,
|
deployment_id=swarm.swarm_id,
|
||||||
@@ -100,13 +95,7 @@ def _stop_swarm_record(db: Session, swarm_id: str, request: SwarmStopRequest) ->
|
|||||||
|
|
||||||
|
|
||||||
async def initialize_and_execute_swarm(swarm_id: str, db_url: str):
|
async def initialize_and_execute_swarm(swarm_id: str, db_url: str):
|
||||||
"""
|
"""Background task to initialize and execute a sub-mode runtime run."""
|
||||||
Background task to initialize and execute swarm.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
swarm_id: Swarm ID
|
|
||||||
db_url: Database URL for creating new session
|
|
||||||
"""
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
@@ -133,23 +122,12 @@ async def initialize_and_execute_swarm(swarm_id: str, db_url: str):
|
|||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create", response_model=SwarmCreateResponse)
|
|
||||||
async def create_swarm(
|
async def create_swarm(
|
||||||
request: SwarmCreateRequest,
|
request: SwarmCreateRequest,
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""
|
"""Create a new sub-mode runtime run."""
|
||||||
Create a new swarm.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: Swarm creation request
|
|
||||||
background_tasks: FastAPI background tasks
|
|
||||||
db: Database session
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Swarm creation response
|
|
||||||
"""
|
|
||||||
swarm_id = generate_swarm_id()
|
swarm_id = generate_swarm_id()
|
||||||
|
|
||||||
project_context = request.project_context.dict() if request.project_context else {}
|
project_context = request.project_context.dict() if request.project_context else {}
|
||||||
@@ -228,12 +206,7 @@ async def create_swarm_compat(
|
|||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""Compatibility entrypoint for Heicode sub-mode Runtime adapters.
|
"""Compatibility entrypoint for Heicode sub-mode Runtime adapters."""
|
||||||
|
|
||||||
Accepts the Manager's structured orchestration_plan payload and maps it to
|
|
||||||
the existing swarm creation path without forcing Manager to call
|
|
||||||
/api/swarm/create directly.
|
|
||||||
"""
|
|
||||||
if payload.get("dry_run") is True:
|
if payload.get("dry_run") is True:
|
||||||
raise HTTPException(status_code=422, detail="dry_run is not supported by Runtime create; no swarm was created")
|
raise HTTPException(status_code=422, detail="dry_run is not supported by Runtime create; no swarm was created")
|
||||||
|
|
||||||
@@ -318,25 +291,6 @@ async def get_swarm_detail_compat(swarm_id: str, db: Session = Depends(get_db)):
|
|||||||
return _build_swarm_status_response(db, swarm)
|
return _build_swarm_status_response(db, swarm)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{swarm_id}/status", response_model=SwarmStatusResponse)
|
|
||||||
async def get_swarm_status(swarm_id: str, db: Session = Depends(get_db)):
|
|
||||||
"""
|
|
||||||
Get swarm status.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
swarm_id: Swarm ID
|
|
||||||
db: Database session
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Swarm status response
|
|
||||||
"""
|
|
||||||
swarm = db.query(Swarm).filter(Swarm.swarm_id == swarm_id).first()
|
|
||||||
if not swarm:
|
|
||||||
raise HTTPException(status_code=404, detail="Swarm not found")
|
|
||||||
|
|
||||||
return _build_swarm_status_response(db, swarm)
|
|
||||||
|
|
||||||
|
|
||||||
@swarms_router.get("/{swarm_id}/status", response_model=SwarmStatusResponse)
|
@swarms_router.get("/{swarm_id}/status", response_model=SwarmStatusResponse)
|
||||||
async def get_swarm_status_compat(swarm_id: str, db: Session = Depends(get_db)):
|
async def get_swarm_status_compat(swarm_id: str, db: Session = Depends(get_db)):
|
||||||
"""Compatibility status endpoint for Manager Runtime bridge."""
|
"""Compatibility status endpoint for Manager Runtime bridge."""
|
||||||
@@ -346,104 +300,6 @@ async def get_swarm_status_compat(swarm_id: str, db: Session = Depends(get_db)):
|
|||||||
return _build_swarm_status_response(db, swarm)
|
return _build_swarm_status_response(db, swarm)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{swarm_id}/results")
|
|
||||||
async def stream_swarm_results(swarm_id: str, db: Session = Depends(get_db)):
|
|
||||||
"""
|
|
||||||
Stream swarm results via SSE.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
swarm_id: Swarm ID
|
|
||||||
db: Database session
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
SSE stream
|
|
||||||
"""
|
|
||||||
swarm = db.query(Swarm).filter(Swarm.swarm_id == swarm_id).first()
|
|
||||||
if not swarm:
|
|
||||||
raise HTTPException(status_code=404, detail="Swarm not found")
|
|
||||||
|
|
||||||
async def event_generator() -> AsyncGenerator[str, None]:
|
|
||||||
last_message_id = 0
|
|
||||||
last_phase = None
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Refresh swarm status
|
|
||||||
db.refresh(swarm)
|
|
||||||
|
|
||||||
# Send phase change event
|
|
||||||
if swarm.phase != last_phase:
|
|
||||||
last_phase = swarm.phase
|
|
||||||
event = {
|
|
||||||
"type": "phase_change",
|
|
||||||
"phase": swarm.phase,
|
|
||||||
"timestamp": datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
yield f"data: {json.dumps(event)}\n\n"
|
|
||||||
|
|
||||||
# Get new messages
|
|
||||||
messages = db.query(SwarmMessage).filter(
|
|
||||||
SwarmMessage.swarm_id == swarm_id,
|
|
||||||
SwarmMessage.id > last_message_id
|
|
||||||
).order_by(SwarmMessage.id).all()
|
|
||||||
|
|
||||||
for msg in messages:
|
|
||||||
last_message_id = msg.id
|
|
||||||
event = {
|
|
||||||
"type": "agent_message",
|
|
||||||
"message_id": msg.message_id,
|
|
||||||
"from_agent_id": msg.from_agent_id,
|
|
||||||
"to_agent_id": msg.to_agent_id,
|
|
||||||
"message_type": msg.message_type,
|
|
||||||
"content": msg.content,
|
|
||||||
"timestamp": msg.created_at.isoformat()
|
|
||||||
}
|
|
||||||
yield f"data: {json.dumps(event)}\n\n"
|
|
||||||
|
|
||||||
# Check if completed
|
|
||||||
if swarm.status in [SwarmStatus.COMPLETED, SwarmStatus.FAILED, SwarmStatus.STOPPED]:
|
|
||||||
result_event = {
|
|
||||||
"type": "result",
|
|
||||||
"status": swarm.status.value,
|
|
||||||
"artifacts": swarm.artifacts,
|
|
||||||
"error_message": swarm.error_message,
|
|
||||||
"timestamp": datetime.utcnow().isoformat()
|
|
||||||
}
|
|
||||||
yield f"data: {json.dumps(result_event)}\n\n"
|
|
||||||
break
|
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
return StreamingResponse(
|
|
||||||
event_generator(),
|
|
||||||
media_type="text/event-stream",
|
|
||||||
headers={
|
|
||||||
"Cache-Control": "no-cache",
|
|
||||||
"Connection": "keep-alive",
|
|
||||||
"X-Accel-Buffering": "no"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{swarm_id}/stop", response_model=SwarmStopResponse)
|
|
||||||
async def stop_swarm(
|
|
||||||
swarm_id: str,
|
|
||||||
request: SwarmStopRequest,
|
|
||||||
db: Session = Depends(get_db)
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Stop a swarm.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
swarm_id: Swarm ID
|
|
||||||
request: Stop request
|
|
||||||
db: Database session
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Stop response
|
|
||||||
"""
|
|
||||||
return _stop_swarm_record(db, swarm_id, request)
|
|
||||||
|
|
||||||
|
|
||||||
@swarms_router.post("/{swarm_id}/stop", response_model=SwarmStopResponse)
|
@swarms_router.post("/{swarm_id}/stop", response_model=SwarmStopResponse)
|
||||||
async def stop_swarm_compat(
|
async def stop_swarm_compat(
|
||||||
swarm_id: str,
|
swarm_id: str,
|
||||||
@@ -454,18 +310,8 @@ async def stop_swarm_compat(
|
|||||||
return _stop_swarm_record(db, swarm_id, request)
|
return _stop_swarm_record(db, swarm_id, request)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{swarm_id}/logs")
|
|
||||||
async def get_swarm_logs(swarm_id: str, db: Session = Depends(get_db)):
|
async def get_swarm_logs(swarm_id: str, db: Session = Depends(get_db)):
|
||||||
"""
|
"""Get aggregated logs from all agents in a sub-mode runtime run."""
|
||||||
Get aggregated logs from all agents in swarm.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
swarm_id: Swarm ID
|
|
||||||
db: Database session
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Aggregated logs
|
|
||||||
"""
|
|
||||||
swarm = db.query(Swarm).filter(Swarm.swarm_id == swarm_id).first()
|
swarm = db.query(Swarm).filter(Swarm.swarm_id == swarm_id).first()
|
||||||
if not swarm:
|
if not swarm:
|
||||||
raise HTTPException(status_code=404, detail="Swarm not found")
|
raise HTTPException(status_code=404, detail="Swarm not found")
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ app.include_router(external_tool_router)
|
|||||||
from api.agnet.router import router as agnet_router
|
from api.agnet.router import router as agnet_router
|
||||||
app.include_router(agnet_router)
|
app.include_router(agnet_router)
|
||||||
|
|
||||||
# 注册 Swarm API Router
|
# 注册 Heicode sub-mode Runtime 兼容 Router
|
||||||
from api.swarm.router import router as swarm_router
|
from api.swarm.router import swarms_router
|
||||||
app.include_router(swarm_router)
|
app.include_router(swarms_router)
|
||||||
|
|
||||||
# 初始化K8s管理器
|
# 初始化K8s管理器
|
||||||
NAMESPACE = os.getenv("NAMESPACE", "ai-agents")
|
NAMESPACE = os.getenv("NAMESPACE", "ai-agents")
|
||||||
|
|||||||
+5
-5
@@ -378,7 +378,7 @@ class AuditLog(Base):
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Swarm Mode Models (NEW)
|
# Sub-mode Runtime Internal Models
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
class SwarmStatus(str, enum.Enum):
|
class SwarmStatus(str, enum.Enum):
|
||||||
@@ -399,7 +399,7 @@ class SwarmAgentStatus(str, enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Swarm(Base):
|
class Swarm(Base):
|
||||||
"""Swarm model for multi-agent collaboration"""
|
"""Internal runtime run model used by Heicode sub-mode execution."""
|
||||||
__tablename__ = "swarms"
|
__tablename__ = "swarms"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
@@ -445,7 +445,7 @@ class Swarm(Base):
|
|||||||
|
|
||||||
|
|
||||||
class SwarmAgent(Base):
|
class SwarmAgent(Base):
|
||||||
"""Swarm agent model for agents in a swarm"""
|
"""Internal runtime agent model for sub-mode execution."""
|
||||||
__tablename__ = "swarm_agents"
|
__tablename__ = "swarm_agents"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
@@ -479,7 +479,7 @@ class SwarmAgent(Base):
|
|||||||
|
|
||||||
|
|
||||||
class SwarmMessage(Base):
|
class SwarmMessage(Base):
|
||||||
"""Swarm message model for agent-to-agent communication"""
|
"""Internal runtime message model for sub-mode execution."""
|
||||||
__tablename__ = "swarm_messages"
|
__tablename__ = "swarm_messages"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
@@ -491,7 +491,7 @@ class SwarmMessage(Base):
|
|||||||
to_agent_id = Column(String(100), index=True) # NULL for broadcast
|
to_agent_id = Column(String(100), index=True) # NULL for broadcast
|
||||||
message_type = Column(String(50)) # task, response, broadcast, artifact
|
message_type = Column(String(50)) # task, response, broadcast, artifact
|
||||||
content = Column(Text)
|
content = Column(Text)
|
||||||
metadata = Column(JSON)
|
message_metadata = Column("metadata", JSON)
|
||||||
|
|
||||||
# Timestamp
|
# Timestamp
|
||||||
created_at = Column(DateTime, default=datetime.utcnow, index=True)
|
created_at = Column(DateTime, default=datetime.utcnow, index=True)
|
||||||
|
|||||||
+1
-1
@@ -1306,7 +1306,7 @@ class K8sManager:
|
|||||||
raise Exception(f"列出Pod失败: {e.reason}")
|
raise Exception(f"列出Pod失败: {e.reason}")
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Swarm Mode Methods (NEW)
|
# Sub-mode runtime helper methods
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def create_swarm_namespace(self, swarm_id: str, role: str) -> str:
|
def create_swarm_namespace(self, swarm_id: str, role: str) -> str:
|
||||||
|
|||||||
@@ -1,232 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test script for Swarm API endpoints.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
|
|
||||||
BASE_URL = "http://localhost:8000"
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_swarm():
|
|
||||||
"""Test creating a swarm"""
|
|
||||||
print("\n=== Testing Swarm Creation ===")
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"task_description": "实现用户认证模块,包括登录、注册、密码重置功能",
|
|
||||||
"project_context": {
|
|
||||||
"repo_url": "https://github.com/test/project",
|
|
||||||
"branch": "feature/auth",
|
|
||||||
"language": "python",
|
|
||||||
"framework": "fastapi"
|
|
||||||
},
|
|
||||||
"agents": [
|
|
||||||
{
|
|
||||||
"role": "architect",
|
|
||||||
"template": "a2a_litellm_agent",
|
|
||||||
"model": "gpt-4",
|
|
||||||
"capabilities": ["design", "planning"],
|
|
||||||
"system_prompt": "你是架构师,负责设计系统架构和API接口",
|
|
||||||
"replicas": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "coder",
|
|
||||||
"template": "code_manager_agent",
|
|
||||||
"model": "gpt-4",
|
|
||||||
"capabilities": ["coding", "git"],
|
|
||||||
"system_prompt": "你是开发工程师,负责实现代码",
|
|
||||||
"replicas": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "reviewer",
|
|
||||||
"template": "a2a_litellm_agent",
|
|
||||||
"model": "gpt-4",
|
|
||||||
"capabilities": ["review", "testing"],
|
|
||||||
"system_prompt": "你是代码审查员,负责审查代码质量",
|
|
||||||
"replicas": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"orchestration": {
|
|
||||||
"strategy": "sequential",
|
|
||||||
"max_iterations": 3,
|
|
||||||
"timeout_minutes": 30
|
|
||||||
},
|
|
||||||
"owner_id": "test_user"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{BASE_URL}/api/swarm/create", json=payload)
|
|
||||||
response.raise_for_status()
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
print(f"✅ Swarm created successfully!")
|
|
||||||
print(f" Swarm ID: {result['swarm_id']}")
|
|
||||||
print(f" Status: {result['status']}")
|
|
||||||
print(f" Agents: {len(result['agents'])}")
|
|
||||||
|
|
||||||
for agent in result['agents']:
|
|
||||||
print(f" - {agent['role']}: {agent['agent_id']} ({agent['status']})")
|
|
||||||
|
|
||||||
return result['swarm_id']
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"❌ Failed to create swarm: {e}")
|
|
||||||
if hasattr(e.response, 'text'):
|
|
||||||
print(f" Response: {e.response.text}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_swarm_status(swarm_id):
|
|
||||||
"""Test getting swarm status"""
|
|
||||||
print(f"\n=== Testing Swarm Status (ID: {swarm_id}) ===")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{BASE_URL}/api/swarm/{swarm_id}/status")
|
|
||||||
response.raise_for_status()
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
print(f"✅ Swarm status retrieved!")
|
|
||||||
print(f" Status: {result['status']}")
|
|
||||||
print(f" Phase: {result.get('phase', 'N/A')}")
|
|
||||||
print(f" Progress: {result['progress']}%")
|
|
||||||
print(f" Total Messages: {result['metrics']['total_messages']}")
|
|
||||||
print(f" Elapsed: {result['metrics']['elapsed_seconds']}s")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"❌ Failed to get swarm status: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def test_stream_swarm_results(swarm_id, duration=10):
|
|
||||||
"""Test streaming swarm results"""
|
|
||||||
print(f"\n=== Testing Swarm Results Stream (ID: {swarm_id}) ===")
|
|
||||||
print(f"Streaming for {duration} seconds...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
f"{BASE_URL}/api/swarm/{swarm_id}/results",
|
|
||||||
stream=True,
|
|
||||||
timeout=duration + 5
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
start_time = time.time()
|
|
||||||
event_count = 0
|
|
||||||
|
|
||||||
for line in response.iter_lines():
|
|
||||||
if time.time() - start_time > duration:
|
|
||||||
break
|
|
||||||
|
|
||||||
if line:
|
|
||||||
line = line.decode('utf-8')
|
|
||||||
if line.startswith('data: '):
|
|
||||||
event_count += 1
|
|
||||||
data = json.loads(line[6:])
|
|
||||||
event_type = data.get('type', 'unknown')
|
|
||||||
print(f" Event #{event_count}: {event_type}")
|
|
||||||
|
|
||||||
if event_type == 'phase_change':
|
|
||||||
print(f" Phase: {data.get('phase')}")
|
|
||||||
elif event_type == 'agent_message':
|
|
||||||
print(f" From: {data.get('from_agent_id', 'orchestrator')}")
|
|
||||||
print(f" To: {data.get('to_agent_id', 'orchestrator')}")
|
|
||||||
elif event_type == 'result':
|
|
||||||
print(f" Final Status: {data.get('status')}")
|
|
||||||
break
|
|
||||||
|
|
||||||
print(f"✅ Received {event_count} events")
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"❌ Failed to stream results: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def test_stop_swarm(swarm_id):
|
|
||||||
"""Test stopping a swarm"""
|
|
||||||
print(f"\n=== Testing Swarm Stop (ID: {swarm_id}) ===")
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"reason": "Test completed",
|
|
||||||
"cleanup": False # Don't cleanup for testing
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{BASE_URL}/api/swarm/{swarm_id}/stop", json=payload)
|
|
||||||
response.raise_for_status()
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
print(f"✅ Swarm stopped successfully!")
|
|
||||||
print(f" Status: {result['status']}")
|
|
||||||
print(f" Stopped at: {result['stopped_at']}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"❌ Failed to stop swarm: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_swarm_logs(swarm_id):
|
|
||||||
"""Test getting swarm logs"""
|
|
||||||
print(f"\n=== Testing Swarm Logs (ID: {swarm_id}) ===")
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{BASE_URL}/api/swarm/{swarm_id}/logs")
|
|
||||||
response.raise_for_status()
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
print(f"✅ Swarm logs retrieved!")
|
|
||||||
print(f" Agents: {len(result.get('agents', []))}")
|
|
||||||
|
|
||||||
for agent in result.get('agents', []):
|
|
||||||
print(f" - {agent['role']} ({agent['agent_id']})")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"❌ Failed to get swarm logs: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run all tests"""
|
|
||||||
print("=" * 60)
|
|
||||||
print("Swarm API Test Suite")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# Test 1: Create swarm
|
|
||||||
swarm_id = test_create_swarm()
|
|
||||||
if not swarm_id:
|
|
||||||
print("\n❌ Cannot continue tests without swarm_id")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Wait a bit for initialization
|
|
||||||
print("\nWaiting 2 seconds for initialization...")
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Test 2: Get status
|
|
||||||
test_get_swarm_status(swarm_id)
|
|
||||||
|
|
||||||
# Test 3: Stream results (for 10 seconds)
|
|
||||||
test_stream_swarm_results(swarm_id, duration=10)
|
|
||||||
|
|
||||||
# Test 4: Get logs
|
|
||||||
test_get_swarm_logs(swarm_id)
|
|
||||||
|
|
||||||
# Test 5: Stop swarm
|
|
||||||
test_stop_swarm(swarm_id)
|
|
||||||
|
|
||||||
# Final status check
|
|
||||||
print("\nFinal status check...")
|
|
||||||
test_get_swarm_status(swarm_id)
|
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
|
||||||
print("Test Suite Completed!")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user