openai-compatible-task-master
Version:
使用MCP解析PRD文档并生成任务列表
135 lines (129 loc) • 6.14 kB
JavaScript
import { callOpenAIAPI, cleanJsonContent } from './llm_utils.js';
import chalk from 'chalk';
// 解析子任务JSON响应
export function parseSubTasksResponse(jsonContent, parentTask) {
const cleanedContent = cleanJsonContent(jsonContent);
try {
// 尝试将内容解析为JSON
const data = JSON.parse(cleanedContent);
if (!data.subTasks || !Array.isArray(data.subTasks)) {
throw new Error('JSON响应格式不正确,无法找到子任务列表');
}
// 验证和处理子任务数据
const subTasks = data.subTasks.map((task, index) => {
// 验证必要字段
if (!task.title || typeof task.title !== 'string') {
throw new Error(`子任务响应格式不正确: 第${index + 1}个子任务缺少title字符串`);
}
if (!task.description || typeof task.description !== 'string') {
throw new Error(`子任务响应格式不正确: 第${index + 1}个子任务缺少description字符串`);
}
if (!task.details || typeof task.details !== 'string') {
throw new Error(`子任务响应格式不正确: 第${index + 1}个子任务缺少details字符串`);
}
if (!task.testStrategy || typeof task.testStrategy !== 'string') {
throw new Error(`子任务响应格式不正确: 第${index + 1}个子任务缺少testStrategy字符串`);
}
// 验证优先级
if (!['high', 'medium', 'low'].includes(String(task.priority).toLowerCase())) {
console.warn(chalk.yellow(`子任务 ${index + 1} 的优先级 "${task.priority}" 无效,将默认为 "medium"`));
task.priority = 'medium';
}
// 生成子任务ID(复合ID)
const subTaskId = `${parentTask.id}.${index + 1}`;
// 返回规范化的子任务数据
return {
id: subTaskId,
title: String(task.title),
description: String(task.description),
status: 'pending',
dependencies: task.dependencies ? task.dependencies.map(dep => `${parentTask.id}.${dep}`) : [],
priority: String(task.priority).toLowerCase(),
details: String(task.details),
testStrategy: String(task.testStrategy),
parentId: String(parentTask.id) // 设置父任务ID
};
});
return subTasks;
}
catch (error) {
const err = error;
console.debug(chalk.blue("原始JSON内容:"), cleanedContent); // 记录清理后的JSON以供调试
throw new Error(`LLM返回的子任务内容无法解析: ${err.message}`);
}
}
// 调用OpenAI Compatible API将任务分解为子任务
export async function breakupTaskWithLLM(parentTask, prompt, openaiUrl, apiKey, model, streamMode, maxRetries = 2) {
if (!apiKey) {
throw new Error('API密钥未提供,请使用 --api-key 参数提供有效的API密钥');
}
// 构建系统提示
const systemPrompt = `你是一个AI助手,负责将软件开发任务分解为更小的子任务。
你将收到一个父任务和一个提示,描述如何将该任务分解为子任务。
你的工作是创建一组结构良好的子任务,这些子任务共同完成父任务的目标。
指南:
1. 每个子任务应该是原子性的,专注于单一职责
2. 子任务应该遵循与父任务相同的数据结构
3. 子任务应该有明确的实现细节和测试策略
4. 子任务应该按照逻辑顺序排列
5. 为每个子任务分配适当的优先级(high/medium/low)
6. 子任务可以有依赖关系,但依赖应该是其他子任务的索引(从1开始)
期望的输出格式:
{
"subTasks": [
{
"title": "子任务标题",
"description": "子任务描述",
"priority": "high|medium|low",
"dependencies": [子任务依赖索引数组,如 ["1", "2"],若无依赖则为空数组 []],
"details": "实现细节",
"testStrategy": "验证方法"
},
... // 其他子任务
]
}
重要: 你的回复必须仅包含有效的JSON,不要添加任何额外的解释、评论或代码块标记。必须使用简体中文回复。`;
// 构建请求体
const messages = [
{
role: 'system',
content: systemPrompt,
},
{
role: 'user',
content: `这是需要分解的父任务:
任务ID: ${parentTask.id}
标题: ${parentTask.title}
描述: ${parentTask.description}
优先级: ${parentTask.priority}
详细信息: ${parentTask.details}
测试策略: ${parentTask.testStrategy}
分解提示:
${prompt || "请将此任务分解为更小的子任务,每个子任务应该专注于单一职责,并且可以独立完成。"}
请根据上述信息将任务分解为子任务。`,
},
];
// 实现重试机制
let lastError = null;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const jsonContent = await callOpenAIAPI(messages, openaiUrl, apiKey, model, streamMode);
return parseSubTasksResponse(jsonContent, parentTask);
}
catch (error) {
lastError = error;
console.warn(chalk.yellow(`尝试 ${attempt + 1}/${maxRetries + 1} 失败: ${lastError.message}`));
// 最后一次尝试失败,抛出错误
if (attempt === maxRetries) {
throw new Error(`分解任务失败,已重试${maxRetries}次: ${lastError.message}`);
}
// 等待一段时间后重试
const delay = Math.pow(2, attempt) * 1000; // 指数退避策略
console.info(chalk.blue(`等待 ${delay / 1000} 秒后重试...`));
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// 这行代码理论上不会执行,但为了类型安全添加
throw new Error(`分解任务失败: ${lastError?.message || '未知错误'}`);
}
//# sourceMappingURL=llm_breakup_task.js.map