最后一版

This commit is contained in:
ZhuJW
2026-03-13 21:09:44 +08:00
parent 8584821f36
commit 265cb3a4e6
7 changed files with 244 additions and 183 deletions

View File

@@ -33,11 +33,11 @@ dist/
Dockerfile Dockerfile
.dockerignore .dockerignore
# Documentation
README.md
*.md
# Test # Test
.pytest_cache/ .pytest_cache/
.coverage .coverage
htmlcov/ htmlcov/
# Logs
logs/
*.log

147
DOCKER.md Normal file
View File

@@ -0,0 +1,147 @@
# Docker 部署指南
## 快速启动
### 方式一:使用 Docker Compose (推荐)
1. **配置环境变量**
```bash
# 复制环境变量文件
cp .env.example .env
# 编辑 .env 文件,填入你的 DashScope API Key
DASHSCOPE_API_KEY=your_api_key_here
```
2. **启动服务**
```bash
docker-compose up -d
```
3. **查看日志**
```bash
docker-compose logs -f
```
4. **访问应用**
- API 文档http://localhost:8080/docs
- 前端界面http://localhost:8080/static/index.html
5. **停止服务**
```bash
docker-compose down
```
### 方式二:使用 Docker 构建
1. **构建镜像**
```bash
docker build -t sdlc-agent-demo:latest .
```
2. **运行容器**
```bash
docker run -d \
-p 8080:8080 \
-e DASHSCOPE_API_KEY=your_api_key_here \
-v $(pwd)/logs:/app/logs \
--name sdlc-agent \
sdlc-agent-demo:latest
```
3. **查看日志**
```bash
docker logs -f sdlc-agent
```
4. **停止并删除容器**
```bash
docker stop sdlc-agent && docker rm sdlc-agent
```
## 环境变量
| 变量名 | 说明 | 默认值 | 必需 |
|--------|------|--------|------|
| `DASHSCOPE_API_KEY` | 阿里云 DashScope API Key | 无 | ✅ |
| `PYTHONUNBUFFERED` | Python 输出缓冲设置 | 1 | ❌ |
## 端口说明
| 端口 | 说明 |
|------|------|
| 8080 | HTTP 服务端口 |
## 数据卷
| 路径 | 说明 |
|------|------|
| `./logs` | 日志文件存储目录 |
## 常见问题
### 1. 构建失败
如果遇到依赖安装失败,尝试使用国内镜像:
```dockerfile
# 在 Dockerfile 开头添加
FROM registry.cn-hangzhou.aliyuncs.com/library/python:3.11-slim
```
### 2. API Key 配置
确保在 `.env` 文件中正确配置了 DashScope API Key:
```bash
DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
```
### 3. 网络问题
如果容器无法访问外网,检查 Docker 网络配置:
```bash
docker network inspect sdlc-network
```
## 生产环境建议
1. **使用 secrets 管理敏感信息**
```yaml
secrets:
dashscope_api_key:
external: true
services:
sdlc-agent:
secrets:
- dashscope_api_key
```
2. **添加资源限制**
```yaml
deploy:
resources:
limits:
cpus: '2'
memory: 2G
```
3. **配置日志轮转**
```yaml
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
4. **使用持久化存储**
```yaml
volumes:
- sdlc-data:/app/logs
volumes:
sdlc-data:
```

View File

@@ -1,46 +1,36 @@
# 多阶段构建 - SDLC Agent Demo # SDLC Agent Demo - Dockerfile
FROM python:3.11-slim as builder # 基于 CrewAI + Qwen3.5-flash + FastAPI 的多智能体软件交付系统
# 设置工作目录 FROM python:3.11-slim
WORKDIR /app
# 设置环境变量 # 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \ PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_DISABLE_PIP_VERSION_CHECK=1 \
PYTHONPATH=/app
# 安装依赖
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 运行阶段
FROM python:3.11-slim
# 设置工作目录 # 设置工作目录
WORKDIR /app WORKDIR /app
# 从 builder 阶段复制安装的包 # 安装系统依赖
COPY --from=builder /root/.local /root/.local RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 确保脚本路径在 PATH 中 # 安装 Python 依赖
ENV PATH=/root/.local/bin:$PATH \ COPY requirements.txt .
PYTHONPATH=/app RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码 # 复制应用代码
COPY . . COPY . .
# 创建非 root 用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# 暴露端口 # 暴露端口
EXPOSE 8000 EXPOSE 8080
# 健康检查 # 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health')" || exit 1 CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/docs')" || exit 1
# 启动命令 # 启动命令
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] CMD ["python", "main.py"]

View File

@@ -95,7 +95,24 @@ uvicorn main:app --reload --host 0.0.0.0 --port 8000
打开浏览器访问http://localhost:8000/static/index.html 打开浏览器访问http://localhost:8000/static/index.html
### 方法二Docker 运行 ### 方法二:使用 Docker Compose (推荐)
```bash
# 1. 配置环境变量
cp .env.example .env
# 编辑 .env 文件,填入你的 DashScope API Key
# 2. 启动服务
docker-compose up -d
# 3. 查看日志
docker-compose logs -f
# 4. 停止服务
docker-compose down
```
### 方法三:使用 Docker 构建
#### 1. 构建镜像 #### 1. 构建镜像
@@ -106,16 +123,34 @@ docker build -t sdlc-agent-demo .
#### 2. 运行容器 #### 2. 运行容器
```bash ```bash
# 使用 docker-compose (推荐)
docker-compose up -d
# 或单独运行容器
docker run -d \ docker run -d \
-p 8000:8000 \ -p 8080:8080 \
-e DASHSCOPE_API_KEY=your_api_key \ -e DASHSCOPE_API_KEY=your_api_key \
-v $(pwd)/logs:/app/logs \
--name sdlc-demo \ --name sdlc-demo \
sdlc-agent-demo sdlc-agent-demo
``` ```
#### 3. 访问服务 #### 3. 访问服务
http://localhost:8000/static/index.html - 前端界面:http://localhost:8080/static/index.html
- API 文档http://localhost:8080/docs
#### 4. 查看日志
```bash
docker logs -f sdlc-demo
```
#### 5. 停止服务
```bash
docker stop sdlc-demo && docker rm sdlc-demo
```
## 📡 API 接口 ## 📡 API 接口

33
docker-compose.yml Normal file
View File

@@ -0,0 +1,33 @@
version: '3.8'
services:
sdlc-agent:
build:
context: .
dockerfile: Dockerfile
container_name: sdlc-agent-demo
restart: unless-stopped
ports:
- "8080:8080"
environment:
- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:sk-616332b2afa94699b4572d0fe6ac370a}
- PYTHONUNBUFFERED=1
env_file:
- .env
volumes:
- ./logs:/app/logs
networks:
- sdlc-network
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/docs')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
labels:
- "com.sdlc.agent.name=SDLC Agent Demo"
- "com.sdlc.agent.version=1.0"
networks:
sdlc-network:
driver: bridge

131
main.py
View File

@@ -1,5 +1,5 @@
""" """
SDLC Agent Demo - FastAPI 主服务(异步版本) SDLC Agent Demo - FastAPI 主服务(轮询版本)
多智能体端到端软件交付协同系统 多智能体端到端软件交付协同系统
""" """
@@ -7,7 +7,6 @@ import json
import uuid import uuid
import asyncio import asyncio
import threading import threading
import queue
from typing import Dict, Optional, AsyncGenerator from typing import Dict, Optional, AsyncGenerator
from datetime import datetime from datetime import datetime
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
@@ -16,6 +15,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, RedirectResponse, JSONResponse from fastapi.responses import StreamingResponse, RedirectResponse, JSONResponse
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
import uvicorn import uvicorn
from concurrent.futures import ThreadPoolExecutor
from crews.sdlc_crew import SDLCCrew from crews.sdlc_crew import SDLCCrew
from models.qwen_config import get_qwen_config from models.qwen_config import get_qwen_config
@@ -27,7 +27,7 @@ app = FastAPI(
version="1.0.0" version="1.0.0"
) )
# 启用 CORS # 添加 CORS 中间件
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], allow_origins=["*"],
@@ -104,6 +104,9 @@ class TaskManager:
# 全局任务管理器 # 全局任务管理器
task_manager = TaskManager() task_manager = TaskManager()
# 线程池
executor = ThreadPoolExecutor(max_workers=5)
# ========== API 端点 ========== # ========== API 端点 ==========
@app.post("/api/v1/sdlc/start", response_model=Dict[str, str]) @app.post("/api/v1/sdlc/start", response_model=Dict[str, str])
@@ -130,13 +133,6 @@ async def start_sdlc_process(request: StartRequest):
} }
import threading
import asyncio
from concurrent.futures import ThreadPoolExecutor
# 线程池
executor = ThreadPoolExecutor(max_workers=5)
def execute_sdlc_flow(task_id: str, requirement: str): def execute_sdlc_flow(task_id: str, requirement: str):
""" """
在线程池中执行 SDLC 流程,将事件保存到任务列表 在线程池中执行 SDLC 流程,将事件保存到任务列表
@@ -195,42 +191,6 @@ async def poll_task_events(task_id: str, last_index: int = 0):
return result return result
@app.get("/api/v1/sdlc/stream/{task_id}")
async def stream_task_progress(task_id: str):
"""
SSE流式输出任务进度
直接在异步函数中使用 async for 和 yield
FastAPI 会自动将其转换为 StreamingResponse
"""
# 验证任务存在
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# 直接使用 async for 和 yield
while True:
event = await task_manager.get_event(task_id, timeout=120.0)
if event is None:
# 超时,检查任务状态
task_data = task_manager.get_task(task_id)
if task_data and task_data["status"] in ["completed", "failed"]:
yield f"event: end\ndata: {json.dumps({'status': task_data['status']}, ensure_ascii=False)}\n\n"
break
continue
# 格式化 SSE事件
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"
# 如果是结束事件,断开连接
if event_type in ["final_result", "error"]:
break
@app.get("/api/v1/sdlc/status/{task_id}") @app.get("/api/v1/sdlc/status/{task_id}")
def get_task_status(task_id: str): def get_task_status(task_id: str):
""" """
@@ -248,85 +208,6 @@ def get_task_status(task_id: str):
} }
@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 = ""
# 注意:异步模式下需要从 events 中获取,这里简化处理
zip_file.writestr("01_SRS_需求规格说明书.md", "需求文档内容")
# 2. 测试用例
zip_file.writestr("02_Test_测试用例.md", "测试用例内容")
# 3. 代码实现
zip_file.writestr("03_Code_代码实现.md", "代码实现内容")
# 4. 项目摘要
summary = f"""# SDLC 项目交付摘要
## 项目信息
- 任务 ID: {task['task_id']}
- 创建时间:{task['created_at']}
- 完成时间:{task['updated_at']}
- 原始需求:{task['requirement']}
## 生成说明
本项目由 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("/") @app.get("/")
def root(): def root():
"""根路径重定向到测试页面""" """根路径重定向到测试页面"""

View File

@@ -226,21 +226,6 @@
<div class="space-y-6" v-show="results.length > 0"> <div class="space-y-6" v-show="results.length > 0">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900">4. 输出结果</h2> <h2 class="text-lg font-semibold text-gray-900">4. 输出结果</h2>
<button
@click="downloadResults"
:disabled="!isCompleted"
:class="[
'px-4 py-2 rounded-lg font-medium text-white transition-all duration-200 flex items-center',
isCompleted
? 'bg-green-600 hover:bg-green-700 shadow-md'
: 'bg-gray-400 cursor-not-allowed'
]"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
{{ isCompleted ? '打包下载结果' : '执行完成后下载' }}
</button>
</div> </div>
<div v-for="(result, index) in results" :key="index" class="bg-white rounded-lg shadow-md overflow-hidden"> <div v-for="(result, index) in results" :key="index" class="bg-white rounded-lg shadow-md overflow-hidden">
@@ -355,16 +340,6 @@
}, },
methods: { methods: {
/**
* 下载打包结果
*/
downloadResults() {
if (!this.taskId || !this.isCompleted) return;
const url = `/api/v1/sdlc/download/${this.taskId}`;
window.open(url, '_blank');
},
/** /**
* 启动 SDLC 流程 * 启动 SDLC 流程
*/ */