Files
crewai/main.py
2026-03-13 18:12:31 +08:00

349 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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"
)