# 研发工单 v1.3.0 - 03-03 AI 任务拆分服务 > 父工单:[03-会议任务拆分](./03-会议任务拆分.md) > > 依赖:03-01(API 契约)、工单 02(`LlmClientService`) --- ## 一、目标 实现基于 LLM 的会议内容分析服务:接收会议纪要文字,通过 LLM prompt 工程提取行动项,生成结构化的待办项建议列表(含标题、优先级、理由)。 ## 二、范围 **包含**: - `MeetingAiBreakdownService`:会议文本 → LLM → 结构化建议列表 - 会议拆分专用 prompt 设计与迭代 - AI 拆分 API 端点:`POST /api/meeting/{taskId}/breakdown` - 批量创建确认端点:`POST /api/meeting/{taskId}/breakdown/confirm` - 在线可用性检测(离线时拒绝 AI 拆分请求) - 复用工单 02 的 `LlmClientService` **不包含**: - 单任务拆分(属于工单 02 `AI_BREAKDOWN` 意图) - LLM 基础设施搭建(由工单 02 提供) - 前端审阅 UI(由 03-04 完成) ## 三、前置条件 | 条件 | 说明 | |---|---| | 03-01 完成 | `MeetingController` 骨架就绪 | | 工单 02 `LlmClientService` | LLM 调用封装可用 | ## 四、详细规格 ### 4.1 MeetingAiBreakdownService ```csharp /// 会议 AI 拆分服务,将会议文字内容分析为待办项建议 public class MeetingAiBreakdownService { private readonly ILlmClientService _llmClient; private readonly MeetingService _meetingService; private readonly ITaskService _taskService; public MeetingAiBreakdownService( ILlmClientService llmClient, MeetingService meetingService, ITaskService taskService) { ... } /// 分析会议内容,返回待办项建议列表 public async Task> AnalyzeAsync( int taskId, string? notes = null, CancellationToken ct = default) { // 1. 验证 taskId 为 Meeting 类型 var meeting = await _meetingService.GetMeetingTaskOrThrow(taskId); // 2. 获取会议文字(参数优先,否则取数据库中的 meetingNotes) var meetingText = notes ?? meeting.MeetingNotes; if (string.IsNullOrWhiteSpace(meetingText)) throw new BusinessException("会议内容为空,请先输入纪要或上传录音"); // 3. 构建 prompt,调用 LLM var prompt = BuildBreakdownPrompt(meetingText, meeting.Title); var response = await _llmClient.SendAsync(prompt, ct); // 4. 解析 LLM 返回的 JSON 为建议列表 return ParseSuggestions(response); } /// 确认建议并批量创建子任务 public async Task ConfirmAndCreateAsync( int taskId, List subTasks, CancellationToken ct = default) { // 1. 验证 taskId 为 Meeting 类型 await _meetingService.GetMeetingTaskOrThrow(taskId); // 2. 批量创建子任务 var created = new List(); foreach (var item in subTasks) { var dto = new CreateTaskDto { Title = item.Title, Priority = item.Priority, ParentTaskId = taskId }; created.Add(await _taskService.CreateTaskAsync(dto)); } return new BatchCreateResult { CreatedCount = created.Count, SubTasks = created }; } } ``` ### 4.2 LLM Prompt 设计 ``` System Prompt: 你是专业的项目管理和会议纪要分析助手。根据用户提供的会议内容,提取所有需要 后续行动的事项,生成待办项建议列表。 要求: 1. 每条建议包含标题(title)、优先级(priority)、原因(reason) 2. 标题应简洁明确(15 字以内),如"整理需求文档"、"安排评审会议" 3. 优先级:High(2)=紧急重要,Medium(1)=一般,Low(0)=可延迟 4. 原因应解释为什么需要这个待办项,引用会议中的具体决策或讨论 5. 只提取明确需要执行的事项,不要生成模糊或无来源的建议 6. 建议数量根据会议内容合理判断,通常 3-10 条 7. 如果会议内容无法提取出明确的行动项,返回空列表 输出格式(只输出 JSON,不要解释): { "suggestions": [ { "title": "待办项标题", "priority": 1, "reason": "原因说明" } ] } ``` ### 4.3 DTO 定义 ```csharp /// 会议任务拆分建议 public class MeetingTaskSuggestion { /// 建议的待办项标题 public string Title { get; set; } = string.Empty; /// 建议优先级 public TaskPriority Priority { get; set; } = TaskPriority.Medium; /// 拆分原因(LLM 输出,用于 UI 展示) public string Reason { get; set; } = string.Empty; } /// 拆分请求 public class BreakdownRequest { /// 会议纪要文字(可选,不传则使用已保存的 meetingNotes) public string? Notes { get; set; } } /// 拆分响应 public class BreakdownResponse { public int TaskId { get; set; } public List Suggestions { get; set; } = new(); } /// 批量创建请求中的单个子任务 public class SubTaskCreateItem { public string Title { get; set; } = string.Empty; public TaskPriority Priority { get; set; } = TaskPriority.Medium; } /// 批量确认请求 public class ConfirmBreakdownRequest { public List SubTasks { get; set; } = new(); } /// 批量创建结果 public class BatchCreateResult { public int CreatedCount { get; set; } public List SubTasks { get; set; } = new(); } ``` ### 4.4 API 端点 #### POST /api/meeting/{taskId}/breakdown - 接收:`BreakdownRequest` - 返回:`ApiResponse` - 处理: 1. 获取会议文字(参数优先于数据库) 2. 调用 `MeetingAiBreakdownService.AnalyzeAsync()` 3. 返回建议列表 - 错误: - 内容为空 → 400 "会议内容为空" - 离线 → 503 "AI 拆分仅在线可用" - taskId 非 Meeting 类型 → 400 #### POST /api/meeting/{taskId}/breakdown/confirm - 接收:`ConfirmBreakdownRequest` - 返回:`ApiResponse` - 处理: 1. 验证 taskId 为 Meeting 类型 2. 逐条创建子任务(通过 `TaskService.CreateTaskAsync`) 3. 返回创建结果 ### 4.5 LLM 响应解析 ```csharp /// 解析 LLM 返回的 JSON 为建议列表 private List ParseSuggestions(string llmResponse) { // 1. 清理 LLM 响应(移除可能的 markdown 代码块包裹、前后空白) var json = CleanJsonResponse(llmResponse); // 2. 反序列化 var result = JsonSerializer.Deserialize(json); // 3. 验证 if (result?.Suggestions == null || result.Suggestions.Count == 0) return new List(); // 4. 过滤无效条目(标题为空) return result.Suggestions .Where(s => !string.IsNullOrWhiteSpace(s.Title)) .Select(s => new MeetingTaskSuggestion { Title = s.Title.Trim(), Priority = s.Priority, Reason = s.Reason?.Trim() ?? string.Empty }) .ToList(); } /// LLM 原始响应结构 private class LlBreakdownResponse { public List Suggestions { get; set; } = new(); } ``` ### 4.6 错误处理 | 场景 | HTTP 状态码 | message | |---|---|---| | 会议文字内容为空 | 400 | 会议内容为空,请先输入纪要或上传录音 | | taskId 不存在 | 404 | 待办项不存在 | | taskId 不是 Meeting 类型 | 400 | 该待办项不是会议类型 | | LLM API 调用失败 | 502 | AI 拆分服务暂时不可用,请稍后重试 | | LLM 返回格式异常 | 500 | AI 拆分结果解析失败 | | 离线 | 503 | AI 拆分仅在线可用 | | 确认时子任务列表为空 | 400 | 请至少选择一个待办项 | ## 五、验收标准 | 验收项 | 验证方法 | 预期结果 | |---|---|---| | 会议内容分析 | 提交一段会议纪要文字 | 返回 3-10 条结构化建议 | | 建议质量 | 检查建议的标题、优先级、原因 | 标题明确、优先级合理、原因引用会议内容 | | 空内容拒绝 | 不传 notes 且 meetingNotes 为空 | 返回 400 | | 非 Meeting 类型拒绝 | 传普通待办项 ID | 返回 400 | | 在线依赖 | 离线时请求拆分 | 返回 503 | | 批量创建 | 确认勾选的建议 | 子任务批量创建成功 | | LLM 异常降级 | 模拟 LLM 调用失败 | 返回 502,不影响其他功能 | | 空结果处理 | 会议内容无可提取的待办项 | 返回空建议列表 + 提示信息 | ## 六、风险 | 风险 | 影响 | 应对 | |---|---|---| | LLM 返回格式不符 | 建议解析失败 | 增加 JSON 清理逻辑(移除 markdown 包裹、前后文本) | | LLM Token 限制 | 长会议纪要超出上下文窗口 | 限制文本长度(如 4000 字);超长时截断并告知用户 | | Prompt 需要迭代 | 建议质量不达预期 | prompt 作为配置项,支持热更新 | ## 七、Touch List | 文件路径 | 修改类型 | |---|---| | `src/Hua.Todo.Application/Meeting/MeetingAiBreakdownService.cs` | 新增 | | `src/Hua.Todo.Application/Meeting/MeetingController.cs` | 修改 | | `src/Hua.Todo.Application/Meeting/Models/MeetingDtos.cs` | 修改 | --- **工单编号**:03-03 **标题**:AI 任务拆分服务 **版本**:v1.3.0 **创建日期**:2026-06-16