做AI邮件助手,最憋屈的往往不是模型效果拉胯,而是“明明已经自动处理了,用户还是不敢信,甚至投诉乱回复”。我参与某邮件产品团队的项目时,前期把宝全押在“全自动总结+自动代回复”上,上线后数据却很难看。AI代发出去的邮件被用户撤回、被收件人当成垃圾营销,后台体验数据里“已由AI代理回复”的提示带来的不是惊喜,而是惊吓。
后面复盘结论很统一:AI在邮件场景里缺的不是聪明,是确定性。用户要的也很简单——由他决定什么时候让AI介入,并且每一步结果都看得见、改得动。于是就有了这份PRD的重新设计,核心是把AI从“抢答者”变成“被提问者”,新增一套完整的手动触发机制来驱动AI总结与回复。这条链路看起来只是加个按钮,真正做起来牵扯到任务状态机、上下文编排、Prompt设计、异常兜底、灰度评估,一环扣一环,踩过的坑值得拆开来聊一聊。这篇文章会把整份方案从需求逻辑、接口设计到上线指标完整讲透,适合正在做邮件助手、客服AIGC、或任何“AI代操作”类产品的产品经理、后端研发和测试同学参考。
1. 需求背景与方案选型逻辑
1.1 原来的“全自动代操作”为什么走不通
先说清楚最原始的场景。某邮件产品的用户每天会收到几十封非订阅类邮件,其中大量是会议确认、项目同步、客户问询,内容重复度高、回复套路相对固定。团队最初的设想是做一个全自动的AI邮件助手:后端定时扫描新邮件,命中关键词或规则后自动生成总结,再结合历史回复风格自动生成回复并直接发送。
理想状态下这确实省事,但实际操作中暴露了三个核心问题:
- 用户完全失去控制感。AI什么时候动了邮件、会不会把不该发的发出去,用户一概无法掌控,一旦出现一次误发,信任就彻底崩塌。
- 自动触发的准确率扛不住长尾场景。垃圾广告、陌生客户、多语言混杂、含敏感信息的邮件,规则引擎和模型判断都很容易误判,自动处理越积极,错得就越离谱。
- 犯错的成本远高于省下的时间。发错一封商务邮件,后续解释、撤回、道歉的代价远超手动处理十封邮件。
所以需求重新定调:AI一定要介入,但介入的开关必须握在用户手里。加上手动触发机制,用“人机协同”替代“全自动遥控”,是这个方案最核心的出发点。
1.2 为什么最终选了“手动触发”而不是命令式或半自动
当时内部讨论过三个备选方案,不只是手动按钮这一条路。
方案A是全自动加事后通知,也就是AI处理完再告诉用户“我已经回复了”。这个方案开发量最小,但用户反馈最差。方案B是命令式触发,比如在邮件标题里加“#AI”或“#summary”标记,让系统识别后处理。这个方案看着很极客,实际上学习成本高,用户还得记特殊语法,在手机端输入尤其别扭。方案C就是最终采用的手动按钮触发:用户主动点击“AI总结”或“AI回复”,生成结果后先预览、可编辑,确认后才发送。
三个方案放在一起对比,感受会更直观:
| 对比维度 | 全自动+事后通知 | 命令式标记 | 手动按钮触发 |
|---|---|---|---|
| 用户掌控感 | 低,容易崩塌 | 中,依赖学习成本 | 高,完全自己决定 |
| 误操作风险 | 高,缺少确认环节 | 中,标记容易输错 | 低,必须先预览确认 |
| 开发成本 | 低 | 中,需解析语法 | 中,需做状态轮询 |
| 多端体验统一性 | 好 | 差,输入习惯不统一 | 好,按钮天然适配 |
| 对AI能力容错 | 低 | 中 | 高,结果可编辑 |
选型的判断依据也很直白:邮件处理是高频但非紧急的场景,用户愿意多花十几秒换取“不错、不乱发”的安全感。手动触发牺牲了一点自动化率,换来了信任和可解释性,这笔账非常划算。用户发起、AI生成、用户确认的闭环,才是产品能长期跑下去的结构。
2. 整体流程设计与任务状态机
2.1 从点击按钮到发送邮件的完整链路
手动触发机制设计之前,我先画了一遍端到端流程,把它切成四个阶段:
- 触发阶段。用户在邮件列表或详情页点击“AI总结”“AI回复”按钮,前端记录点击来源和邮箱标识,生成一个全局唯一的requestToken,然后调用后端接口。
- 校验与入队阶段。后端做三件事:校验用户是否有权限、模型配置是否可用、该邮件是否在短时间内重复触发。校验通过后创建AI任务记录,进入待处理队列。
- 处理阶段。编排层负责拉取邮件原始内容、清洗HTML、过滤签名和引用段、执行敏感信息脱敏,然后按邮件长度选择Prompt模板并截断长文本,再调用模型服务生成结果。
- 回传与确认阶段。前端轮询任务状态,拿到结果后展示“摘要卡片”和“回复草稿框”。草稿不是直接发送,而是等用户编辑、确认后,点击“发送”才会真正调发送接口。
其中最关键的设计边界是:AI永远只负责生成草稿,发送权始终保留在用户手上。这一点必须在PRD里写死,否则下游开发很容易“为了体验优化”跳过确认环节,把半成品直接发出去。
2.2 任务状态的定义、流转与数据模型
手动触发机制的稳定性,很大程度上取决于任务状态机的设计。我在方案里定义了六种状态:
| 状态 | 含义 | 进入条件 |
|---|---|---|
| PENDING | 已入队,等待处理 | 用户触发成功后创建 |
| RUNNING | 正在拉取邮件或调用模型 | 队列调度到该任务 |
| SUCCEED | 生成结果成功,等待用户确认 | 模型结果通过校验 |
| FAILED | 处理失败 | 超时、接口异常、内容校验失败 |
| CANCELLED | 用户主动取消 | 用户在等待期间点击取消 |
| EXPIRED | 结果过期 | 草稿生成超过24小时未确认发送 |
状态流转看起来简单,真正要关注的是“超时”和“取消”这两个分支。比如任务卡在RUNNING超过90秒,不能一直转圈让用户干等,必须主动置为FAILED并提示重试;用户点击取消后,如果模型已经返回结果,后端也要把结果丢弃并标记CANCELLED,避免脏数据影响后续统计。
数据模型上,我建议任务表至少包含这些字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| task_id | string | 任务唯一ID,建议UUID |
| user_id | string | 发起用户 |
| email_uid | string | 原邮件唯一标识 |
| mailbox_channel | string | 邮箱渠道标识 |
| trigger_source | string | 触发来源:详情页/列表页/批量操作 |
| request_token | string | 幂等键,防重复触发 |
| status | string | 当前状态 |
| retry_count | int | 重试次数 |
| prompt_version | string | 使用的Prompt版本号 |
| model_name | string | 使用的模型名称 |
| latency_ms | int | 模型调用耗时 |
| error_code | string | 失败错误码 |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |
requestToken是这里最重要的一个字段。用户快速双击按钮、网络重试、前端轮询重发等场景全部依赖它做幂等判断,否则同一个任务会被后端重复执行,浪费模型调用费是小事,重复生成草稿导致状态错乱才是大问题。
3. 触发入口与交互细节设计
3.1 不同入口的交互方案与反馈机制
手动触发机制要覆盖的入口不能只有一个详情页按钮。用户看邮件的习惯差异很大,我按场景拆成了三类入口:
一是邮件详情页的“AI总结”和“AI回复”两个按钮。这是最自然的主入口,用户在阅读邮件时最需要辅助。按钮常驻右上角,点击后立即置灰并展示“正在分析”状态,防止重复点击。
二是邮件列表页的单条操作。用户在列表页勾选一封邮件,从底部操作栏点“AI总结”,不需要进详情页就能快速了解核心内容。这里要注意的是,列表页入口必须与详情页共用同一个任务通道,不能各做一套逻辑,否则同一个requestToken会被生成两次或状态会串。
三是批量多选模式。用户勾选多封邮件后点“批量AI总结”,最多允许一次处理10封,后端会创建多子任务并行处理。交互上要明确展示“第2/10封处理中”的进度,而不是一个笼统的loading,否则用户很快会失去耐心。
用户点击触发按钮后的反馈节奏也做了明确要求:300毫秒内必须给出按钮反馈,包括置灰、文案变化或轻量 loading,超过3秒的任务必须显示进度条或“预计30秒内完成”的预估文案。这是因为模型调用通常需要5到10秒,如果前端没有任何进度反馈,用户会以为系统没响应而反复点击,后面会引发一堆重复任务。
3.2 接口设计与关键参数说明
手动触发入口的前端请求和后端响应需要提前定清楚。核心接口我设计为两个:
POST /v1/ai-mail/trigger,用于创建任务。
{ "request_token": "uuid-xxxx", "email_uid": "msg-192829", "mailbox_channel": "imap_work", "action_type": "summary", "summary_scope": "detail", "reply_tone": "neutral", "need_draft": true, "model_id": "default_v2" }GET /v1/ai-mail/tasks/{task_id},用于轮询任务结果。
{ "task_id": "task-xxx", "status": "SUCCEED", "summary": "会议确认邮件,时间下周三15:00", "draft_content": "收到,会议时间确认无误。", "need_human_confirm": "low", "prompt_version": "summary_v3", "latency_ms": 4500 }这里几个参数的取舍值得单独说。
action_type只有summary和reply两种,分别对应对邮件“总结”和“生成回复”两个诉求,不要在初期做成“summarize_and_reply”这种复合动作,阶段越大画得越糙,用户反而不知道该看哪个结果。
summary_scope有short和detail两个选项,short只生成三行以内摘要,detail允许生成带结构化要点的长摘要。这个参数要允许前端在按钮上通过“长按”或“展开”透出,满足不同阅读深度需求。
reply_tone控制回复语气,可选supportive、neutral、direct三类,对应商务场景里的温和、中立、直接。这个参数是后续Prompt工程的重要输入,后端要原样透传给模型服务,不能在前端被丢弃。
need_draft是一个开关,表示是否生成可编辑的邮件正文草稿。如果用户只想快速看摘要,不打算AI代回,那就不需要生成草稿,能省一轮模型调用成本。
接口层面有一个很重要的约定,就是创建任务和确认发送必须严格分离。确认发送使用独立接口POST /v1/ai-mail/send,并且只接受用户在草稿框里最终编辑过的内容,绝不能在后端把“任务里生成的草稿”直接自动发送。这是整条交互链路里不能妥协的边界。
4. 模型编排与Prompt设计实战
4.1 总结与回复双通道的Prompt结构
手动触发机制确定之后,真正的技术天花板就落在Prompt设计上。这里最重要的经验是:不要试图用一个万能Prompt同时完成总结和回复,两个通道必须独立设计。
总结通道的核心目标是“把一封邮件压缩成用户能快速理解的信息”,指令结构我按这个顺序组织:
你是商务邮件助理。用户需要快速把握一封邮件的核心内容。 请按以下顺序输出: 1. 一句话概括邮件主题 2. 关键信息点,每点不超过20字,最多列5点 3. 需要用户关注的行动项,如果没有输出"无" 注意事项: - 忽略邮件签名、免责声明、广告推广段落 - 只输出中文结果 - 输出格式为纯文本列表回复通道的Prompt更复杂,因为回复要考虑语气、上下文历史、收件人身份,结构上要有更细的约束:
你是商务邮件助理。请基于原始邮件内容,为用户生成一封合适的回复草稿。 背景信息: - 用户与收件人的关系:{relation_context} - 用户期望的语气:{reply_tone} - 历史往来邮件摘要:{history_summary} 原始邮件内容: {email_content} 输出要求: - 开头称呼礼貌,结尾有合适署名占位符 - 核心段落回应用邮件中的关键问题 - 如果原始邮件中存在不明确信息,必须在草稿中标注"[待确认]" - 正文不要超过150字 - 输出纯文本,不要输出JSON或额外说明两个Prompt都刻意要求“输出纯文本”,而不是“输出JSON”。在实际测试中,让模型直接输出tructured JSON虽然方便解析,但用户查看草稿时会看到大括号和字段名,体验非常糟糕。我的方案是让模型输出纯文本,后端再对关键位置做规则补全和格式整理,这样既保留了用户的阅读体验,又能在后端拿到稳定字段,比如把末尾的“[待确认]”提取出来生成提示标签。
4.2 长邮件截断策略与上下文拼接
邮件正文长度差异极大,短则几十字,长则上万字。直接全文塞给模型有两个问题:一是Token上限扛不住,二是越长注意力越分散,摘要质量反而下降。我设计了“保留关键段”的截断策略,而不是简单从开头截取前N个字符。
具体做法分为三步:
第一步,把邮件内容按空行拆成段落,过滤掉签名区、免责声明区、公司模板脚注区,这些区域经常超过正文长度的40%,过滤后文本立刻缩小一大截。
第二步,给段落打重要度分。邮件开头两段通常包含完整背景,结尾前两段通常包含行动项和结论,中间内容往往是在补充细节。打分规则是:标题段落权重3,开头两段权重3,结尾三段权重2,中间段落按是否包含“需要”“请在本周”“答复”“确认”这类行动词加权。
第三步,按分数从高到低选择段落,直至累计字符达到预设窗口。窗口大小按模型Token估算,我预设8000字符,约占2000Token,留出足够余量给Prompt模板和输出。截断完成后要保留一份完整邮件正文存到任务表里,后续用户点击“查看完整原文”时不需要重新拉取。
这背后的逻辑可以类比成读长邮件:人也不会逐字读完全文,先看起头搞清楚背景,再扫结尾找行动项,中间快速跳过。截断策略本质上是把这个阅读习惯显式建模,减少模型被无关段落干扰的概率。
4.3 让模型输出“不确定感”——置信度与人工确认标记
手动触发机制还有一个容易被忽略的设计点:AI生成的结果必须能表达“我并没有把握”。如果模型对邮件的理解本身就不确定,却还违心输出一个自信满满的摘要,用户一旦发现错误,信任立刻归零。
我在Prompt结尾增加了一条输出要求:
最后一行单独输出:confidence:high/medium/low 含义如下: - high:邮件内容清晰,可以放心使用 - medium:部分信息来自推测,建议人工核对 - low:邮件内容残缺或依赖大量推断,强烈建议人工确认后再发送后端拿到confidence标记后,会决定结果卡片上是否展示黄色提醒条。low置信度的草稿,发送按钮还需要二次确认弹窗。这个设计对模型幻觉的约束效果显著,尤其在处理转发链很长的邮件时,模型生成的总结经常和真正的问题不符,有了low标记,用户至少知道“这个结果别直接用”。
5. 异常处理与质量兜底
5.1 超时、并发与限流设计
手动触发机制意味着用户每次点击都会消耗一次模型调用,一旦用户量起来,并发问题必须提前评估。我按10000日活的业务量做了简单估算:假设人均每天触发5次,全天任务量约5万次,分散到10小时,平均QPS约1.4,高峰集中在上午10点和下午3点,峰值约3到4QPS,再考虑到批量触发的一次性并发,后端至少需要支持15QPS的吞吐,预留5倍缓冲比较稳妥。
超时参数我设置为:连接超时5秒,模型调用超时30秒,总体任务超时90秒。选这个数是因为主流模型P95延迟通常不超过8秒,30秒给足余量但又不会让用户等到麻木;90秒覆盖了邮件拉取、清洗、调用、校验的完整链路,超过即FAILED。
限流规则按用户维度控制,每人每分钟最多触发10次,每天最多100次。批量任务按邮件数量折算,一次10封批量任务算10次配额。这样既能防止用户误操作刷爆队列,也能避免某个高频用户的异常行为拖垮整体资源。实际检查时,超过负载的业务节点要及时告警,因为邮件拉取还可能受外部邮箱服务商的接口频控限制,多通道叠加时必须统一监控。
5.2 内容安全与隐私脱敏
邮件是最敏感的数据之一。AI处理邮件时会把正文内容发给模型服务,这个环节必须做脱敏。脱敏规则要覆盖:邮箱地址、手机号、身份证号、银行卡号、IP地址和具体的内部项目代号。
脱敏的实现要在两处做:第一处是在构造Prompt之前,对原始邮件正文执行正则替换;第二处是在模型结果返回后,再扫描一次脱敏标记,防止模型把标记原样抄回来。例如,正则将[email protected]替换为[EMAIL],模型如果只是照抄内容,输出里会残留[EMAIL],后端再替换成“对方邮箱”这类中性表达,避免把真实邮箱展示给用户。
还要额外考虑隐私场景:如果用户触发AI回复的收件人地址本身出现在原文里,生成草稿时,收件人信息只展示在界面上,不允许出现在草稿正文里。这个约束要在Prompt系统指令中明确写入,不是靠规则兜底能解决的。
5.3 失败重试与幂等控制
模型调用不可能100%成功,手动触发场景下的失败处理比自动处理更敏感,因为用户是盯着屏幕等结果的。重试策略我定义为:第一次失败立即自动重试一次,第二次失败进入退避队列,分别延迟1分钟、3分钟、10分钟重试,超过三次则标记FAILED并提示用户手动重新触发。
这里必须强调重试的幂等控制。每次重试都必须携带原始requestToken,后端收到重试请求时,先查任务表里是否存在同token且状态为RUNNING的任务,如果存在就直接复用,不另起新任务。否则一个网络抖动就会导致同一封邮件被处理两三遍,用户会看到多个一模一样的结果卡片,数据统计也会全部失真。
另一个容易踩的坑是模型返回结果“成功但并不合法”。比如返回了空字符串、未按Prompt要求输出confidence、或输出内容只是一段重复文本。这些情况后端要做质量校验,至少要检查非空、长度大于10、不包含明显重复的大段占位符,校验不通过同样走FAILED流程,而不是直接把垃圾内容展示给用户。
6. 测试要点与灰度上线评估
6.1 核心测试用例的设计思路
手动触发机制的功能测试不能只测“正常点击后生成草稿”这条主线。我整理测试用例时,会刻意覆盖几类容易被忽略的边界场景:
| 测试场景 | 用例说明 | 预期结果 |
|---|---|---|
| 正常单封总结 | 点击详情页AI总结按钮 | 生成摘要卡片,状态SUCCEED |
| 超长邮件 | 邮件正文超过20000字符 | 按截断策略生成,不超时 |
| 重复点击 | 用户3秒内连点5次 | 仅创建一个任务,接口幂等生效 |
| 模型超时 | 模拟模型接口等待35秒 | 任务FAILED,前端提示可重试 |
| 模型返回非合规内容 | 返回空字符串或纯标点 | 校验拦截,状态FAILED |
| 多语言邮件 | 英文/日文邮件触发总结 | 输出中文摘要,特殊字符不丢失 |
| 批量触发 | 勾选12封邮件点批量总结 | 超过10封限制,前端拦截提示 |
| 取消任务 | 处理中点击取消 | 任务CANCELLED,结果丢弃 |
| 草稿修改后发送 | 用户增删草稿内容后点发送 | 发送的必须是草稿框最终内容 |
能看出测试重点并没有放在模型生成文本的“质量”上,因为质量是灰度阶段用数据评估的,功能测试阶段要保证的是流程的正确性和系统的容错性。把状态流转和幂等控制测稳,后面迭代调Prompt才敢放手改。
6.2 灰度发布策略与效果指标
手动触发机制上线不能全量铺开,我采用的是四步灰度:内部5人体验小组先跑一周,再开放给1000人白名单,确认稳定后放10%流量,最后观察48小时再全量。每一步的退出条件都要提前定好,不能凭感觉。
评估的核心指标有五项:
| 指标 | 计算方法 | 目标值 |
|---|---|---|
| 任务成功率 | SUCCEED任务数 / 总触发任务数 | ≥ 95% |
| 总结采纳率 | 用户直接复制/采用总结次数 / 总结生成次数 | ≥ 45% |
| 回复修改率 | 生成草稿后被修改后发送的数量 / 回复草稿发送总数 | ≤ 60% |
| 端到端耗时 | 用户点击到结果展示的平均耗时 | ≤ 12秒 |
| 用户主动反馈率 | 提交纠错或负反馈的用户数 / 灰度用户数 | ≤ 0.5% |
如果灰度期间任务成功率低于90%,或负反馈率持续升高,就立即停止放量,回到功能层面排查任务失败原因和Prompt输出质量问题。回复修改率这个指标要看得动态一些:新功能上线初期用户总会习惯性地改几笔,等熟悉之后修改率会自然下降,如果连续两周仍在60%以上,说明Prompt生成内容距离用户真实表达习惯还有差距,需要回炉调参。
这里补充一个估算案例:假如灰度10%覆盖了约1000个用户,人均每天触发5次,单日任务量5000次,当任务成功率从98%下降到90%时,意味着每天多出400个失败任务,对应数百次体验投诉,这种情况下做灰度决策的置信度就非常高了。
7. 落地过程中的避坑心得
7.1 Prompt版本管理比模型参数更重要
手动触发机制跑通以后,团队最容易陷入一个误区:每次效果不好就去调模型参数,或者换模型。我发现真正的杠杆在Prompt版本管理。我要求每一次Prompt改动都要带上版本号,比如summary_v3、reply_v5,每个版本上线前必须用同一批历史邮件做回归测试,不能让模型效果在一个月内悄悄退化而毫无察觉。
我的做法是建立两条Prompt资产:一条是稳定的线上版本,只在固定版本上迭代;另一条是实验版本,跑批量离线回放。只有离线回放的采纳率、置信度分布均优于线上版本,才允许切换。模型参数反而很少动,保持一个默认配置即可,这样问题排查起来会非常快,出问题时只需要对比Prompt版本,不用同时怀疑参数、模型和服务状态。
7.2 不要把模型返回内容直接展示给用户
最初版本里,AI回复草稿就是模型返回原文,结果用户反馈“看起来太像机器写的”。后来排查发现,模型输出的换行、称谓、空行格式非常不稳定,有的回复以“邮件内容如下”开头,有的漏掉了开头称谓。这类问题不适合靠模型“自觉”修正,而是要在后端做一层模板后处理。
我在后端定义了回复草稿的格式化流程:提取模型输出中的正文首行作为第一段,统一添加占位符“尊敬的[收件人]:”,再对段间空行做规整,最后检查末尾是否有署名;没有则加上“[你的名字]”占位符。用户看到的是一个标准商务邮件结构,而不是模型原生输出的半成品,这个体验差异很大。
7.3 全链路日志与任务回放能力
手动触发机制上线后,用户反馈问题比自动模式下多得多,因为用户是主动发起,对结果的预期更高,一旦不满意就会直接提工单。没有全链路日志,排查问题就等于大海捞针。
我在每个任务的创建、调度、拉取邮件、脱敏、截断、模型调用、结果校验、前端轮询八个关键节点都打了一条带taskId的日志。线上出现任何问题,先按taskId把时间轴拉出来,每段耗时和返回值一目了然,三分钟就能定位是模型慢、脱敏正则写错还是前端轮询断了。这个能力在建任务表时就应该规划好,而不是等出了问题再补,否则漏打日志的历史任务会变成永远查不清的悬案。
执行下来还有个体会是:手动触发机制看着像一个不起眼的交互按钮,实际它撬动的是AI产品可控性的设计范式。大模型能力越强,产品上越要把它当成“需要被监护的实习生”,什么时候让他干活、干完的活谁来验收,这些规则必须由产品结构和工程机制来保障。手动触发、状态机、确认发送,本质上都是在解决同一件事——AI生成内容必须建立人类确认闭环,这也是这个方案最值得被其他AI代操作类产品拿走的那部分。后面做类似功能的同学,建议从小范围真实邮件回放开始验证Prompt效果,再投入资源做交互和状态机,能少走不少弯路。