fix
This commit is contained in:
@@ -22,7 +22,6 @@ def create_dev_agent() -> Agent:
|
|||||||
1. 快速理解需求并设计合理的系统架构
|
1. 快速理解需求并设计合理的系统架构
|
||||||
2. 编写清晰、可维护、高性能的代码
|
2. 编写清晰、可维护、高性能的代码
|
||||||
3. 遵循 TDD(测试驱动开发)实践
|
3. 遵循 TDD(测试驱动开发)实践
|
||||||
4. 遵循博世研发规范中的编码标准
|
|
||||||
|
|
||||||
你的代码实现必须通过所有测试用例,并满足性能和安全要求。""",
|
你的代码实现必须通过所有测试用例,并满足性能和安全要求。""",
|
||||||
verbose=True,
|
verbose=True,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ def create_pm_agent() -> Agent:
|
|||||||
1. 快速理解用户业务场景和核心痛点
|
1. 快速理解用户业务场景和核心痛点
|
||||||
2. 识别功能性需求和非功能性需求
|
2. 识别功能性需求和非功能性需求
|
||||||
3. 定义清晰的验收标准 (Acceptance Criteria)
|
3. 定义清晰的验收标准 (Acceptance Criteria)
|
||||||
4. 遵循博世研发规范,确保需求的可追溯性和可验证性
|
|
||||||
|
|
||||||
你的输出将作为测试和开发团队的输入,务必保证准确性和完整性。""",
|
你的输出将作为测试和开发团队的输入,务必保证准确性和完整性。""",
|
||||||
verbose=True,
|
verbose=True,
|
||||||
@@ -75,7 +74,6 @@ def create_pm_task(requirement: str) -> Task:
|
|||||||
## 5. 约束条件
|
## 5. 约束条件
|
||||||
- 5.1 技术约束
|
- 5.1 技术约束
|
||||||
- 5.2 业务约束
|
- 5.2 业务约束
|
||||||
- 5.3 合规约束(博世研发规范)
|
|
||||||
|
|
||||||
【注意事项】
|
【注意事项】
|
||||||
- 使用清晰、无歧义的语言
|
- 使用清晰、无歧义的语言
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ def create_qa_agent() -> Agent:
|
|||||||
1. 基于需求文档设计全面的测试策略
|
1. 基于需求文档设计全面的测试策略
|
||||||
2. 编写高质量的 Pytest 自动化测试脚本
|
2. 编写高质量的 Pytest 自动化测试脚本
|
||||||
3. 覆盖单元测试、集成测试、端到端测试
|
3. 覆盖单元测试、集成测试、端到端测试
|
||||||
4. 遵循博世研发规范中的测试要求
|
|
||||||
|
|
||||||
你的测试用例将作为开发实现的验证标准,务必保证覆盖率和可执行性。""",
|
你的测试用例将作为开发实现的验证标准,务必保证覆盖率和可执行性。""",
|
||||||
verbose=True,
|
verbose=True,
|
||||||
|
|||||||
90
main.py
90
main.py
@@ -6,6 +6,8 @@ SDLC Agent Demo - FastAPI 主服务(异步版本)
|
|||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import asyncio
|
import asyncio
|
||||||
|
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
|
||||||
@@ -50,7 +52,7 @@ class TaskManager:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tasks: Dict[str, Dict] = {}
|
self.tasks: Dict[str, Dict] = {}
|
||||||
self.task_queues: Dict[str, asyncio.Queue] = {}
|
self.task_events: Dict[str, list] = {} # 存储任务的所有事件
|
||||||
|
|
||||||
def create_task(self, requirement: str) -> str:
|
def create_task(self, requirement: str) -> str:
|
||||||
"""创建新任务"""
|
"""创建新任务"""
|
||||||
@@ -62,8 +64,8 @@ class TaskManager:
|
|||||||
"created_at": datetime.now().isoformat(),
|
"created_at": datetime.now().isoformat(),
|
||||||
"updated_at": datetime.now().isoformat()
|
"updated_at": datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
# 创建异步队列用于 SSE 推送
|
# 创建事件列表用于存储所有事件
|
||||||
self.task_queues[task_id] = asyncio.Queue()
|
self.task_events[task_id] = []
|
||||||
return task_id
|
return task_id
|
||||||
|
|
||||||
def update_task_status(self, task_id: str, status: str):
|
def update_task_status(self, task_id: str, status: str):
|
||||||
@@ -72,22 +74,27 @@ class TaskManager:
|
|||||||
self.tasks[task_id]["status"] = status
|
self.tasks[task_id]["status"] = status
|
||||||
self.tasks[task_id]["updated_at"] = datetime.now().isoformat()
|
self.tasks[task_id]["updated_at"] = datetime.now().isoformat()
|
||||||
|
|
||||||
async def send_event(self, task_id: str, event: dict):
|
def add_event(self, task_id: str, event: dict):
|
||||||
"""发送事件到队列"""
|
"""添加事件到任务"""
|
||||||
if task_id in self.task_queues:
|
if task_id in self.task_events:
|
||||||
await self.task_queues[task_id].put(event)
|
self.task_events[task_id].append(event)
|
||||||
|
|
||||||
async def get_event(self, task_id: str, timeout: float = 60.0) -> Optional[dict]:
|
def get_events(self, task_id: str, last_index: int = 0) -> Dict:
|
||||||
"""从队列获取事件"""
|
"""获取任务的新事件"""
|
||||||
if task_id in self.task_queues:
|
if task_id not in self.task_events:
|
||||||
try:
|
return {"events": [], "has_more": False}
|
||||||
return await asyncio.wait_for(
|
|
||||||
self.task_queues[task_id].get(),
|
events = self.task_events[task_id]
|
||||||
timeout=timeout
|
new_events = events[last_index:]
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
task = self.tasks.get(task_id, {})
|
||||||
return None
|
has_more = task.get("status") not in ["completed", "failed", "pending"]
|
||||||
return None
|
|
||||||
|
return {
|
||||||
|
"events": new_events,
|
||||||
|
"has_more": has_more,
|
||||||
|
"status": task.get("status", "unknown")
|
||||||
|
}
|
||||||
|
|
||||||
def get_task(self, task_id: str) -> Optional[Dict]:
|
def get_task(self, task_id: str) -> Optional[Dict]:
|
||||||
"""获取任务信息"""
|
"""获取任务信息"""
|
||||||
@@ -102,7 +109,7 @@ task_manager = TaskManager()
|
|||||||
@app.post("/api/v1/sdlc/start", response_model=Dict[str, str])
|
@app.post("/api/v1/sdlc/start", response_model=Dict[str, str])
|
||||||
async def start_sdlc_process(request: StartRequest):
|
async def start_sdlc_process(request: StartRequest):
|
||||||
"""
|
"""
|
||||||
启动 SDLC 流程(异步执行)
|
启动 SDLC 流程(使用线程池异步执行)
|
||||||
"""
|
"""
|
||||||
# 验证配置
|
# 验证配置
|
||||||
try:
|
try:
|
||||||
@@ -113,8 +120,9 @@ async def start_sdlc_process(request: StartRequest):
|
|||||||
# 创建任务
|
# 创建任务
|
||||||
task_id = task_manager.create_task(request.requirement)
|
task_id = task_manager.create_task(request.requirement)
|
||||||
|
|
||||||
# 异步执行 SDLC 流程
|
# 在线程池中异步执行 SDLC 流程
|
||||||
asyncio.create_task(execute_sdlc_flow(task_id, request.requirement))
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_in_executor(executor, execute_sdlc_flow, task_id, request.requirement)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
@@ -122,9 +130,16 @@ async def start_sdlc_process(request: StartRequest):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def execute_sdlc_flow(task_id: str, requirement: str):
|
import threading
|
||||||
|
import asyncio
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
# 线程池
|
||||||
|
executor = ThreadPoolExecutor(max_workers=5)
|
||||||
|
|
||||||
|
def execute_sdlc_flow(task_id: str, requirement: str):
|
||||||
"""
|
"""
|
||||||
异步执行 SDLC 流程(使用 asyncio.to_thread 运行同步生成器)
|
在线程池中执行 SDLC 流程,将事件保存到任务列表
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
task_id: 任务 ID
|
task_id: 任务 ID
|
||||||
@@ -133,8 +148,8 @@ async def execute_sdlc_flow(task_id: str, requirement: str):
|
|||||||
task_manager.update_task_status(task_id, "processing")
|
task_manager.update_task_status(task_id, "processing")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 先发送一个任务启动事件
|
# 添加启动事件
|
||||||
await task_manager.send_event(task_id, {
|
task_manager.add_event(task_id, {
|
||||||
"event": "task_started",
|
"event": "task_started",
|
||||||
"data": {
|
"data": {
|
||||||
"status": "starting",
|
"status": "starting",
|
||||||
@@ -143,13 +158,13 @@ async def execute_sdlc_flow(task_id: str, requirement: str):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# 在线程池中执行同步生成器
|
# 直接执行 CrewAI (同步阻塞)
|
||||||
crew = SDLCCrew()
|
crew = SDLCCrew()
|
||||||
for event in crew.execute(requirement):
|
for event in crew.execute(requirement):
|
||||||
# 发送事件到队列
|
# 添加事件到任务
|
||||||
await task_manager.send_event(task_id, event)
|
task_manager.add_event(task_id, event)
|
||||||
|
|
||||||
# 如果是最终结果或错误,更新状态
|
# 更新状态
|
||||||
event_type = event.get("event", "")
|
event_type = event.get("event", "")
|
||||||
if event_type == "final_result":
|
if event_type == "final_result":
|
||||||
task_manager.update_task_status(task_id, "completed")
|
task_manager.update_task_status(task_id, "completed")
|
||||||
@@ -158,7 +173,7 @@ async def execute_sdlc_flow(task_id: str, requirement: str):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
task_manager.update_task_status(task_id, "failed")
|
task_manager.update_task_status(task_id, "failed")
|
||||||
await task_manager.send_event(task_id, {
|
task_manager.add_event(task_id, {
|
||||||
"event": "error",
|
"event": "error",
|
||||||
"data": {
|
"data": {
|
||||||
"error": str(e),
|
"error": str(e),
|
||||||
@@ -167,6 +182,19 @@ async def execute_sdlc_flow(task_id: str, requirement: str):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/sdlc/poll/{task_id}")
|
||||||
|
async def poll_task_events(task_id: str, last_index: int = 0):
|
||||||
|
"""
|
||||||
|
轮询任务事件(替代 SSE)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_id: 任务 ID
|
||||||
|
last_index: 最后已知的 event 索引
|
||||||
|
"""
|
||||||
|
result = task_manager.get_events(task_id, last_index)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/sdlc/stream/{task_id}")
|
@app.get("/api/v1/sdlc/stream/{task_id}")
|
||||||
async def stream_task_progress(task_id: str):
|
async def stream_task_progress(task_id: str):
|
||||||
"""
|
"""
|
||||||
@@ -316,7 +344,7 @@ if __name__ == "__main__":
|
|||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"main:app",
|
"main:app",
|
||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=8000,
|
port=8080,
|
||||||
reload=False,
|
reload=False,
|
||||||
log_level="info"
|
log_level="info"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -399,8 +399,8 @@
|
|||||||
// 初始化阶段
|
// 初始化阶段
|
||||||
this.initStages();
|
this.initStages();
|
||||||
|
|
||||||
// 连接 SSE
|
// 开始轮询任务事件
|
||||||
this.connectSSE(data.task_id);
|
this.connectPolling(data.task_id);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('启动失败:', error);
|
console.error('启动失败:', error);
|
||||||
@@ -423,88 +423,118 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接 SSE
|
* 轮询任务事件(替代 SSE)
|
||||||
*/
|
*/
|
||||||
connectSSE(taskId) {
|
connectPolling(taskId) {
|
||||||
const url = `/api/v1/sdlc/stream/${taskId}`;
|
|
||||||
|
|
||||||
this.connectionStatus = 'connecting';
|
this.connectionStatus = 'connecting';
|
||||||
this.addLog('system', 'SSE', `连接到:${url}`);
|
this.addLog('system', 'POLL', `开始轮询任务:${taskId}`);
|
||||||
|
|
||||||
this.eventSource = new EventSource(url);
|
let lastIndex = 0;
|
||||||
|
let pollCount = 0;
|
||||||
|
const maxPolls = 600; // 最多轮询 600 次 (10 分钟)
|
||||||
|
|
||||||
// 连接成功
|
const poll = () => {
|
||||||
this.eventSource.onopen = () => {
|
if (pollCount >= maxPolls) {
|
||||||
this.connectionStatus = 'connected';
|
this.addLog('system', 'POLL', '轮询超时');
|
||||||
this.addLog('system', 'SSE', '连接成功');
|
this.isProcessing = false;
|
||||||
|
this.connectionStatus = 'disconnected';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/api/v1/sdlc/poll/${taskId}?last_index=${lastIndex}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const { events, has_more, status } = data;
|
||||||
|
|
||||||
|
// 处理新事件
|
||||||
|
events.forEach(event => {
|
||||||
|
lastIndex++;
|
||||||
|
this.handleEvent(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否继续轮询
|
||||||
|
if (status === 'completed' || status === 'failed') {
|
||||||
|
this.isProcessing = false;
|
||||||
|
this.connectionStatus = 'disconnected';
|
||||||
|
this.addLog('system', 'POLL', `任务完成,状态:${status}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_more || events.length > 0) {
|
||||||
|
pollCount++;
|
||||||
|
setTimeout(poll, 500); // 每 500ms 轮询一次
|
||||||
|
} else if (status === 'processing') {
|
||||||
|
pollCount++;
|
||||||
|
setTimeout(poll, 1000); // 无新事件时 1 秒后再试
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('轮询失败:', err);
|
||||||
|
this.addLog('error', 'POLL', err.message);
|
||||||
|
pollCount++;
|
||||||
|
setTimeout(poll, 2000);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// PM 阶段
|
// 开始轮询
|
||||||
this.eventSource.addEventListener('pm_start', (event) => {
|
setTimeout(poll, 500);
|
||||||
const data = JSON.parse(event.data);
|
},
|
||||||
this.updateStageStatus('pm', 'processing');
|
|
||||||
this.addLog('pm_start', 'PM Agent', '开始需求分析...');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventSource.addEventListener('pm_complete', (event) => {
|
/**
|
||||||
const data = JSON.parse(event.data);
|
* 处理单个事件
|
||||||
this.updateStageStatus('pm', 'completed');
|
*/
|
||||||
this.addLog('pm_complete', 'PM Agent', '需求分析完成');
|
handleEvent(event) {
|
||||||
this.addResult('📋 软件需求规格说明书 (SRS)', data.content, data.timestamp);
|
const eventType = event.event;
|
||||||
});
|
const data = event.data;
|
||||||
|
|
||||||
// QA 阶段
|
switch(eventType) {
|
||||||
this.eventSource.addEventListener('qa_start', (event) => {
|
case 'task_started':
|
||||||
const data = JSON.parse(event.data);
|
this.addLog('task_started', 'System', data.message || '任务已启动');
|
||||||
this.updateStageStatus('qa', 'processing');
|
break;
|
||||||
this.addLog('qa_start', 'QA Agent', '开始测试用例设计...');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventSource.addEventListener('qa_complete', (event) => {
|
case 'pm_start':
|
||||||
const data = JSON.parse(event.data);
|
this.updateStageStatus('pm', 'processing');
|
||||||
this.updateStageStatus('qa', 'completed');
|
this.addLog('pm_start', 'PM Agent', '开始需求分析...');
|
||||||
this.addLog('qa_complete', 'QA Agent', '测试用例设计完成');
|
break;
|
||||||
this.addResult('🧪 测试方案与用例', data.content, data.timestamp);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dev 阶段
|
case 'pm_complete':
|
||||||
this.eventSource.addEventListener('dev_start', (event) => {
|
this.updateStageStatus('pm', 'completed');
|
||||||
const data = JSON.parse(event.data);
|
this.addLog('pm_complete', 'PM Agent', '需求分析完成');
|
||||||
this.updateStageStatus('dev', 'processing');
|
this.addResult('📋 软件需求规格说明书 (SRS)', data.content, data.timestamp);
|
||||||
this.addLog('dev_start', 'Dev Agent', '开始代码实现...');
|
break;
|
||||||
});
|
|
||||||
|
|
||||||
this.eventSource.addEventListener('dev_complete', (event) => {
|
case 'qa_start':
|
||||||
const data = JSON.parse(event.data);
|
this.updateStageStatus('qa', 'processing');
|
||||||
this.updateStageStatus('dev', 'completed');
|
this.addLog('qa_start', 'QA Agent', '开始测试用例设计...');
|
||||||
this.addLog('dev_complete', 'Dev Agent', '代码实现完成');
|
break;
|
||||||
this.addResult('💻 代码实现', data.content, data.timestamp);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 最终结果
|
case 'qa_complete':
|
||||||
this.eventSource.addEventListener('final_result', (event) => {
|
this.updateStageStatus('qa', 'completed');
|
||||||
const data = JSON.parse(event.data);
|
this.addLog('qa_complete', 'QA Agent', '测试用例设计完成');
|
||||||
this.updateStageStatus('final', 'completed');
|
this.addResult('🧪 测试方案与用例', data.content, data.timestamp);
|
||||||
this.addLog('final_result', 'System', 'SDLC 流程完成');
|
break;
|
||||||
this.isProcessing = false;
|
|
||||||
this.connectionStatus = 'disconnected';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 错误处理
|
case 'dev_start':
|
||||||
this.eventSource.addEventListener('error', (event) => {
|
this.updateStageStatus('dev', 'processing');
|
||||||
const data = JSON.parse(event.data);
|
this.addLog('dev_start', 'Dev Agent', '开始代码实现...');
|
||||||
this.addLog('error', 'Error', data.error || '未知错误');
|
break;
|
||||||
this.isProcessing = false;
|
|
||||||
this.connectionStatus = 'disconnected';
|
|
||||||
alert(`执行错误:${data.error}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 连接错误
|
case 'dev_complete':
|
||||||
this.eventSource.onerror = () => {
|
this.updateStageStatus('dev', 'completed');
|
||||||
this.addLog('system', 'SSE', '连接断开');
|
this.addLog('dev_complete', 'Dev Agent', '代码实现完成');
|
||||||
this.connectionStatus = 'disconnected';
|
this.addResult('💻 代码实现', data.content, data.timestamp);
|
||||||
this.eventSource.close();
|
break;
|
||||||
};
|
|
||||||
|
case 'final_result':
|
||||||
|
this.updateStageStatus('final', 'completed');
|
||||||
|
this.addLog('final_result', 'System', 'SDLC 流程完成');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
this.addLog('error', 'Error', data.error || '未知错误');
|
||||||
|
alert(`执行错误:${data.error}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
57
test_sse.py
Normal file
57
test_sse.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"""
|
||||||
|
SSE 测试脚本 - 验证事件流是否正确发送
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 设置控制台输出编码
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
# 启动任务
|
||||||
|
response = requests.post(
|
||||||
|
'http://localhost:8080/api/v1/sdlc/start',
|
||||||
|
json={'requirement': '测试需求:做一个简单的计算器'},
|
||||||
|
headers={'Content-Type': 'application/json'}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"Start failed: {response.status_code} - {response.text}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
task_data = response.json()
|
||||||
|
task_id = task_data['task_id']
|
||||||
|
print(f"Task started: {task_id}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 连接 SSE
|
||||||
|
sse_url = f'http://localhost:8080/api/v1/sdlc/stream/{task_id}'
|
||||||
|
print(f"Connecting SSE: {sse_url}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
with requests.get(sse_url, stream=True, timeout=300) as sse_response:
|
||||||
|
for line in sse_response.iter_lines():
|
||||||
|
if line:
|
||||||
|
line_str = line.decode('utf-8')
|
||||||
|
print(line_str)
|
||||||
|
|
||||||
|
# 解析事件
|
||||||
|
if line_str.startswith('event:'):
|
||||||
|
event_type = line_str[6:].strip()
|
||||||
|
print(f" -> Event type: {event_type}")
|
||||||
|
|
||||||
|
if line_str.startswith('data:'):
|
||||||
|
try:
|
||||||
|
data = json.loads(line_str[5:].strip())
|
||||||
|
print(f" -> Data: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 如果是结束事件,退出
|
||||||
|
if 'event: final_result' in line_str or 'event: error' in line_str:
|
||||||
|
print("=" * 60)
|
||||||
|
print("Task completed!")
|
||||||
|
break
|
||||||
|
|
||||||
|
print("Test completed")
|
||||||
Reference in New Issue
Block a user