diff --git a/.dockerignore b/.dockerignore index 6a0bed1..935f04f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -33,11 +33,11 @@ dist/ Dockerfile .dockerignore -# Documentation -README.md -*.md - # Test .pytest_cache/ .coverage htmlcov/ + +# Logs +logs/ +*.log diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..db1867b --- /dev/null +++ b/DOCKER.md @@ -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: + ``` diff --git a/Dockerfile b/Dockerfile index caa7fee..14264d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,36 @@ -# 多阶段构建 - SDLC Agent Demo -FROM python:3.11-slim as builder +# SDLC Agent Demo - Dockerfile +# 基于 CrewAI + Qwen3.5-flash + FastAPI 的多智能体软件交付系统 -# 设置工作目录 -WORKDIR /app +FROM python:3.11-slim # 设置环境变量 ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 - -# 安装依赖 -COPY requirements.txt . -RUN pip install --user --no-cache-dir -r requirements.txt - -# 运行阶段 -FROM python:3.11-slim + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PYTHONPATH=/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 中 -ENV PATH=/root/.local/bin:$PATH \ - PYTHONPATH=/app +# 安装 Python 依赖 +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 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 \ - CMD python -c "import httpx; httpx.get('http://localhost:8000/health')" || exit 1 +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + 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"] diff --git a/README.md b/README.md index 48f799d..01816b9 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,24 @@ uvicorn main:app --reload --host 0.0.0.0 --port 8000 打开浏览器访问: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. 构建镜像 @@ -106,16 +123,34 @@ docker build -t sdlc-agent-demo . #### 2. 运行容器 ```bash +# 使用 docker-compose (推荐) +docker-compose up -d + +# 或单独运行容器 docker run -d \ - -p 8000:8000 \ + -p 8080:8080 \ -e DASHSCOPE_API_KEY=your_api_key \ + -v $(pwd)/logs:/app/logs \ --name sdlc-demo \ sdlc-agent-demo ``` #### 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 接口 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..edea5fa --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/main.py b/main.py index d6604c8..b33b6c1 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ """ -SDLC Agent Demo - FastAPI 主服务(异步版本) +SDLC Agent Demo - FastAPI 主服务(轮询版本) 多智能体端到端软件交付协同系统 """ @@ -7,7 +7,6 @@ import json import uuid import asyncio import threading -import queue from typing import Dict, Optional, AsyncGenerator from datetime import datetime from fastapi import FastAPI, HTTPException @@ -16,6 +15,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse, RedirectResponse, JSONResponse from pydantic import BaseModel, Field import uvicorn +from concurrent.futures import ThreadPoolExecutor from crews.sdlc_crew import SDLCCrew from models.qwen_config import get_qwen_config @@ -27,7 +27,7 @@ app = FastAPI( version="1.0.0" ) -# 启用 CORS +# 添加 CORS 中间件 app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -104,6 +104,9 @@ class TaskManager: # 全局任务管理器 task_manager = TaskManager() +# 线程池 +executor = ThreadPoolExecutor(max_workers=5) + # ========== API 端点 ========== @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): """ 在线程池中执行 SDLC 流程,将事件保存到任务列表 @@ -195,42 +191,6 @@ async def poll_task_events(task_id: str, last_index: int = 0): 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}") 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("/") def root(): """根路径重定向到测试页面""" diff --git a/static/index.html b/static/index.html index 21a5318..2660e1b 100644 --- a/static/index.html +++ b/static/index.html @@ -226,21 +226,6 @@