Files
crewai/main.py

349 lines
10 KiB
Python
Raw Normal View History

2026-03-13 14:20:58 +08:00
"""
2026-03-13 18:12:31 +08:00
SDLC Agent Demo - FastAPI 主服务 (纯同步版本)
多智能体端到端软件交付协同系统
2026-03-13 14:20:58 +08:00
"""
import json
2026-03-13 18:12:31 +08:00
import uuid
import threading
from typing import Dict, Optional, Generator
2026-03-13 14:20:58 +08:00
from datetime import datetime
2026-03-13 18:12:31 +08:00
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
2026-03-13 14:20:58 +08:00
from fastapi.middleware.cors import CORSMiddleware
2026-03-13 18:12:31 +08:00
from fastapi.responses import StreamingResponse, RedirectResponse, JSONResponse
2026-03-13 14:20:58 +08:00
from pydantic import BaseModel, Field
2026-03-13 18:12:31 +08:00
import uvicorn
import time
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
from crews.sdlc_crew import SDLCCrew
from models.qwen_config import get_qwen_config
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# ========== FastAPI 应用初始化 ==========
app = FastAPI(
title="SDLC Agent Demo",
description="多智能体端到端软件交付协同系统 - 基于 CrewAI + Qwen3.5-flash",
version="1.0.0"
)
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# 启用 CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# 挂载静态文件目录
app.mount("/static", StaticFiles(directory="static"), name="static")
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# ========== 数据模型 ==========
class StartRequest(BaseModel):
"""启动请求模型"""
requirement: str = Field(..., description="用户需求描述", min_length=10)
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# ========== 任务管理(内存存储) ==========
class TaskManager:
"""任务管理器 - 负责任务状态持久化"""
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
def __init__(self):
self.tasks: Dict[str, Dict] = {}
self._lock = threading.Lock()
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
def create_task(self, requirement: str) -> str:
"""创建新任务"""
task_id = str(uuid.uuid4())
with self._lock:
self.tasks[task_id] = {
"task_id": task_id,
"requirement": requirement,
"status": "pending",
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat(),
"events": []
}
return task_id
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
def update_task_status(self, task_id: str, status: str):
"""更新任务状态"""
with self._lock:
if task_id in self.tasks:
self.tasks[task_id]["status"] = status
self.tasks[task_id]["updated_at"] = datetime.now().isoformat()
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
def add_event(self, task_id: str, event: dict):
"""添加事件"""
with self._lock:
if task_id in self.tasks:
self.tasks[task_id]["events"].append(event)
self.tasks[task_id]["updated_at"] = datetime.now().isoformat()
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
def get_task(self, task_id: str) -> Optional[Dict]:
"""获取任务信息"""
with self._lock:
return self.tasks.get(task_id).copy() if task_id in self.tasks else None
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
def get_events_after(self, task_id: str, last_index: int):
"""获取指定索引之后的事件"""
with self._lock:
if task_id not in self.tasks:
return []
events = self.tasks[task_id]["events"]
return [e.copy() for e in events[last_index:]]
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# 全局任务管理器
task_manager = TaskManager()
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# ========== API 端点 ==========
@app.post("/api/v1/sdlc/start", response_model=Dict[str, str])
async def start_sdlc_process(request: StartRequest):
"""
启动 SDLC 流程后台线程执行
2026-03-13 14:20:58 +08:00
"""
2026-03-13 18:12:31 +08:00
# 验证配置
try:
get_qwen_config()
except ValueError as e:
raise HTTPException(status_code=500, detail=str(e))
# 创建任务
task_id = task_manager.create_task(request.requirement)
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# 在后台线程中执行 SDLC 流程
thread = threading.Thread(
target=execute_sdlc_flow,
args=(task_id, request.requirement),
daemon=True
)
thread.start()
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
return {
"task_id": task_id,
"status": "processing"
}
def execute_sdlc_flow(task_id: str, requirement: str):
"""
同步执行 SDLC 流程在后台线程中运行
2026-03-13 14:20:58 +08:00
"""
2026-03-13 18:12:31 +08:00
task_manager.update_task_status(task_id, "processing")
2026-03-13 14:20:58 +08:00
try:
2026-03-13 18:12:31 +08:00
crew = SDLCCrew()
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# 同步执行并收集所有事件
for event in crew.execute_sync(requirement):
task_manager.add_event(task_id, event)
# 标记完成
task_manager.update_task_status(task_id, "completed")
2026-03-13 14:20:58 +08:00
except Exception as e:
2026-03-13 18:12:31 +08:00
task_manager.update_task_status(task_id, "failed")
task_manager.add_event(task_id, {
"event": "error",
"data": {
"error": str(e),
"timestamp": datetime.now().isoformat()
}
})
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
@app.get("/api/v1/sdlc/stream/{task_id}")
def stream_task_progress(task_id: str):
2026-03-13 14:20:58 +08:00
"""
2026-03-13 18:12:31 +08:00
SSE流式输出任务进度同步生成器
2026-03-13 14:20:58 +08:00
"""
2026-03-13 18:12:31 +08:00
# 验证任务存在
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
def event_generator():
"""生成 SSE事件同步"""
last_event_index = 0
max_wait_time = 300 # 最多等待 5 分钟
start_time = time.time()
while True:
# 检查超时
if time.time() - start_time > max_wait_time:
break
# 获取新事件
events = task_manager.get_events_after(task_id, last_event_index)
for event in events:
event_type = event.get("event", "message")
event_data = event.get("data", {})
yield f"event: {event_type}\ndata: {json.dumps(event_data, ensure_ascii=False)}\n\n"
last_event_index += 1
# 如果是结束事件,断开连接
if event_type in ["final_result", "error"]:
return
# 检查任务状态
task_data = task_manager.get_task(task_id)
if task_data and task_data["status"] in ["completed", "failed"]:
break
# 等待一下再检查
time.sleep(0.5)
2026-03-13 14:20:58 +08:00
return StreamingResponse(
2026-03-13 18:12:31 +08:00
event_generator(),
2026-03-13 14:20:58 +08:00
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
2026-03-13 18:12:31 +08:00
"X-Accel-Buffering": "no"
2026-03-13 14:20:58 +08:00
}
)
2026-03-13 18:12:31 +08:00
@app.get("/api/v1/sdlc/status/{task_id}")
def get_task_status(task_id: str):
"""
获取任务状态非流式
"""
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
2026-03-13 14:20:58 +08:00
return {
2026-03-13 18:12:31 +08:00
"task_id": task["task_id"],
"status": task["status"],
"created_at": task["created_at"],
"updated_at": task["updated_at"],
"events_count": len(task["events"])
2026-03-13 14:20:58 +08:00
}
2026-03-13 18:12:31 +08:00
@app.get("/api/v1/sdlc/result/{task_id}")
def get_task_result(task_id: str):
"""
获取任务完整结果
"""
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
if task["status"] != "completed":
raise HTTPException(
status_code=400,
detail=f"Task not completed yet. Status: {task['status']}"
)
return task
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
@app.get("/api/v1/sdlc/download/{task_id}")
def download_result(task_id: str):
"""
打包下载任务结果ZIP 文件
"""
import zipfile
import io
from fastapi.responses import StreamingResponse
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
if task["status"] != "completed":
raise HTTPException(
status_code=400,
detail=f"Task not completed yet. Status: {task['status']}"
)
# 创建 ZIP 文件
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
# 1. SRS 文档
srs_content = ""
for event in task["events"]:
if event["event"] == "pm_complete":
srs_content = event["data"].get("content", "")
break
zip_file.writestr("01_SRS_需求规格说明书.md", srs_content)
# 2. 测试用例
test_content = ""
for event in task["events"]:
if event["event"] == "qa_complete":
test_content = event["data"].get("content", "")
break
zip_file.writestr("02_Test_测试用例.md", test_content)
# 3. 代码实现
code_content = ""
for event in task["events"]:
if event["event"] == "dev_complete":
code_content = event["data"].get("content", "")
break
zip_file.writestr("03_Code_代码实现.md", code_content)
# 4. 项目摘要
summary = f"""# SDLC 项目交付摘要
## 项目信息
- 任务 ID: {task['task_id']}
- 创建时间{task['created_at']}
- 完成时间{task['updated_at']}
- 原始需求{task['requirement']}
## 交付物清单
1. 01_SRS_需求规格说明书.md - 软件需求规格说明书
2. 02_Test_测试用例.md - 测试方案与用例
3. 03_Code_代码实现.md - 业务代码实现
## 生成说明
本项目由 SDLC Agent Demo 自动生成
基于 CrewAI + Qwen3.5-flash + FastAPI
"""
zip_file.writestr("README_项目摘要.md", summary)
# 准备下载
zip_buffer.seek(0)
return StreamingResponse(
zip_buffer,
media_type="application/zip",
headers={
"Content-Disposition": f"attachment; filename=SDLC_Result_{task_id[:8]}.zip"
}
)
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
@app.get("/")
def root():
"""根路径重定向到测试页面"""
return RedirectResponse(url="/static/index.html")
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
@app.get("/health")
def health_check():
"""健康检查端点"""
return {"status": "healthy", "version": "1.0.0"}
2026-03-13 14:20:58 +08:00
2026-03-13 18:12:31 +08:00
# ========== 主程序入口 ==========
2026-03-13 14:20:58 +08:00
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
2026-03-13 18:12:31 +08:00
port=8000,
2026-03-13 14:20:58 +08:00
reload=True,
log_level="info"
)