349 lines
10 KiB
Python
349 lines
10 KiB
Python
"""
|
||
SDLC Agent Demo - FastAPI 主服务 (纯同步版本)
|
||
多智能体端到端软件交付协同系统
|
||
"""
|
||
|
||
import json
|
||
import uuid
|
||
import threading
|
||
from typing import Dict, Optional, Generator
|
||
from datetime import datetime
|
||
from fastapi import FastAPI, HTTPException
|
||
from fastapi.staticfiles import StaticFiles
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import StreamingResponse, RedirectResponse, JSONResponse
|
||
from pydantic import BaseModel, Field
|
||
import uvicorn
|
||
import time
|
||
|
||
from crews.sdlc_crew import SDLCCrew
|
||
from models.qwen_config import get_qwen_config
|
||
|
||
# ========== FastAPI 应用初始化 ==========
|
||
app = FastAPI(
|
||
title="SDLC Agent Demo",
|
||
description="多智能体端到端软件交付协同系统 - 基于 CrewAI + Qwen3.5-flash",
|
||
version="1.0.0"
|
||
)
|
||
|
||
# 启用 CORS
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# 挂载静态文件目录
|
||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||
|
||
|
||
# ========== 数据模型 ==========
|
||
class StartRequest(BaseModel):
|
||
"""启动请求模型"""
|
||
requirement: str = Field(..., description="用户需求描述", min_length=10)
|
||
|
||
|
||
# ========== 任务管理(内存存储) ==========
|
||
class TaskManager:
|
||
"""任务管理器 - 负责任务状态持久化"""
|
||
|
||
def __init__(self):
|
||
self.tasks: Dict[str, Dict] = {}
|
||
self._lock = threading.Lock()
|
||
|
||
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
|
||
|
||
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()
|
||
|
||
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()
|
||
|
||
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
|
||
|
||
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:]]
|
||
|
||
|
||
# 全局任务管理器
|
||
task_manager = TaskManager()
|
||
|
||
|
||
# ========== API 端点 ==========
|
||
@app.post("/api/v1/sdlc/start", response_model=Dict[str, str])
|
||
async def start_sdlc_process(request: StartRequest):
|
||
"""
|
||
启动 SDLC 流程(后台线程执行)
|
||
"""
|
||
# 验证配置
|
||
try:
|
||
get_qwen_config()
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
# 创建任务
|
||
task_id = task_manager.create_task(request.requirement)
|
||
|
||
# 在后台线程中执行 SDLC 流程
|
||
thread = threading.Thread(
|
||
target=execute_sdlc_flow,
|
||
args=(task_id, request.requirement),
|
||
daemon=True
|
||
)
|
||
thread.start()
|
||
|
||
return {
|
||
"task_id": task_id,
|
||
"status": "processing"
|
||
}
|
||
|
||
|
||
def execute_sdlc_flow(task_id: str, requirement: str):
|
||
"""
|
||
同步执行 SDLC 流程(在后台线程中运行)
|
||
"""
|
||
task_manager.update_task_status(task_id, "processing")
|
||
|
||
try:
|
||
crew = SDLCCrew()
|
||
|
||
# 同步执行并收集所有事件
|
||
for event in crew.execute_sync(requirement):
|
||
task_manager.add_event(task_id, event)
|
||
|
||
# 标记完成
|
||
task_manager.update_task_status(task_id, "completed")
|
||
|
||
except Exception as e:
|
||
task_manager.update_task_status(task_id, "failed")
|
||
task_manager.add_event(task_id, {
|
||
"event": "error",
|
||
"data": {
|
||
"error": str(e),
|
||
"timestamp": datetime.now().isoformat()
|
||
}
|
||
})
|
||
|
||
|
||
@app.get("/api/v1/sdlc/stream/{task_id}")
|
||
def stream_task_progress(task_id: str):
|
||
"""
|
||
SSE流式输出任务进度(同步生成器)
|
||
"""
|
||
# 验证任务存在
|
||
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)
|
||
|
||
return StreamingResponse(
|
||
event_generator(),
|
||
media_type="text/event-stream",
|
||
headers={
|
||
"Cache-Control": "no-cache",
|
||
"Connection": "keep-alive",
|
||
"X-Accel-Buffering": "no"
|
||
}
|
||
)
|
||
|
||
|
||
@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")
|
||
|
||
return {
|
||
"task_id": task["task_id"],
|
||
"status": task["status"],
|
||
"created_at": task["created_at"],
|
||
"updated_at": task["updated_at"],
|
||
"events_count": len(task["events"])
|
||
}
|
||
|
||
|
||
@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
|
||
|
||
|
||
@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"
|
||
}
|
||
)
|
||
|
||
|
||
@app.get("/")
|
||
def root():
|
||
"""根路径重定向到测试页面"""
|
||
return RedirectResponse(url="/static/index.html")
|
||
|
||
|
||
@app.get("/health")
|
||
def health_check():
|
||
"""健康检查端点"""
|
||
return {"status": "healthy", "version": "1.0.0"}
|
||
|
||
|
||
# ========== 主程序入口 ==========
|
||
if __name__ == "__main__":
|
||
uvicorn.run(
|
||
"main:app",
|
||
host="0.0.0.0",
|
||
port=8000,
|
||
reload=True,
|
||
log_level="info"
|
||
)
|