40 KiB
01_老板 AI 秘书与 AI 草稿
1. 模块目标
本模块负责把老板在飞书私聊或平台入口输入的一句话,交给 AiSecretaryAgent 理解、归类、补上下文,并生成可确认的 AI 草稿或直接回复。
这个模块是老板秘书的大脑,不直接创建任务、提醒或发送通知。所有执行动作都必须走人工确认后的任务、提醒和通知模块。
说明:整体方案中的“事项、需求、安排”在第一版统一归入 task。如果后续单独拆出 requirement 类型,需要先更新本 spec 和数据对象约定。
2. 第一版做什么
第一版核心定位:
老板一句话
-> AiSecretaryAgent 判断意图和上下文
-> 意图归类为 task / reminder / qa / realtime_qa / note / need_more_info / unknown / unsupported
-> 稳定 AiSecretaryResponse JSON
-> 生成 AI 草稿 / 追问 / 直接回复
-> 老板确认、补充、取消或转程经理
- 支持老板通过飞书私聊或平台入口输入文本。
- 统一走
POST /api/secretary/handle-message。 - 判断
task/reminder/qa/realtime_qa/note/need_more_info/unknown/unsupported。 - 生成稳定的
AiSecretaryResponseJSON。 - 对任务和提醒只生成可确认草稿,不直接执行。
- 对普通聊天、实时问答兜底、普通记录直接回复或记录。
- 支持 30 分钟内补充、重说、确认、取消和转程经理。
- 支持低置信度追问和最多 3 轮追问收敛。
- 使用 PostgreSQL 持久化老板秘书当前会话记忆、消息记录和
BotContext。 - 记录模型调用、提示词版本、操作日志和失败记录。
3. 第一版不做
- 不做员工通用 AI 工作台。
- 不让程经理或普通员工通过机器人派活。
- 不让 AI 自动创建任务、提醒、通知或外部系统操作。
- 不做复杂多轮通用聊天记忆、跨账号记忆或长期画像;第一阶段只持久化老板秘书当前会话所需的对话记忆和草稿上下文。
- 不把公司背景库全文每次塞进模型,只使用
PromptContext中的压缩摘要和版本。 - 不做实时行情、交易、数据库、后台系统自动查询和自动判断。
- 不把普通记录做成独立知识库、向量库或长期记忆,只保留当前演示闭环需要的消息记录。
4. 核心流程
4.1 统一入口
所有老板消息统一走:
POST /api/secretary/handle-message
飞书回调、/api/demo/boss-message、未来平台入口都转发到该入口。
处理顺序:
记录原始消息
-> 按 source + message_id 做幂等检查
-> 校验发送人角色
-> 非老板直接固定回复并记录
-> 读取 BotContext,外部 context 只作为参考
-> 判断本次消息是 follow_up / new_request / qa / realtime_qa / note / unknown / 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 建议取值:
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 / unknown / unsupported / command。 - 老板说“换成东东”“改到明天”“刚才那个不要了”,优先修正当前草稿。
- 老板说“另外再安排一个”“新建一个”“再帮我记一个”,创建新草稿。
- 老板说“确认”“就这样”“发给他”,确认当前草稿。
- 老板说“算了”“取消”“不要了”,取消当前草稿并清空上下文。
- 老板问“刚才我说了什么”“总结一下”,做上下文回顾,不修改草稿。
- 超过 30 分钟,上下文标记
expired,下一条默认按新输入处理;如果老板明显仍在说上一条草稿,可先提示“上一条草稿已过期,我按新请求处理”。 - 老板确认、取消或草稿转换为任务/提醒后立即清空上下文,不受 30 分钟窗口约束。
- 30 分钟窗口只绑定未确认草稿;草稿进入
confirmed / converted / cancelled / superseded / expired后,后续“改一下刚才那个”不能再修改原草稿,只能作为已发布任务/提醒变更请求或第一阶段能力外请求处理。 - 多次
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_confirmation,或生成带missing_fields的终态草稿后收到下一条新消息。
5. 数据对象
本模块主要涉及:
AiSecretaryResponseBotContext / ConversationSecretaryDraft / ai_draftsSecretaryMessage / notePromptContextModelCallLogOperationLogFailureRecordFeishuEventLog
字段事实源以 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字段第一阶段只做读取展示和调试,不作为复杂查询主路径。
最小字段建议:
{
"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|unknown|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:
{
"intent": "task|reminder|qa|realtime_qa|note|need_more_info|unknown|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/unknown/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/unknown/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 | 信息不足但属于可处理范围,主动追问 |
| unknown | 暂时无法判断意图,只允许澄清或留痕 |
| unsupported | 超出第一版能力,解释为什么不能做,不追问执行字段 |
优先级和兜底规则:
- 天气、新闻、股价、行情、今日走势、实时政策等需要实时数据的输入,固定归为
realtime_qa。 realtime_qa.answer固定使用模板:“这个需要实时数据查询,第一版暂不作为正式能力;我不会把它生成任务或提醒。”- 超出第一版能力但不属于实时数据类的输入,归为
unsupported。 unsupported.answer应说明当前入口只处理老板事项草稿、提醒草稿、普通问答和普通记录,不追问执行字段。unknown.answer只能澄清或提示补充,不得创建事项、提醒或通知。
示例:
- “安排一下” ->
need_more_info,追问安排谁、做什么。 - “帮我分析今天的股票走势” ->
realtime_qa,提示第一版不做正式实时数据查询或投资判断。 - “帮我写一份完整年报” ->
unsupported,说明超出第一版老板秘书入口能力。 - “记一下,今天先别催东东” ->
note,只保存记录。
5.4 草稿状态
ai_drafts.status 只表示已经落 PostgreSQL 草稿表的草稿状态。非执行意图(qa/realtime_qa/note/unknown/unsupported)和纯追问结果(need_more_info)不得进入事项、提醒或通知闭环;如需留痕,只保存 SecretaryMessage、BotContext、model_call_logs 或最小草稿记录。
ai_drafts.status 以 docs/contracts/状态流转约定.md 为准,核心状态:
pending_confirmation
awaiting_follow_up
confirmed
converted
cancelled
answered
superseded
expired
parse_failed
对老板可见时可以简化为:待确认、等待补充、已确认、已取消、已失效、已完成、已失败。
状态含义:
pending_confirmation:草稿可确认,等待老板确认、补充/重说或取消。awaiting_follow_up:老板点击补充/重说后,等待 30 分钟内下一条消息。confirmed:确认链路已完成,可以交给任务或提醒模块转换。converted:草稿已经成功转换为任务或提醒;复杂事项已创建pending_manager_confirm事项壳也视为转换完成。cancelled:老板取消草稿。answered:已直接回复或留痕,不转换为任务或提醒。superseded:已被补充/重说生成的新草稿替代。expired:补充/重说等待超时,旧确认卡片不可继续使用。parse_failed:草稿或 AI 解析过程失败,仅用于调试追踪。
非草稿处理状态:
need_more_info:本轮只追问老板,不创建ai_drafts;追问内容保存为SecretaryMessage(intent_type=need_more_info),上下文状态为BotContext.status=awaiting_more_info。answered:qa/realtime_qa/note/context_summary/unknown/unsupported的处理结果,不转换为任务或提醒,不影响当前有效BotContext。- 追问 3 轮仍补不全、但系统决定生成带
missing_fields的草稿时,模型输出必须从need_more_info转为task或reminder,创建pending_confirmation草稿,并由确认阻塞规则控制能否确认。
DRAFTING 不作为第一版 ai_drafts.status 持久化枚举。实现中如需表达“AI 已决定生成草稿但还在校验/解析”的过程态,只能作为方法内的内存标记或局部变量;落库状态必须来自 状态流转约定.md。
草稿确认卡片生命周期:
- 每个草稿同一时间只能有一张有效确认卡片,对应
active_card_notification_id。 - 老板点击“补充/重说”后,原草稿进入
awaiting_follow_up,原确认卡片应失效。 - 30 分钟内收到补充消息后,新草稿
parent_draft_id指向原草稿,原草稿进入superseded。 - 超过 30 分钟仍未收到补充消息时,原草稿进入
expired,老板下一条消息按新输入处理。 - 飞书回调处理确认、取消、补充/重说前,必须校验回调来自当前有效卡片;旧卡片只记录事件,不写业务对象。
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 统一消息入口
POST /api/secretary/handle-message
输入包括:
sourcemessage_idsender.open_idsender.namesender.roletextcontext.has_pending_draftcontext.pending_draft_id
context 是外部调用方可选提示。后端必须以本地 BotContext 为准:
- 调用方没传 context 时,后端自己查询 BotContext。
- 调用方传了 context 但和 BotContext 不一致时,以 BotContext 为准。
- 外部 context 不能绕过草稿归属、过期时间和权限校验。
返回包括:
intentreply_typeanswerdraftquestionscontext_summaryfailure
failure 只在不可恢复错误或降级时返回。结构建议:
{
"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. 状态流转
老板输入
-> AI 解析成功
-> draft_preview / follow_up_question / plain_answer
-> 老板补充
-> 新草稿或修订草稿
-> 老板确认
-> direct_after_boss_confirm: confirmed -> TaskAdapter / ReminderService 转换 -> converted
-> manager_confirm_required: confirmed -> 创建 pending_manager_confirm 事项壳 -> converted -> 程经理确认后通知接收人
草稿确认后的边界:
direct_after_boss_confirm:老板确认成功后,草稿进入confirmed,转换成功后进入converted。manager_confirm_required:老板确认成功后,草稿进入confirmed;转换时先创建tasks.status=pending_manager_confirm的事项壳,并给程经理发送待确认提醒;事项壳创建成功后草稿进入converted。- 程经理确认复杂事项时,补齐接收人、内容和通知信息,使事项进入
pending_notify,再通知接收人。 - 任务/提醒转换失败时,不回滚老板确认;草稿保持
confirmed,写FailureRecord(draft_convert_failed),支持后台重试或人工处理。 - 老板确认后要清空老板侧
BotContext;后续经理确认不再依赖老板 30 分钟上下文。 confirmed / converted / cancelled / superseded / expired后,01 不再直接修改原草稿;老板后续说“把刚才发出去那个改一下”,应识别为任务/提醒变更请求,第一阶段未接入变更能力时回复暂不支持或引导到任务模块。
直接回复流转:
qa / realtime_qa / note / unknown / context_summary
-> answered 处理结果
-> 不进入事项、提醒或通知闭环,不清空当前有效 BotContext
context_summary 也必须保存为一条 SecretaryMessage(intent_type=context_summary),便于调试页回看“老板什么时候要求回顾、系统回了什么”。
失败流转:
模型调用失败
-> 重试 1 次
-> 仍失败则不创建可确认草稿
-> FailureRecord(ai_model_failed)
JSON 非法 / schema 校验失败
-> 不创建可确认草稿
-> 可选创建 parse_failed 草稿仅用于调试;如果无法创建最小草稿,failure_records.target_type 使用 model_call_log
-> FailureRecord(ai_parse_failed)
parse_failed 草稿不能确认、不能转换为任务/提醒、不能发送通知,只能用于调试追踪。
取消流转:
pending_confirmation
-> cancelled
-> 清空 BotContext
awaiting_more_info 且还没有 ai_drafts
-> 清空 BotContext
-> 记录 OperationLog(cancel_context)
复杂事项壳 pending_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 任务草稿
输入:
让东东跟进一下合同
期望:
- 生成
task草稿。 - 不因为没有时间反复追问。
- 接收人候选包含东东。
should_create_draft=true。
10.2 提醒草稿
输入:
明天上午 9 点提醒东东准备咖啡
期望:
- 生成
reminder草稿。 scheduled_at使用Asia/Shanghai。- recurrence 为
none。
10.3 补充上一条
前置:存在一条待确认 task 草稿,内容为“让东东跟进合同”,没有明确 due_at 或 schedule_text;该草稿不是提醒草稿。
输入:
改成明天下午
期望:
- 判断为
follow_up。 - 修订上一条草稿,不新建任务。
- 更新该任务草稿的
due_at或schedule_text,不修改reminder.scheduled_at。
10.4 新建例外
前置:存在待确认草稿。
输入:
另外再安排一个,让行政订会议室
期望:
- 判断为
new_request。 - 新建草稿。
10.5 取消
输入:
算了,刚才那个不要了
期望:
- 取消当前草稿。
- 清空上下文。
10.6 非老板访问
输入:普通员工私聊机器人。
期望:
- 固定回复“当前入口是老板秘书,如需处理事项请进入平台。”
- 不调用 AI。
- 写失败和操作日志。
10.7 追问信息不足
输入:
安排一下
期望:
- 判断为
need_more_info。 - 不生成任务或提醒。
- 追问“安排谁、做什么”等关键问题。
10.8 实时问答兜底
输入:
帮我分析今天的股票走势
期望:
- 判断为
realtime_qa。 - 不生成草稿。
- 回复固定模板:“这个需要实时数据查询,第一版暂不作为正式能力;我不会把它生成任务或提醒。”
10.9 普通记录
输入:
记一下,今天先别催东东
期望:
- 判断为
note。 - 只保存消息记录。
- 不创建草稿、不通知任何人。
10.10 接收人解析失败
输入:
让小王处理一下合同
前置:PersonResolver 无法识别“小王”。
期望:
- 草稿可以生成。
receiver_text=小王。missing_fields包含{ "field": "receiver", "reason": "missing_person_mapping", "raw_value": "小王" }。- 正常确认被阻止,回复老板需要先补齐人员映射。
10.11 能力外请求
输入:
帮我写一份完整年报
期望:
- 判断为
unsupported。 - 不生成草稿。
- 不追问执行人、时间等任务字段。
- 说明当前入口只处理老板事项草稿、提醒草稿、普通问答和普通记录。
10.12 上下文回顾
输入:
我刚才说了什么
期望:
- 返回
reply_type=context_summary。 - 返回
context_summary,默认摘要最近 10 条消息、当前待确认草稿和最近 3 条已确认/已转换草稿。 - 不创建新草稿,不覆盖当前待确认草稿。
- 保存
SecretaryMessage(intent_type=context_summary)。
10.13 无草稿 command
前置:当前没有待确认草稿。
输入:
确认
期望:
- 命中 command,但不抛异常。
- 不创建草稿。
- 返回
reply_type=plain_answer。 - 回复“目前没有待确认的草稿”。
10.14 错误降级
前置:模型返回非法 JSON。
期望:
- 不进入任务、提醒、通知流程。
- 写
ModelCallLog和FailureRecord(ai_parse_failed)。 - 返回
reply_type=error。 answer=暂时没处理好,请稍后再试。failure.type=ai_parse_failed。- 如落
parse_failed草稿,仅用于调试,不能确认或转换。
10.15 低置信度追问
前置:存在一条待确认任务草稿。
输入:
这个换一下
期望:
- 如果无法判断是修改接收人、时间、措辞,还是切换到新事项,不默认修改当前草稿。
- 返回
intent=need_more_info。 - 追问“你是想修改刚才那条草稿,还是新建一条?具体想换什么?”。
10.16 已发布后修改
前置:上一条草稿已经 converted,BotContext 已清空。
输入:
把刚才那个改成明天下午
期望:
- 不修改已转换草稿。
- 识别为任务/提醒变更请求或第一阶段能力外请求。
- 第一阶段未接入变更能力时,回复“刚才的事项已经发布,第一阶段暂不支持直接修改已发布任务;我可以帮你重新整理一条补充说明。”。
10.17 消息幂等
前置:同一个 source + message_id 已处理成功并生成草稿。
输入:同一消息再次进入。
期望:
- 不重复调用模型。
- 不重复创建草稿。
- 不重复追加 PostgreSQL
secretary_messages或SecretaryMessage。 - 返回上一轮处理结果或已存在草稿引用。
10.18 待确认草稿中的普通聊天
前置:存在一条待确认任务草稿。
输入:
这个措辞是不是太硬了?
期望:
- 判断为
qa或低置信度追问,不默认修改当前草稿。 - 不创建新草稿。
- 不清空当前有效
BotContext。
10.19 30 分钟过期
前置:存在一条 awaiting_confirm 草稿,但 expires_at 已过期。
输入:
改成明天下午
期望:
- 不直接修改旧草稿。
- 写
FailureRecord(follow_up_expired)或 OperationLog 过期记录。 - 按新输入处理,或追问老板“上一条草稿已过期,你是想修改刚才那条还是新建一条?”。
10.20 PostgreSQL AI 记忆读写失败
前置:PostgreSQL 保存 secretary_messages 或 BotContext 失败。
输入:
确认
期望:
- 不继续依赖内存确认草稿。
- 不转换任务或提醒。
- 返回
reply_type=error。 - 写
FailureRecord(memory_store_failed)。
10.21 程经理确认流转
前置:草稿 route_type=manager_confirm_required,draft.need_manager_confirm=true。
输入:
确认
期望:
- 老板确认后草稿进入
confirmed。 - 清空老板侧
BotContext。 - 创建
tasks.status=pending_manager_confirm的事项壳,不直接通知最终接收人。 - 程经理确认后,事项壳进入
pending_notify并通知接收人;草稿在事项壳创建成功后进入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/unknown/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 / superseded / expired后立即清空或失效上下文,后续修改不再直接改原草稿。 - 是否
manager_confirm_required走confirmed -> 创建 pending_manager_confirm 事项壳 -> converted -> 程经理确认事项壳 -> pending_notify,不会绕过程经理确认。 - 是否转换失败写
FailureRecord(draft_convert_failed),且不回滚老板确认。 - 是否上下文回顾默认范围为最近 10 条消息、当前待确认草稿和最近 3 条已确认/已转换草稿。
- 是否
/api/feishu/callback的验签、去重和鉴权没有写进本模块。