Files
AI-Secretary/docs/specs/01_老板AI秘书与AI草稿.md
T
2026-06-22 16:30:11 +08:00

915 lines
38 KiB
Markdown
Raw 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.
# 01_老板 AI 秘书与 AI 草稿
## 1. 模块目标
本模块负责把老板在飞书私聊或平台入口输入的一句话,交给 `AiSecretaryAgent` 理解、归类、补上下文,并生成可确认的 AI 草稿或直接回复。
这个模块是老板秘书的大脑,不直接创建任务、提醒或发送通知。所有执行动作都必须走人工确认后的任务、提醒和通知模块。
说明:整体方案中的“事项、需求、安排”在第一版统一归入 `task`。如果后续单独拆出 requirement 类型,需要先更新本 spec 和数据对象约定。
## 2. 第一版做什么
第一版核心定位:
```text
老板一句话
-> AiSecretaryAgent 判断意图和上下文
-> 意图归类为 task / reminder / qa / realtime_qa / note / need_more_info / unsupported
-> 稳定 AiSecretaryResponse JSON
-> 生成 AI 草稿 / 追问 / 直接回复
-> 老板确认、补充、取消或转程经理
```
- 支持老板通过飞书私聊或平台入口输入文本。
- 统一走 `POST /api/secretary/handle-message`
- 判断 `task/reminder/qa/realtime_qa/note/need_more_info/unsupported`
- 生成稳定的 `AiSecretaryResponse` JSON。
- 对任务和提醒只生成可确认草稿,不直接执行。
- 对普通聊天、实时问答兜底、普通记录直接回复或记录。
- 支持 30 分钟内补充、重说、确认、取消和转程经理。
- 支持低置信度追问和最多 3 轮追问收敛。
- 使用 PostgreSQL 持久化老板秘书当前会话记忆、消息记录和 `BotContext`
- 记录模型调用、提示词版本、操作日志和失败记录。
## 3. 第一版不做
- 不做员工通用 AI 工作台。
- 不让程经理或普通员工通过机器人派活。
- 不让 AI 自动创建任务、提醒、通知或外部系统操作。
- 不做复杂多轮通用聊天记忆、跨账号记忆或长期画像;第一阶段只持久化老板秘书当前会话所需的对话记忆和草稿上下文。
- 不把公司背景库全文每次塞进模型,只使用 `PromptContext` 中的压缩摘要和版本。
- 不做实时行情、交易、数据库、后台系统自动查询和自动判断。
- 不把普通记录做成独立知识库、向量库或长期记忆,只保留当前演示闭环需要的消息记录。
## 4. 核心流程
### 4.1 统一入口
所有老板消息统一走:
```http
POST /api/secretary/handle-message
```
飞书回调、`/api/demo/boss-message`、未来平台入口都转发到该入口。
处理顺序:
```text
记录原始消息
-> 按 source + message_id 做幂等检查
-> 校验发送人角色
-> 非老板直接固定回复并记录
-> 读取 BotContext,外部 context 只作为参考
-> 判断本次消息是 follow_up / new_request / qa / realtime_qa / note / unsupported / command
-> 组装 PromptContext
-> 调用模型
-> 校验 AiSecretaryResponse
-> 写 ModelCallLog
-> 生成草稿 / 追问 / 直接回复
-> 写 OperationLog 和必要 FailureRecord
```
幂等规则:
- 同一个 `source + message_id` 重复进入时,不重复调用模型、不重复创建草稿、不重复写业务对象。
- 幂等命中时,不重复追加 PostgreSQL `secretary_messages`,不重复写 `SecretaryMessage`,只返回既有处理结果。
- 飞书或外部入口的原始回调可以写 `FeishuEventLog` 并标记为 duplicate,但不能污染老板对话记忆。
- 如果上一轮已经处理成功,直接返回上一轮处理结果或可重放的 `reply_type/answer/draft_id`
- 如果上一轮处理失败,允许按失败类型进入人工重试或后台重试,但必须继续复用同一个幂等键。
### 4.2 上下文规则
`BotContext / Conversation` 记录老板当前可补充的草稿。
`BotContext.status` 建议取值:
```text
empty
awaiting_more_info
awaiting_confirm
expired
cleared
```
状态含义:
- `empty`:当前没有可补充或可确认的草稿。
- `awaiting_more_info`AI 已追问,等待老板补充。
- `awaiting_confirm`:草稿已整理完成,等待老板确认、修改、取消或转经理。
- `expired`:超过 30 分钟,旧上下文只可用于回顾,不再默认参与修改。
- `cleared`:草稿已确认、取消或转换完成,当前上下文已清空。
规则:
- 每次进入 `awaiting_more_info``awaiting_confirm` 状态时重置 `expires_at = now + 30 分钟`
- 点击或说出“补充/重说”后,30 分钟内下一条消息优先尝试作为上一条草稿的修正。
- 30 分钟内不能无条件吞掉所有消息,必须先判断为 `follow_up / new_request / qa / realtime_qa / note / unsupported / command`
- 老板说“换成东东”“改到明天”“刚才那个不要了”,优先修正当前草稿。
- 老板说“另外再安排一个”“新建一个”“再帮我记一个”,创建新草稿。
- 老板说“确认”“就这样”“发给他”,确认当前草稿。
- 老板说“算了”“取消”“不要了”,取消当前草稿并清空上下文。
- 老板问“刚才我说了什么”“总结一下”,做上下文回顾,不修改草稿。
- 超过 30 分钟,上下文标记 `expired`,下一条默认按新输入处理;如果老板明显仍在说上一条草稿,可先提示“上一条草稿已过期,我按新请求处理”。
- 老板确认、取消或草稿转换为任务/提醒后立即清空上下文,不受 30 分钟窗口约束。
- 30 分钟窗口只绑定未确认草稿;草稿进入 `CONFIRMED / CONVERTED / CANCELLED` 后,后续“改一下刚才那个”不能再修改原草稿,只能作为已发布任务/提醒变更请求或第一阶段能力外请求处理。
- 多次 `follow_up` 必须合并到同一个 `pending_draft`,直到老板确认、取消、过期或明确新建,不得每次补充都新建草稿。
- `BotContext` 只提供上下文候选,不强制绑定消息。当本地规则或模型无法判断是修改当前草稿、新建请求、普通聊天还是取消当前草稿时,必须走 `need_more_info` 追问,不允许默认修改当前草稿。
- 老板可以通过“另外、重新来、换个事、先别管这个、取消、重新安排”等自然语言随时跳出当前上下文。
`command` 不是模型最终 `intent`,而是进入模型前后的消息预分类,用于处理“确认、取消、转经理、补充/重说、上下文回顾”等对当前 `BotContext` 的操作。命中明确 command 时,后端优先处理当前上下文;需要模型理解时,再把 command 结果作为上下文提示传给模型。
命中确认、取消、补充、转经理等 command,但当前没有待处理草稿时,不抛异常、不创建新草稿,按 `qa`/`plain_answer` 回复“目前没有待确认的草稿”。
自然语言指令最小关键词集合:
| 类型 | 关键词 |
| --- | --- |
| 新建信号 | 另外、新建、再来一个、再安排、再记一个 |
| 跳出信号 | 重新来、换个事、先别管这个、说另一个、重新安排 |
| 补充信号 | 改成、改为、换成、改到、补充、加上 |
| 确认信号 | 确认、就这样、发给他、可以了 |
| 取消信号 | 取消、算了、不要了、作废、刚才那个不要 |
关键词只是第一层判断,最终仍要结合 BotContext、当前草稿和模型结构化结果确认。
### 4.3 追问收敛
- 缺少关键字段时先追问。
- 每次最多 3 个问题。
- 累计最多 3 轮追问。
- 老板说“就这样”“差不多”“先这样”时,停止追问,生成带 `missing_fields` 的草稿。
- 3 轮仍补不全,也生成带 `missing_fields` 的草稿,不继续拉扯。
- 每次追问都必须保存一条 `SecretaryMessage``intent_type=need_more_info`,内容为本次给老板的问题。
- 追问轮次写入 `BotContext.follow_up_count`,并记录 `OperationLog(action=ask_follow_up)`
- `follow_up_count` 在以下情况重置为 0:草稿被确认、取消、过期、进入 `PENDING_CONFIRM`,或生成带 `missing_fields` 的终态草稿后收到下一条新消息。
## 5. 数据对象
本模块主要涉及:
- `AiSecretaryResponse`
- `BotContext / Conversation`
- `SecretaryDraft / ai_drafts`
- `SecretaryMessage / note`
- `PromptContext`
- `ModelCallLog`
- `OperationLog`
- `FailureRecord`
- `FeishuEventLog`
字段事实源以 `docs/contracts/数据对象约定.md` 为准。本 spec 只强调模块规则。
`BotContext` 归本模块维护;`05_权限日志失败记录.md` 只消费它做调试展示、过期记录和操作审计。
`BotContext` 第一阶段建议字段:
| 字段 | 说明 |
| --- | --- |
| `conversation_id` | 当前老板秘书会话 ID,第一阶段可固定为 `boss_secretary_default` |
| `boss_id` | PostgreSQL 中的老板用户 ID |
| `status` | `empty/awaiting_more_info/awaiting_confirm/expired/cleared` |
| `pending_draft_id` | 当前待补充或待确认的 PostgreSQL 草稿 ID |
| `pending_draft_type` | `task/reminder/none` |
| `last_intent` | 上一条有效意图 |
| `expires_at` | 当前上下文过期时间,进入待补充或待确认时重置为 30 分钟后 |
| `follow_up_count` | 当前草稿累计追问轮次 |
| `last_message_id` | 最近一次参与上下文判断的消息 ID |
| `extracted_facts` | 从多轮对话提取的候选事实,只用于草稿修订,不作为业务事实源 |
| `updated_at` | 上下文最近更新时间 |
`SecretaryMessage` 是老板消息、AI 追问、普通记录和上下文回顾的统一保存对象。`note` 不新建独立业务表,第一版复用 `SecretaryMessage`,至少保存 `message_id/source/sender_id/requester_open_id/requester_name/text/answer/intent_type/raw_payload/created_at`,并在回顾时按时间倒序读取默认最近 10 条消息、当前待确认草稿和最近 3 条已确认/已转换草稿摘要。`note` 类型可以没有 `answer`,或 `answer=""`,回顾时主要展示原始 `text`
`PromptContext` 至少包含:
| 字段 | 说明 |
| --- | --- |
| `company_background_summary` | 公司背景库压缩摘要,不塞全文 |
| `boss_communication_style` | 老板大白话风格和回复口吻规则 |
| `ai_secretary_rules` | 本 spec 中的第一阶段业务规则摘要 |
| `role_and_alias_rules` | 角色、人员称呼和别名规则摘要 |
| `current_bot_context` | 当前 `BotContext` 摘要 |
| `pending_draft_summary` | 当前待确认草稿摘要,没有则为空 |
| `recent_messages_summary` | 最近对话摘要,用于判断补充、新建、普通聊天 |
| `prompt_version` | 提示词版本号,必须写入 `ModelCallLog` |
第一阶段不再依赖纯内存保存对话上下文,也不再拆多套数据库。PostgreSQL 是唯一数据源:角色、人员映射、草稿确认、任务、提醒、通知、反馈、失败记录、操作日志,以及老板秘书当前会话的 AI 对话记忆、消息记录和 `BotContext` 快照都落 PostgreSQL。
### 5.1 第一阶段对话记忆持久化(PostgreSQL 最小版)
第一阶段只做一个老板秘书入口的对话记忆持久化,不做多账号、多租户、跨老板记忆合并、长期画像、embedding 召回或复杂记忆策略。
PostgreSQL 最小保存三类 AI 记忆数据:
- `secretary_conversations`:当前老板秘书会话,可先使用固定 `conversation_id=boss_secretary_default`,并关联 PostgreSQL 中的 `boss_id`
- `secretary_messages`:老板输入、AI 回复、追问、上下文回顾、普通记录等对话消息。
- `bot_contexts`:当前待确认草稿、`pending_draft_id`、上一条意图、30 分钟过期时间、`follow_up_count`、是否等待补充等上下文状态。
读写规则:
- `secretary_messages` 只追加,不修改,用于对话回放、上下文回顾和问题复盘。
- `bot_contexts` 表示当前最新上下文,可覆盖更新。
- `secretary_messages.bot_context_snapshot` 使用 `jsonb` 保存消息发生时的快照,不代表当前最新状态。
- `bot_contexts.extracted_facts` 使用 `jsonb` 保存从多轮对话里提取出的结构化事实,例如 `receiver_text/task_content/schedule_text/requires_feedback`;这些事实只能用于草稿修订,不能替代正式业务表。
-`conversation_id + created_at``source + message_id``bot_contexts.conversation_id` 建索引;`jsonb` 字段第一阶段只做读取展示和调试,不作为复杂查询主路径。
最小字段建议:
```json
{
"conversation_id": "boss_secretary_default",
"boss_id": "postgres_user_id",
"source": "feishu|web|debug",
"message_id": "外部消息ID或内部生成ID",
"role": "boss|assistant|system",
"text": "原始输入或AI回复",
"intent": "task|reminder|qa|realtime_qa|note|need_more_info|unsupported|context_summary",
"draft_id": "postgres_ai_draft_id",
"bot_context_snapshot": {},
"created_at": "2026-06-22T10:00:00+08:00"
}
```
边界规则:
- PostgreSQL 是唯一事实源;AI 对话记忆表也在 PostgreSQL 内,但不能替代正式业务表。
- `secretary_messages``bot_contexts` 中的 `draft_id/task_id/reminder_id/notification_id` 只能引用正式业务表记录,不能替代对应业务状态。
- 创建草稿、确认草稿、创建任务、创建提醒、发送通知、员工反馈,必须以 PostgreSQL 事务和正式业务表状态为准。
- 服务重启后必须能从 PostgreSQL 恢复最近有效 `BotContext`,避免 30 分钟内的补充/确认丢失。
- 如果 PostgreSQL 的 AI 记忆表或上下文读写失败,不能继续依赖内存悄悄处理确认类命令;必须降级回复“暂时没处理好,请稍后再试”,并写入 `FailureRecord(memory_store_failed)`。如果 PostgreSQL 整体不可用导致 `FailureRecord` 也无法写入,至少写应用日志/告警,恢复后补偿记录。
- 后续如果要做多账号、多会话、长期记忆或向量召回,优先在 PostgreSQL 中扩展 schema、`jsonb`、全文索引或 pgvector;只有 PostgreSQL 明确无法满足时,再另行评估外部存储。
### 5.2 AiSecretaryResponse
模型必须返回稳定 JSON
```json
{
"intent": "task|reminder|qa|realtime_qa|note|need_more_info|unsupported",
"draft_type": "task|reminder|none",
"should_create_draft": true,
"route_type": "none|direct_after_boss_confirm|manager_confirm_required",
"answer": "给老板看的自然语言回复",
"draft": {
"title": "草稿标题",
"content": "整理后内容",
"receiver_candidates": ["东东"],
"receiver_text": "东东",
"scheduled_at": "2026-06-23T09:00:00+08:00",
"schedule_text": "明天上午 9 点",
"recurrence_type": "none|daily|weekly|monthly",
"requires_feedback": true,
"need_manager_confirm": false,
"missing_fields": [
{
"field": "receiver",
"reason": "missing_person_mapping",
"raw_value": "小王",
"message": "无法识别接收人"
}
]
},
"questions": ["最多3个追问"],
"reason": "简要说明判断原因"
}
```
校验规则:
- `task/reminder` 必须 `should_create_draft=true``draft_type` 必须对应为 `task``reminder`
- `qa/realtime_qa/note/need_more_info/unsupported` 必须 `should_create_draft=false``draft_type=none`
- `task``route_type` 必填,只能是 `direct_after_boss_confirm``manager_confirm_required`
- `reminder/qa/realtime_qa/note/need_more_info/unsupported``route_type=none`
- `route_type=manager_confirm_required` 时,`draft.need_manager_confirm=true`;老板主动说“给程经理看看”“让经理确认”时,也必须置为 true。
- `route_type=direct_after_boss_confirm` 时,`draft.need_manager_confirm=false`
- `task` 必须有事项内容;时间不是必填项。
- `reminder` 必须有提醒内容和可解析时间;缺时间必须追问或进入 `missing_fields`
- `missing_fields` 必须是对象数组;`field``reason` 必填,`raw_value``message` 可选。常用 `reason` 包括 `missing_person_mapping``time_not_parsed``receiver_missing``content_missing``scheduled_at_missing`
- `questions` 最多 3 个;`intent=need_more_info` 时至少 1 个、最多 3 个,其他 intent 必须为空数组。
- `reason` 必填,用于调试和复盘;写入 `ModelCallLog.parsed_result`,不返回给老板,但可通过调试接口查看。
- `answer` 不能出现“已通知”“已创建”“已发送”“已经安排”等执行语义;出现时视为校验失败或强制改写为“我整理成草稿,等你确认”。
- `answer` 是给老板看的口语回复,`draft.content` 是正式草稿正文;二者可以表达方式不同,但不能语义矛盾。
- `title/content/receiver/schedule` 不能编造老板没说过的具体业务结论,只能整理、压缩和明确表达。
- `receiver_candidates` 无法解析到 open_id 时,不直接失败;保留 `receiver_text`,把 `missing_person_mapping` 写入 `missing_fields`,草稿仍可生成,但确认前必须由后端、调试页或人员映射补齐。
- 草稿确认前必须检查 `missing_fields`;阻塞字段未补齐时不允许正常确认,回复老板补齐关键信息。调试页如果强制确认,必须写对应 `FailureRecord` 和操作日志。
- 非法 JSON 不允许进入任务、提醒、通知流程。
确认阻塞规则:
| draft_type | 阻止确认的 missing_fields.reason | 不阻止确认 |
| --- | --- | --- |
| task | `content_missing``receiver_missing``missing_person_mapping` | `time_not_parsed``scheduled_at_missing` |
| reminder | `content_missing``scheduled_at_missing``time_not_parsed``receiver_missing``missing_person_mapping` | 无 |
如果模型无法判断本次输入应归为 `follow_up / new_request / qa / cancel`,必须返回 `need_more_info` 并追问,例如“你是想修改刚才那条草稿,还是新建一条?”。
### 5.3 意图区分
| intent | 处理 |
| --- | --- |
| task | 生成任务草稿,等待老板确认 |
| reminder | 生成提醒草稿,等待老板确认 |
| qa | 直接回答,不生成草稿、不通知 |
| realtime_qa | 固定提示需要实时数据查询,第一版暂不作为正式能力 |
| note | 只保存消息记录,不生成草稿、不通知,可用于“刚才说了什么”回顾 |
| need_more_info | 信息不足但属于可处理范围,主动追问 |
| unsupported | 超出第一版能力,解释为什么不能做,不追问执行字段 |
优先级和兜底规则:
- 天气、新闻、股价、行情、今日走势、实时政策等需要实时数据的输入,固定归为 `realtime_qa`
- `realtime_qa.answer` 固定使用模板:“这个需要实时数据查询,第一版暂不作为正式能力;我不会把它生成任务或提醒。”
- 超出第一版能力但不属于实时数据类的输入,归为 `unsupported`
- `unsupported.answer` 应说明当前入口只处理老板事项草稿、提醒草稿、普通问答和普通记录,不追问执行字段。
示例:
- “安排一下” -> `need_more_info`,追问安排谁、做什么。
- “帮我分析今天的股票走势” -> `realtime_qa`,提示第一版不做正式实时数据查询或投资判断。
- “帮我写一份完整年报” -> `unsupported`,说明超出第一版老板秘书入口能力。
- “记一下,今天先别催东东” -> `note`,只保存记录。
### 5.4 草稿状态
`ai_drafts.status` 只表示已经落 PostgreSQL 草稿表的草稿状态。非草稿意图(`qa/realtime_qa/note/unsupported`)和纯追问结果(`need_more_info`)不落 `ai_drafts`,只保存 `SecretaryMessage``BotContext` 和处理结果。
`ai_drafts.status` 建议状态:
```text
PENDING_CONFIRM
NEED_MANAGER_CONFIRM
CONFIRMED
CANCELLED
FAILED
CONVERTED
```
对老板可见时可以简化为:待确认、待程经理确认、已确认、已取消、已失败。
状态含义:
- `PENDING_CONFIRM`:草稿可确认,等待老板确认。
- `NEED_MANAGER_CONFIRM`:老板已确认,但该任务仍需要程经理确认,不能直接转换任务。
- `CONFIRMED`:确认链路已完成,可以交给任务或提醒模块转换。
- `CANCELLED`:老板取消草稿。
- `FAILED`:草稿或 AI 解析过程失败的草稿状态;失败原因写入 `FailureRecord.failure_type`,例如 `ai_parse_failed`,两者不能混用。
- `CONVERTED`:草稿已经成功转换为任务或提醒。
非草稿处理状态:
- `need_more_info`:本轮只追问老板,不创建 `ai_drafts`;追问内容保存为 `SecretaryMessage(intent_type=need_more_info)`,上下文状态为 `BotContext.status=awaiting_more_info`
- `answered``qa/realtime_qa/note/context_summary/unsupported` 的处理结果,不转换为任务或提醒,不影响当前有效 `BotContext`
- 追问 3 轮仍补不全、但系统决定生成带 `missing_fields` 的草稿时,模型输出必须从 `need_more_info` 转为 `task``reminder`,创建 `PENDING_CONFIRM` 草稿,并由确认阻塞规则控制能否确认。
`DRAFTING` 不作为第一版 `ai_drafts.status` 持久化枚举。实现中如需表达“AI 已决定生成草稿但还在校验/解析”的过程态,只能作为方法内的内存标记或局部变量;落库状态必须从 `PENDING_CONFIRM``NEED_MANAGER_CONFIRM``FAILED` 开始。
### 5.5 Prompt 硬规则
- AI 只整理草稿,不直接执行动作。
- AI 不得在回复里声称“已创建、已通知、已发送、已安排完成”。
- AI 不得编造老板没有给出的业务事实、责任结论、交易判断或研究结论。
- 结构化字段必须正式清晰,不能出现游戏化表达。
- 实时问答、行情、股价、新闻、天气等输入,第一版默认不作为正式能力,走 `realtime_qa` 兜底。
- note 只作为消息记录,不升级成任务、提醒或通知。
模型调用失败策略:
- 百炼或模型网关超时、网络失败、服务异常时,最多自动重试 1 次。
- 重试仍失败时,不创建草稿、不继续修改 `BotContext`,返回 `reply_type=error`
- 失败回复固定为:“暂时没处理好,请稍后再试。”
-`ModelCallLog`,并写 `FailureRecord(ai_model_failed)`
- 模型返回非法 JSON、schema 校验失败或字段不匹配,走 `ai_parse_failed`,不使用 `ai_model_failed`
## 6. 接口需求
### 6.1 统一消息入口
```http
POST /api/secretary/handle-message
```
输入包括:
- `source`
- `message_id`
- `sender.open_id`
- `sender.name`
- `sender.role`
- `text`
- `context.has_pending_draft`
- `context.pending_draft_id`
`context` 是外部调用方可选提示。后端必须以本地 `BotContext` 为准:
- 调用方没传 context 时,后端自己查询 BotContext。
- 调用方传了 context 但和 BotContext 不一致时,以 BotContext 为准。
- 外部 context 不能绕过草稿归属、过期时间和权限校验。
返回包括:
- `intent`
- `reply_type`
- `answer`
- `draft`
- `questions`
- `context_summary`
- `failure`
`failure` 只在不可恢复错误或降级时返回。结构建议:
```json
{
"type": "ai_parse_failed|ai_model_failed|bot_message_failed|follow_up_expired|bot_unauthorized|missing_person_mapping|memory_store_failed|draft_convert_failed|permission_error|system_error",
"message": "给前端或调试页看的失败说明",
"record_id": "failure record id,可为空"
}
```
当发生 `ai_parse_failed``ai_model_failed``memory_store_failed``system_error` 等不可恢复错误时,`reply_type=error``answer` 使用固定兜底话术:“暂时没处理好,请稍后再试。”,并写 `FailureRecord`
`reply_type` 建议取值:
- `draft_preview`:返回草稿预览,等待老板确认。
- `follow_up_question`:返回追问问题。
- `plain_answer`:普通问答回复。
- `context_summary`:上下文回顾回复。
- `unsupported`:能力外兜底回复。
- `error`:降级或失败回复。
当老板问“刚才我说了什么”“总结一下”时,返回 `reply_type=context_summary`,并返回 `context_summary` 字段,默认包含最近 10 条 `messages`、当前待确认草稿、最近 3 条已确认/已转换草稿摘要;该回复不创建草稿、不覆盖当前待确认草稿。
### 6.2 兼容接口
现有 Demo 接口保留,但内部转发:
- `/api/demo/boss-message`
- `/api/feishu/callback`
- `/api/demo/drafts/{id}/confirm`
- `/api/demo/drafts/{id}/cancel`
`/api/feishu/callback` 的验签、去重、鉴权和原始事件记录归 `03_飞书通知与反馈.md``05_权限日志失败记录.md` 负责;本模块只消费解析后的老板消息或草稿操作命令。
## 7. 权限规则
- 飞书机器人私聊入口第一版只允许老板使用。
- 非老板消息不调用 AI,不生成草稿,不创建业务对象。
- 草稿确认、取消、补充和转经理必须是草稿发起老板本人。
- 程经理不能通过机器人对话派活,只通过 Web 或通知跳转确认复杂任务。
- AI 不得绕过人工确认。
## 8. 状态流转
```text
老板输入
-> AI 解析成功
-> draft_preview / follow_up_question / plain_answer
-> 老板补充
-> 新草稿或修订草稿
-> 老板确认
-> direct_after_boss_confirm: CONFIRMED -> TaskAdapter / ReminderService 转换 -> CONVERTED
-> manager_confirm_required: NEED_MANAGER_CONFIRM -> 程经理确认 -> CONFIRMED -> TaskAdapter 转换 -> CONVERTED
```
草稿确认后的边界:
- `direct_after_boss_confirm`:老板确认成功后,草稿进入 `CONFIRMED`
- `manager_confirm_required`:老板确认成功后,草稿进入 `NEED_MANAGER_CONFIRM`,通知或展示给程经理确认;程经理确认后才进入 `CONFIRMED`
- 任务/提醒转换成功后,草稿进入 `CONVERTED`,并清空 `BotContext`
- 任务/提醒转换失败时,不回滚老板确认;草稿保持 `CONFIRMED`,写 `FailureRecord(draft_convert_failed)`,支持后台重试或人工处理。
- 老板确认后,无论进入 `CONFIRMED` 还是 `NEED_MANAGER_CONFIRM`,都要清空老板侧 `BotContext`;后续经理确认不再依赖老板 30 分钟上下文。
- `NEED_MANAGER_CONFIRM / CONFIRMED / CONVERTED` 后,01 不再直接修改原草稿;老板后续说“把刚才发出去那个改一下”,应识别为任务/提醒变更请求,第一阶段未接入变更能力时回复暂不支持或引导到任务模块。
直接回复流转:
```text
qa / realtime_qa / note / context_summary
-> answered 处理结果
-> 不落 ai_drafts,不转换,不清空当前有效 BotContext
```
`context_summary` 也必须保存为一条 `SecretaryMessage(intent_type=context_summary)`,便于调试页回看“老板什么时候要求回顾、系统回了什么”。
失败流转:
```text
模型调用失败
-> 重试 1 次
-> 仍失败则不创建可确认草稿
-> FailureRecord(ai_model_failed)
JSON 非法 / schema 校验失败
-> 不创建可确认草稿
-> 可选创建 FAILED 草稿仅用于调试
-> FailureRecord(ai_parse_failed)
```
`FAILED` 草稿不能确认、不能转换为任务/提醒、不能发送通知,只能用于调试追踪。
取消流转:
```text
PENDING_CONFIRM
-> CANCELLED
-> 清空 BotContext
awaiting_more_info 且还没有 ai_drafts
-> 清空 BotContext
-> 记录 OperationLog(cancel_context)
```
`NEED_MANAGER_CONFIRM` 阶段如需取消或退回,归程经理确认模块或任务模块处理;01 只负责展示状态和记录操作,不再把它当作可补充草稿处理。
## 9. 失败和日志
必须记录:
- 老板原始输入。
- BotContext 命中或过期。
- PromptContext 使用版本。
- 模型请求、响应、耗时和解析结果。
- `AiSecretaryResponse.reason`,写入 `ModelCallLog.parsed_result`,只用于调试和复盘。
- AI JSON 校验失败。
- 草稿创建、修订、确认、取消、转经理。
- AI 追问消息,保存为 `SecretaryMessage(intent_type=need_more_info)`
- 追问轮次变化,保存到 `BotContext.follow_up_count`
- 追问动作,记录 `OperationLog(action=ask_follow_up)`
- 非老板访问。
- note 消息保存。
- context_summary 上下文回顾,保存为 `SecretaryMessage(intent_type=context_summary)`
- receiver_candidates 解析失败和人工补映射。
`failure.type``FailureRecord.failure_type` 使用同一组枚举:
| failure_type | 触发条件 |
| --- | --- |
| `ai_parse_failed` | 模型返回非法 JSON、schema 校验失败、字段组合不符合 `AiSecretaryResponse` |
| `ai_model_failed` | 百炼或模型网关超时、网络失败、服务异常,重试 1 次后仍失败 |
| `bot_message_failed` | 入口消息结构缺失、无法解析文本或 sender 信息,导致无法进入正常处理 |
| `follow_up_expired` | 存在旧 `BotContext` 但已超过 30 分钟,老板仍尝试修改上一条草稿 |
| `bot_unauthorized` | 非老板访问老板秘书入口 |
| `missing_person_mapping` | 接收人候选无法映射真实人员;生成草稿时可记录低严重度,确认被阻止或调试页强制确认时必须记录 |
| `memory_store_failed` | PostgreSQL AI 记忆表、上下文或快照保存/读取失败 |
| `draft_convert_failed` | 老板或程经理确认后,转换任务/提醒失败 |
| `permission_error` | 非草稿发起老板尝试确认、取消、补充或转程经理 |
| `system_error` | 未归类系统异常 |
## 10. 给 AI 的测试样例
### 10.1 任务草稿
输入:
```text
让东东跟进一下合同
```
期望:
- 生成 `task` 草稿。
- 不因为没有时间反复追问。
- 接收人候选包含东东。
- `should_create_draft=true`
### 10.2 提醒草稿
输入:
```text
明天上午 9 点提醒东东准备咖啡
```
期望:
- 生成 `reminder` 草稿。
- `scheduled_at` 使用 `Asia/Shanghai`
- recurrence 为 `none`
### 10.3 补充上一条
前置:存在一条待确认 `task` 草稿,内容为“让东东跟进合同”,没有明确 `due_at``schedule_text`;该草稿不是提醒草稿。
输入:
```text
改成明天下午
```
期望:
- 判断为 `follow_up`
- 修订上一条草稿,不新建任务。
- 更新该任务草稿的 `due_at``schedule_text`,不修改 `reminder.scheduled_at`
### 10.4 新建例外
前置:存在待确认草稿。
输入:
```text
另外再安排一个,让行政订会议室
```
期望:
- 判断为 `new_request`
- 新建草稿。
### 10.5 取消
输入:
```text
算了,刚才那个不要了
```
期望:
- 取消当前草稿。
- 清空上下文。
### 10.6 非老板访问
输入:普通员工私聊机器人。
期望:
- 固定回复“当前入口是老板秘书,如需处理事项请进入平台。”
- 不调用 AI。
- 写失败和操作日志。
### 10.7 追问信息不足
输入:
```text
安排一下
```
期望:
- 判断为 `need_more_info`
- 不生成任务或提醒。
- 追问“安排谁、做什么”等关键问题。
### 10.8 实时问答兜底
输入:
```text
帮我分析今天的股票走势
```
期望:
- 判断为 `realtime_qa`
- 不生成草稿。
- 回复固定模板:“这个需要实时数据查询,第一版暂不作为正式能力;我不会把它生成任务或提醒。”
### 10.9 普通记录
输入:
```text
记一下,今天先别催东东
```
期望:
- 判断为 `note`
- 只保存消息记录。
- 不创建草稿、不通知任何人。
### 10.10 接收人解析失败
输入:
```text
让小王处理一下合同
```
前置:`PersonResolver` 无法识别“小王”。
期望:
- 草稿可以生成。
- `receiver_text=小王`
- `missing_fields` 包含 `{ "field": "receiver", "reason": "missing_person_mapping", "raw_value": "小王" }`
- 正常确认被阻止,回复老板需要先补齐人员映射。
### 10.11 能力外请求
输入:
```text
帮我写一份完整年报
```
期望:
- 判断为 `unsupported`
- 不生成草稿。
- 不追问执行人、时间等任务字段。
- 说明当前入口只处理老板事项草稿、提醒草稿、普通问答和普通记录。
### 10.12 上下文回顾
输入:
```text
我刚才说了什么
```
期望:
- 返回 `reply_type=context_summary`
- 返回 `context_summary`,默认摘要最近 10 条消息、当前待确认草稿和最近 3 条已确认/已转换草稿。
- 不创建新草稿,不覆盖当前待确认草稿。
- 保存 `SecretaryMessage(intent_type=context_summary)`
### 10.13 无草稿 command
前置:当前没有待确认草稿。
输入:
```text
确认
```
期望:
- 命中 command,但不抛异常。
- 不创建草稿。
- 返回 `reply_type=plain_answer`
- 回复“目前没有待确认的草稿”。
### 10.14 错误降级
前置:模型返回非法 JSON。
期望:
- 不进入任务、提醒、通知流程。
-`ModelCallLog``FailureRecord(ai_parse_failed)`
- 返回 `reply_type=error`
- `answer=暂时没处理好,请稍后再试。`
- `failure.type=ai_parse_failed`
- 如落 `FAILED` 草稿,仅用于调试,不能确认或转换。
### 10.15 低置信度追问
前置:存在一条待确认任务草稿。
输入:
```text
这个换一下
```
期望:
- 如果无法判断是修改接收人、时间、措辞,还是切换到新事项,不默认修改当前草稿。
- 返回 `intent=need_more_info`
- 追问“你是想修改刚才那条草稿,还是新建一条?具体想换什么?”。
### 10.16 已发布后修改
前置:上一条草稿已经 `CONVERTED``BotContext` 已清空。
输入:
```text
把刚才那个改成明天下午
```
期望:
- 不修改已转换草稿。
- 识别为任务/提醒变更请求或第一阶段能力外请求。
- 第一阶段未接入变更能力时,回复“刚才的事项已经发布,第一阶段暂不支持直接修改已发布任务;我可以帮你重新整理一条补充说明。”。
### 10.17 消息幂等
前置:同一个 `source + message_id` 已处理成功并生成草稿。
输入:同一消息再次进入。
期望:
- 不重复调用模型。
- 不重复创建草稿。
- 不重复追加 PostgreSQL `secretary_messages``SecretaryMessage`
- 返回上一轮处理结果或已存在草稿引用。
### 10.18 待确认草稿中的普通聊天
前置:存在一条待确认任务草稿。
输入:
```text
这个措辞是不是太硬了?
```
期望:
- 判断为 `qa` 或低置信度追问,不默认修改当前草稿。
- 不创建新草稿。
- 不清空当前有效 `BotContext`
### 10.19 30 分钟过期
前置:存在一条 `awaiting_confirm` 草稿,但 `expires_at` 已过期。
输入:
```text
改成明天下午
```
期望:
- 不直接修改旧草稿。
-`FailureRecord(follow_up_expired)` 或 OperationLog 过期记录。
- 按新输入处理,或追问老板“上一条草稿已过期,你是想修改刚才那条还是新建一条?”。
### 10.20 PostgreSQL AI 记忆读写失败
前置:PostgreSQL 保存 `secretary_messages``BotContext` 失败。
输入:
```text
确认
```
期望:
- 不继续依赖内存确认草稿。
- 不转换任务或提醒。
- 返回 `reply_type=error`
-`FailureRecord(memory_store_failed)`
### 10.21 程经理确认流转
前置:草稿 `route_type=manager_confirm_required``draft.need_manager_confirm=true`
输入:
```text
确认
```
期望:
- 老板确认后草稿进入 `NEED_MANAGER_CONFIRM`
- 清空老板侧 `BotContext`
- 不直接调用 `TaskAdapter` 转换任务。
- 程经理确认后草稿进入 `CONFIRMED`,再转换为任务并进入 `CONVERTED`
## 11. Review 重点
- 是否所有入口都走 `POST /api/secretary/handle-message`
- 是否用 `source + message_id` 做幂等,避免重复调用模型和重复创建草稿。
- 是否幂等命中时不会重复追加 PostgreSQL `secretary_messages``SecretaryMessage`
- 是否实现 30 分钟上下文,但没有无脑吞掉所有消息。
- 是否 `BotContext.status` 使用明确状态,并且只绑定未确认草稿。
- 是否 `BotContext` 字段按本 spec 集中字段表实现。
- 是否低置信度时追问老板,而不是默认修改当前草稿。
- 是否 `ai_drafts.status` 和非草稿处理状态已经拆清,`need_more_info/answered` 不误落草稿表。
- 是否所有 AI 输出都做 JSON 校验。
- 是否 `qa/realtime_qa/note/need_more_info/unsupported` 没有误生成草稿。
- 是否普通任务不强制时间。
- 是否提醒缺时间会追问。
- 是否 AI 只生成草稿,不直接执行。
- 是否禁止“已通知、已创建、已发送”等执行语义。
- 是否禁止编造老板没说过的业务结论。
- 是否 receiver 解析失败时保留草稿并标记 `missing_person_mapping`
- 是否记录 PromptContext 和 ModelCallLog。
- 是否 PromptContext 包含公司背景摘要、老板风格、业务规则、角色别名、当前上下文、待确认草稿摘要、最近消息摘要和版本号。
- 是否模型超时/百炼失败最多重试 1 次,仍失败写 `FailureRecord(ai_model_failed)`
- 是否非老板入口完全绕开 AI。
- 是否为任务、提醒、通知留下 adapter 边界。
- 是否明确区分 `realtime_qa``unsupported`,实时数据类固定走 `realtime_qa`
- 是否把 `command` 作为消息预分类而不是最终 `intent`
- 是否追问消息也保存为 `SecretaryMessage`
- 是否 `missing_person_mapping` 会阻止正常确认。
- 是否不同 `draft_type` 的确认阻塞字段已明确。
- 是否 `reason` 必填且只进入日志/调试,不直接展示给老板。
- 是否 `DRAFTING` 没有作为持久化草稿状态使用。
- 是否 `missing_fields` 使用对象数组,并能区分字段和失败原因。
- 是否 command 命中但无草稿时返回普通说明,不抛异常。
- 是否 `failure` 返回结构只在错误降级时出现。
- 是否 `failure.type``FailureRecord.failure_type` 使用同一枚举,并有明确触发条件。
- 是否对话消息、上下文回顾和 `BotContext` 快照已落 PostgreSQL,而不是只放内存。
- 是否 `secretary_messages` 只追加、`bot_contexts` 可覆盖、`bot_context_snapshot` 不作为最新状态。
- 是否 PostgreSQL 作为唯一事实源,AI 记忆表不替代正式草稿、任务、提醒、通知和反馈表状态。
- 是否 PostgreSQL AI 记忆读写异常时会降级并写 `FailureRecord(memory_store_failed)` 或应用日志/告警。
- 是否草稿 `CONFIRMED / CONVERTED / CANCELLED` 后立即清空上下文,后续修改不再直接改原草稿。
- 是否 `manager_confirm_required``NEED_MANAGER_CONFIRM -> 程经理确认 -> CONFIRMED -> CONVERTED`,不会绕过程经理确认。
- 是否转换失败写 `FailureRecord(draft_convert_failed)`,且不回滚老板确认。
- 是否上下文回顾默认范围为最近 10 条消息、当前待确认草稿和最近 3 条已确认/已转换草稿。
- 是否 `/api/feishu/callback` 的验签、去重和鉴权没有写进本模块。