Files
Hua.Todo/docs/project/研发工单-v1.3.0/03-03-AI任务拆分服务.md
ShaoHua 9223ceca50 feat(mcp): 新增 MCP 服务基础设施,重构规则文件序号,新增 v1.3.0 工单文档
- 规则重组:全局/ 下 8 个规则合并为 6 个(01+02→01,05+06→04),序号顺延

- 新增项目规则 05-多入口功能同步规范(UI/语音入口覆盖检查)

- 新增 MCP 服务基础设施:Mcp/ 目录(DI 注册、端点扩展、动态工具描述符)、单元测试

- v1.3.0 工单文档:03 系列(会议任务拆分)、04(富文本描述与附件管理)

- MCP 接口与前端集成指南:docs/manual/08、09
2026-06-16 01:15:40 +08:00

282 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 研发工单 v1.3.0 - 03-03 AI 任务拆分服务
> 父工单:[03-会议任务拆分](./03-会议任务拆分.md)
>
> 依赖:03-01API 契约)、工单 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
/// <summary>会议 AI 拆分服务,将会议文字内容分析为待办项建议</summary>
public class MeetingAiBreakdownService
{
private readonly ILlmClientService _llmClient;
private readonly MeetingService _meetingService;
private readonly ITaskService _taskService;
public MeetingAiBreakdownService(
ILlmClientService llmClient,
MeetingService meetingService,
ITaskService taskService) { ... }
/// <summary>分析会议内容,返回待办项建议列表</summary>
public async Task<List<MeetingTaskSuggestion>> 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);
}
/// <summary>确认建议并批量创建子任务</summary>
public async Task<BatchCreateResult> ConfirmAndCreateAsync(
int taskId, List<SubTaskCreateItem> subTasks, CancellationToken ct = default)
{
// 1. 验证 taskId 为 Meeting 类型
await _meetingService.GetMeetingTaskOrThrow(taskId);
// 2. 批量创建子任务
var created = new List<TaskDto>();
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
/// <summary>会议任务拆分建议</summary>
public class MeetingTaskSuggestion
{
/// <summary>建议的待办项标题</summary>
public string Title { get; set; } = string.Empty;
/// <summary>建议优先级</summary>
public TaskPriority Priority { get; set; } = TaskPriority.Medium;
/// <summary>拆分原因(LLM 输出,用于 UI 展示)</summary>
public string Reason { get; set; } = string.Empty;
}
/// <summary>拆分请求</summary>
public class BreakdownRequest
{
/// <summary>会议纪要文字(可选,不传则使用已保存的 meetingNotes</summary>
public string? Notes { get; set; }
}
/// <summary>拆分响应</summary>
public class BreakdownResponse
{
public int TaskId { get; set; }
public List<MeetingTaskSuggestion> Suggestions { get; set; } = new();
}
/// <summary>批量创建请求中的单个子任务</summary>
public class SubTaskCreateItem
{
public string Title { get; set; } = string.Empty;
public TaskPriority Priority { get; set; } = TaskPriority.Medium;
}
/// <summary>批量确认请求</summary>
public class ConfirmBreakdownRequest
{
public List<SubTaskCreateItem> SubTasks { get; set; } = new();
}
/// <summary>批量创建结果</summary>
public class BatchCreateResult
{
public int CreatedCount { get; set; }
public List<TaskDto> SubTasks { get; set; } = new();
}
```
### 4.4 API 端点
#### POST /api/meeting/{taskId}/breakdown
- 接收:`BreakdownRequest`
- 返回:`ApiResponse<BreakdownResponse>`
- 处理:
1. 获取会议文字(参数优先于数据库)
2. 调用 `MeetingAiBreakdownService.AnalyzeAsync()`
3. 返回建议列表
- 错误:
- 内容为空 → 400 "会议内容为空"
- 离线 → 503 "AI 拆分仅在线可用"
- taskId 非 Meeting 类型 → 400
#### POST /api/meeting/{taskId}/breakdown/confirm
- 接收:`ConfirmBreakdownRequest`
- 返回:`ApiResponse<BatchCreateResult>`
- 处理:
1. 验证 taskId 为 Meeting 类型
2. 逐条创建子任务(通过 `TaskService.CreateTaskAsync`
3. 返回创建结果
### 4.5 LLM 响应解析
```csharp
/// <summary>解析 LLM 返回的 JSON 为建议列表</summary>
private List<MeetingTaskSuggestion> ParseSuggestions(string llmResponse)
{
// 1. 清理 LLM 响应(移除可能的 markdown 代码块包裹、前后空白)
var json = CleanJsonResponse(llmResponse);
// 2. 反序列化
var result = JsonSerializer.Deserialize<LlBreakdownResponse>(json);
// 3. 验证
if (result?.Suggestions == null || result.Suggestions.Count == 0)
return new List<MeetingTaskSuggestion>();
// 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();
}
/// <summary>LLM 原始响应结构</summary>
private class LlBreakdownResponse
{
public List<MeetingTaskSuggestion> 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