|
|
using AI.Interface;
|
|
|
using AI.Models;
|
|
|
using System;
|
|
|
using System.Diagnostics;
|
|
|
using System.Text.Json;
|
|
|
using System.Text.RegularExpressions;
|
|
|
using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
namespace AI.Workflow
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Planner - 负责规划任务,将任务分解成步骤
|
|
|
/// </summary>
|
|
|
public class Planner
|
|
|
{
|
|
|
private readonly IChatBackend _chatBackend;
|
|
|
private readonly ChatSession _planningSession;
|
|
|
|
|
|
public Planner(IChatBackend chatBackend)
|
|
|
{
|
|
|
_chatBackend = chatBackend;
|
|
|
_planningSession = new ChatSession();
|
|
|
|
|
|
// 初始化规划提示词
|
|
|
_planningSession.AddSystemMessage(GetPlanningPrompt());
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 规划任务
|
|
|
/// </summary>
|
|
|
/// <param name="goal">任务目标</param>
|
|
|
/// <param name="cancellationToken">取消令牌</param>
|
|
|
/// <returns>规划结果</returns>
|
|
|
public async Task<Plan> PlanAsync(string goal, CancellationToken cancellationToken = default)
|
|
|
{
|
|
|
var plan = new Plan
|
|
|
{
|
|
|
Goal = goal
|
|
|
};
|
|
|
|
|
|
try
|
|
|
{
|
|
|
// 请求 Planner 生成规划
|
|
|
string planningPrompt = $"请为以下任务制定详细的执行计划:\n\n任务:{goal}\n\n请将任务分解为具体的步骤,每个步骤应该是可执行的、明确的。";
|
|
|
|
|
|
string response = await _chatBackend.AskAsync(planningPrompt, _planningSession, null, cancellationToken);
|
|
|
|
|
|
// 解析规划结果
|
|
|
ParsePlanFromResponse(response, plan);
|
|
|
|
|
|
Debug.WriteLine($"Planner 生成了 {plan.Steps.Count} 个步骤");
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
Debug.WriteLine($"Planner 规划失败: {ex.Message}");
|
|
|
throw;
|
|
|
}
|
|
|
|
|
|
return plan;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 从响应中解析规划步骤
|
|
|
/// </summary>
|
|
|
private void ParsePlanFromResponse(string response, Plan plan)
|
|
|
{
|
|
|
// 尝试解析 JSON 格式
|
|
|
if (TryParseJsonPlan(response, plan))
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 尝试解析列表格式(1. 步骤1 2. 步骤2 ...)
|
|
|
if (TryParseListPlan(response, plan))
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 如果都解析失败,将整个响应作为单个步骤
|
|
|
plan.Steps.Add(new PlanStep
|
|
|
{
|
|
|
Order = 1,
|
|
|
Description = response.Trim()
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 尝试解析 JSON 格式的规划
|
|
|
/// </summary>
|
|
|
private bool TryParseJsonPlan(string response, Plan plan)
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
// 尝试提取 JSON 部分
|
|
|
var jsonMatch = Regex.Match(response, @"\{[\s\S]*\}");
|
|
|
if (!jsonMatch.Success)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
var json = jsonMatch.Value;
|
|
|
var doc = JsonDocument.Parse(json);
|
|
|
var root = doc.RootElement;
|
|
|
|
|
|
if (root.TryGetProperty("steps", out var stepsElement) && stepsElement.ValueKind == JsonValueKind.Array)
|
|
|
{
|
|
|
int order = 1;
|
|
|
foreach (var stepElement in stepsElement.EnumerateArray())
|
|
|
{
|
|
|
var step = new PlanStep
|
|
|
{
|
|
|
Order = order++,
|
|
|
Description = stepElement.GetProperty("description").GetString() ?? string.Empty
|
|
|
};
|
|
|
plan.Steps.Add(step);
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
catch
|
|
|
{
|
|
|
// JSON 解析失败,继续尝试其他格式
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 尝试解析列表格式的规划
|
|
|
/// </summary>
|
|
|
private bool TryParseListPlan(string response, Plan plan)
|
|
|
{
|
|
|
// 匹配各种列表格式:1. 2. 3. 或 - 或 * 等
|
|
|
var patterns = new[]
|
|
|
{
|
|
|
@"^\s*(\d+)\.\s+(.+)$", // 1. 步骤
|
|
|
@"^\s*[-*]\s+(.+)$", // - 步骤 或 * 步骤
|
|
|
@"^\s*(\d+)\)\s+(.+)$" // 1) 步骤
|
|
|
};
|
|
|
|
|
|
var lines = response.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
|
|
int order = 1;
|
|
|
|
|
|
foreach (var line in lines)
|
|
|
{
|
|
|
var trimmedLine = line.Trim();
|
|
|
if (string.IsNullOrWhiteSpace(trimmedLine))
|
|
|
continue;
|
|
|
|
|
|
foreach (var pattern in patterns)
|
|
|
{
|
|
|
var match = Regex.Match(trimmedLine, pattern);
|
|
|
if (match.Success)
|
|
|
{
|
|
|
string description = match.Groups.Count > 2 ? match.Groups[2].Value : match.Groups[1].Value;
|
|
|
plan.Steps.Add(new PlanStep
|
|
|
{
|
|
|
Order = order++,
|
|
|
Description = description.Trim()
|
|
|
});
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return plan.Steps.Count > 0;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 获取规划提示词
|
|
|
/// </summary>
|
|
|
private string GetPlanningPrompt()
|
|
|
{
|
|
|
return @"你是一个专业的任务规划专家,擅长将复杂任务分解为清晰、可执行的步骤序列。
|
|
|
|
|
|
【核心职责】
|
|
|
将用户提出的任务目标分解为一系列有序、可执行的步骤,确保每个步骤都有明确的目标和可衡量的结果。
|
|
|
|
|
|
【规划原则】
|
|
|
1. **原子性**:每个步骤应该是独立的、可单独执行的最小任务单元
|
|
|
2. **顺序性**:步骤之间必须有清晰的逻辑顺序和依赖关系
|
|
|
3. **可执行性**:每个步骤的描述应该具体、明确,能够直接指导执行
|
|
|
4. **完整性**:确保所有必要的步骤都被包含,不遗漏关键环节
|
|
|
5. **可验证性**:每个步骤完成后应该有明确的验证标准
|
|
|
|
|
|
【步骤描述要求】
|
|
|
- 使用动词开头,明确表达要执行的动作(如:""加载文件""、""验证数据""、""生成报告"")
|
|
|
- 包含必要的上下文信息(如:""从指定路径加载 XYZ 文件"")
|
|
|
- 避免模糊表述,使用具体、可操作的语言
|
|
|
- 每个步骤描述控制在 20-50 字之间
|
|
|
|
|
|
【输出格式】
|
|
|
优先使用 JSON 格式,结构如下:
|
|
|
{
|
|
|
""steps"": [
|
|
|
{""description"": ""步骤1的详细描述""},
|
|
|
{""description"": ""步骤2的详细描述""},
|
|
|
...
|
|
|
]
|
|
|
}
|
|
|
|
|
|
如果无法返回 JSON,请使用编号列表格式:
|
|
|
1. 步骤1的详细描述
|
|
|
2. 步骤2的详细描述
|
|
|
...
|
|
|
|
|
|
【注意事项】
|
|
|
- 不要创建过于细碎的步骤,也不要创建过于宏大的步骤
|
|
|
- 考虑步骤之间的依赖关系,确保顺序合理
|
|
|
- 如果任务涉及多个阶段,可以按阶段组织步骤
|
|
|
- 对于需要用户输入或确认的步骤,明确标注";
|
|
|
}
|
|
|
}
|
|
|
}
|