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
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
# 研发工单 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
|
||||
/// <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
|
||||
Reference in New Issue
Block a user