背景:需求澄清是一个被低估的成本中心
做过研发的人都知道,需求评审会结束后,研发真正动手写代码,往往还要经历一轮”追着产品问”的过程。这些问题有时候很小(一个按钮文案),有时候很大(整个业务逻辑是否成立)。问题问得晚,代价是已经写了一半的代码要返工;问题没问到,代价是上线后出 bug 或功能偏差。
我们在 Codelix 中尝试用 AI 来解决这个问题。本文记录了从立项到多轮迭代的全过程,包括遇到的坑、做的取舍,以及最终效果。
第一阶段:打通主链路
最初的设计
最简单的思路:拿到 TAPD 需求单 → 调 AI 分析 → 输出澄清点列表 → 人工确认。
我们接入了内部的 Knot 智能体,它有联网和 RAG 能力,能结合代码库上下文分析需求。技术选型:
| 层次 | 方案 |
|---|---|
| 后端 | Go,集成进 agent-server |
| Knot 通信 | Go SSE 客户端 |
| 存储 | 本地 JSON 文件,按 storyId 分目录 |
| 前端 | React SPA,新增路由 /clarification |
第一个版本跑通了,但踩了几个坑。
SSE 解析的坑
Knot 的响应协议是 SSE,但格式和标准 SSE 不完全一样:
- 标准格式是
data: {...}(有空格),Knot 是data:{...}(无空格) - 某些单行内容超过
bufio.Scanner默认的 64 KB,直接截断导致 JSON 解析失败 - Knot 有时在一次响应里输出多个 JSON 数组,有时还用
```json ```代码块包裹
解决方案:
- 扫描缓冲区从 64KB 扩到 4MB
- 解析时优先提取代码块,再回退到文本中所有
[...]候选,取首个可解析的非空数组 - 兼容
content、delta、delta.content、delta.text四种字段格式
还有一个问题:LLM 生成的 JSON 字符串里有时含未转义的英文双引号,导致反序列化失败。解决方法是先做一次轻量的 sanitizeJSONQuotes 修复再重试。
TAPD URL 兼容性
TAPD 需求单有两种 URL 格式:
- 旧版:
https://tapd.woa.com/{workspaceId}/prong/stories/view/{storyId} - 新版:
https://tapd.woa.com/tapd_fe/{workspaceId}/story/detail/{storyId}
返回结构也有四种变体:带 Story 嵌套、直接字段 map、data 数组等。评论拉取失败时不阻断主流程,降级为”无评论”继续处理。
版本管理:patch-based 版本链
澄清点会被多人编辑,需要保留完整历史。我们设计了 patch-based 版本链:
versions[0]是 Knot 分析的全量 baseline,只读- 后续每次编辑追加 patch 版本,只记录 delta
Resolve(N)从最近 checkpoint 起重放 patch 链还原到版本 N- patch 版本累计 10 个时,自动落一次全量 checkpoint
并发控制:前端提交 baseVersion,后端做 CAS 校验。不同 item 的修改自动 merge,同一 item 冲突返回 409,提示用户刷新重试。
Knot 凭证安全设计
用户首次使用时需要输入 Knot API Token,但不能把 token 直接存到 localStorage——在公用机上会造成泄露。
最终设计是服务端会话:
- 用户输入 Token + RTX 用户名 → 后端创建
clarification_session,写入HttpOnlycookie(SameSite=Lax, Secure, Max-Age=15552000) - 浏览器只保存 cookie,不保存 token 明文
- 会话有效期 6 个月,到期或 Knot 返回
401/403时立即失效 - 检测到
token_invalid时服务端主动清空 session 并清 cookie,前端自动弹凭证重新输入弹窗
这里有个细节:平台化后产物生成阶段使用 claude-internal CLI 时,需要把 knotApiToken 透传给远程机作为 CLI 鉴权凭证(映射为 CODEBUDDY_API_KEY)。所以 token 必须保留在服务端 session 里直到会话过期,不能在完成分析后就清掉。
第二阶段:平台化——从本地工具到协作平台
核心链路跑通后,几个问题开始暴露:
- 数据存在本地 JSON 文件,多人协作时一致性难保证
- 完成澄清后缺少产物生成的闭环
- 没有历史记录和指标
于是启动平台化改造:所有澄清数据迁入 platform-server 的数据库,本地不再是真源。
数据模型
核心表:
clarification_runs:每次分析一条记录,含状态机和is_primary标志clarification_items:当前最新澄清点状态clarification_item_versions:完整编辑历史,每次变更记before_snapshot + after_snapshot(field_diffs读时计算,避免三者不一致)clarification_item_comments:每条澄清项的讨论回复clarification_artifacts:生成产物(summary、tech_doc、各端技术文档、流程图、协议、tapd_update 等)clarification_knowledge_records:完成澄清后的知识沉淀
状态机
1 | editing → ready_to_complete → completing → completed |
所有澄清点非 pending 时自动推进到 ready_to_complete,允许点击”完成澄清”。完成后允许 reopen 重新进入编辑。
完成澄清的并发控制
多人协作时,”完成澄清”只能有一个人成功触发一次。用 DB CAS 实现:
1 | UPDATE clarification_runs |
RowsAffected = 0 说明已有其他人先点,前端提示”已有其他人正在完成澄清”。
大模型调用边界的划分
这里有一个关键设计决策:两类 LLM 调用必须严格分开。
| 场景 | 调用来源 | 原因 |
|---|---|---|
| 首次分析 TAPD 需求 | Knot | 需要联网、RAG、内部知识召回 |
| 完成澄清后的产物生成 | 内部 LLM(claude-internal) | 只基于已确认澄清结论,避免引入未确认内容 |
协议是唯一例外——它需要结合真实协议仓上下文,允许单独调用 Knot 生成(见后文)。
产物生成
完成澄清后异步生成:澄清总结 + 整体技术文档 + 各端技术文档(iOS/Android/Kuikly/Web/后台,只在该端有已决策条目时生成)+ 业务流程图(Mermaid)+ 后台协议 + TAPD 正文更新内容(tapd_update)。
前端 4s 轮询直到所有产物就绪。单个产物失败不阻断其他产物。
TAPD 回写:状态必须持久化
完成澄清后,支持将澄清结论评论到需求单,并把 tapd_update 产物写回需求正文(展示左右 diff,人工确认后写回)。
这里有个重要设计:回写状态必须持久化,不能只存前端 UI 状态。
1 | tapd_comment_sync_status: pending | in_progress | synced | skipped |
用户中途关页面后重新进入,应能恢复到正确状态:
- 弹窗未选就关页面 → 入口还在(
pending) - 点了自动更新,后端未完成就关页面 → 后端继续跑;回来看到
synced或pending(失败可重试)
有一个严格约束:diff 右侧只允许来自 tapd_update artifact,禁止用 tech_doc 或 summary 等替代写回。这些产物的生成目标不同,混用会导致需求正文变成技术文档风格。
第三阶段:体验升级——从「找问题」到「辅助决策」
问题的核心
平台化之后,真正使用时发现 AI 输出的澄清点质量有根本性问题。典型的旧格式:
阈值确定责任方和灰度策略未明确。
这种描述能指出风险,但产品拿到这句话之后还是不知道怎么决策——它指出了不确定性,但没有给决策路径。
推荐答案和选项
新设计的澄清点包含:
- 推荐答案(结论态表达)
- 推荐理由 + 置信度
- ABCD 选项,每个选项有描述和推荐度
- 每个选项下的快捷动作:
回复并澄清(自动写入评论)和AI 更新澄清点
1 | { |
rewriteDescription 是 AI 给出的”可以直接替换澄清点原文”的结论态文案。点击 AI 更新澄清点 时不是一键直替,而是弹出确认框,提供三个选项:
- 取消:关闭,不做改动
- 直接使用:用
rewriteDescription替换原文 + 自动标记为已澄清 + 写系统评论留下来由(🤖 采用 AI 推荐方案【A】:...) - 编辑再替换:把
rewriteDescription预填进编辑框,用户确认后再提交,不自动设为已澄清
这个交互设计的考量是:把”一键直替”变成”可控的二次确认”,避免用户因为误点进入无法回退的状态。
归属体系扩展
原始归属只有”后台开发”,实际上算法团队(推荐排序、模型策略、置信度评估)与后台开发差异很大。本轮新增:
算法:独立于后台开发,用于 AI 能力、推荐系统相关澄清点数产:数据产品团队,负责上报方案相关确认
同时 Knot prompt 新增了上报专项检查,6 类触发条件:
- 关键新增页面未提及曝光上报
- 重要点击行为未提及点击上报
- 描述了上报点但未给出上报 ID
- 上报触发时机含糊(”上报播放”未区分开始/结束/心跳)
- 上报字段不完整
- 已有类似点位,需确认复用或新增
命中任一条件,自动生成 module=数据上报, attribution=["产品","数产"] 的澄清项。
协议生成:从推测到 Knot 生成
原来协议产物是用当前上下文推测的,容易出现编造接口形态、字段结构不符合真实协议仓、忽略历史约束等问题。
本轮把 api_contract 从普通产物链路拆出,单独调用 Knot 生成,传入:需求原文 + 已确认澄清结论 + 相关后台代码 + 协议仓上下文 + 历史修改记录。协议内容中明确标注”待确认项”,不允许把不确定内容写成确定结论。
Knot 模型一致性
一个容易被忽略的问题:发起分析时选的 Knot 模型,在重新分析和协议生成时应该复用。否则用户会看到同一个 run 里不同阶段的产物风格迥异。
实现:clarification_runs 增加 knot_model 字段,分析时写入,重新分析和协议生成时从 run 读取。老数据没有该字段时,fallback 到系统默认 Knot 模型。
前端详情页显示”本轮分析模型:deepseek-v4-flash”,便于追溯。
推荐确认人的坑
让 Knot 基于代码提交历史输出推荐开发人员,一开始踩了个明显的坑:
Knot 把需求单处理人(产品经理 morsonxie)归到了 Kuikly 客户端开发。
根因:Knot 在代码线索不足时会回退到需求单的处理人/关注人,而这些经常是产品经理。
修复:prompt 明确约束——只推荐开发角色(后台/算法/iOS/Android/Kuikly/Web)、推荐依据必须是代码提交历史和模块归属、证据不足时输出”暂无可靠推荐”而非编造。
澄清项评论
每条澄清点下方有独立的讨论回复区(clarification_item_comments):
- 按新到旧排序,展示作者 · 时间 · 内容
- 卡片底部提供回复输入框,回车或点「回复」提交
- 生成澄清总结(
summaryartifact)时,会读取每条澄清点的回复评论,将用户在回复里补充的最终决策、原因或约束吸收进结论,避免”只复述原始问题”
工作量变化与 ROI 提醒
每条澄清点可以填写:
effort_delta_d:工作量变化(天数,可为负)effort_reason:原因说明roi_review_required:是否需要产品评估 ROI
当本次澄清累计开发工作量变化 > 0.5D 时,页面顶部显示显著警告横幅:
本次澄清带来超过 0.5D 的开发工作量变化,请提醒产品评估 ROI
这个信息同时出现在”完成澄清”按钮附近、TAPD 评论摘要里、以及数据看板的 ROI 风险报表中。
参考文档来源
每条澄清项下方支持展示 AI 分析时参考的来源(sourceRefs):
1 | { |
支持 iwiki / gongfeng / tapd / doc / code 五种类型。Knot prompt 要求输出 sourceRefs 可选字段,解析后落库,前端在澄清点卡片中展示”参考来源”折叠区块。
产物重试
产物生成失败后,不需要重新完成整轮澄清,支持:
- 单个失败产物”重试”按钮
- 产物区”重试全部失败产物”
- 重试不新建同类型 artifact 记录,直接更新
status,保留artifactId不变,避免前端轮询引用失效 retry_count/last_retry_at记录历史
并发保护:同一产物 pending 时不允许重复触发重试。
技术文档改为手动触发
完成澄清后,原来整体技术文档和各端技术文档(iOS/Android/Kuikly/Web/后台)会自动批量生成,token 成本较高,且模型质量口径和协议不一致(协议走 Knot,技术文档走内部 LLM)。
本轮调整:
tech_doc及各端tech_doc_*从自动批量生成中剔除,改为idle状态预插入产物列表- 产物区显示”未生成(手动触发)”+ 「生成」按钮,用户按需单独触发
- 技术文档同样改走 Knot 生成(和协议对齐),并追加”去过程化”约束(不输出 Knot 的检索/思考过程,直接出正文)
- 自动批量仍保留:summary / tapd_update / flowchart / api_contract
这个决策的核心是:技术文档的质量需要代码库上下文,Knot 更合适;但每次完成澄清后全自动生成 5-6 份文档成本太高,改为按需手动触发可以控制成本。
第四阶段:统计口径和 AI 效果度量
采纳率的坑
早期采纳率直接按条目算,同一个需求单重新分析几次、每次都上报,会重复计数。
修正口径:
- 按
story_id去重,只取最近一次上报的结果 - 公式:
采纳率 = (采纳 + 搁置) / (采纳 + 拒绝 + 搁置),pending 不进分母 - 搁置视为”AI 建议未被否决”,计入分子
技术实现遇到了 MySQL 优化器 bug:用 IN (SELECT COALESCE(correlated_sub1, correlated_sub2)) 嵌套相关子查询时,MySQL 会静默返回空结果集,不报错不警告。改为非相关派生表 INNER JOIN 写法后解决。
漏报率
“漏报”定义为用户在 AI 初始分析之外手动新增的澄清点。
clarification_items 新增 origin 字段(ai / manual),漏报率 = 人工新增澄清点数 / 所有澄清点数,可以持续评估 AI 分析质量的变化趋势。
完成引导弹窗
典型用户行为问题:全部澄清点都确认完了,但忘记点”完成澄清”,导致产物没有触发生成。
解决:当最后一个 pending 澄清点被确认时,立即弹窗引导。触发条件严格限定为:本次更新前 pending > 0,更新后 pending = 0,且 run 未完成。刷新页面、在已完成的 run 上改判某项都不会误弹。
主按钮点击记 complete_now 埋点,进入 AI 效果漏斗统计。
各环节耗时统计
clarification_runs 表新增时间戳字段:
analysis_ms:Knot 分析耗时(毫秒)first_confirmed_at:第一个澄清点被确认的时间artifacts_generated_at:自动批量产物全部生成完成的时间tapd_comment_synced_at/tapd_content_synced_at:TAPD 回写完成时间
这些字段在各状态变更点写入,支持看板展示:平均分析耗时、分析完成→首个确认间隔、完成→自动产物、完成→TAPD 评论等。
数据看板
最终包含三个维度(以纵向 Section 排列,不是 Tab 切换):
质量:采纳率(按需求去重,口径 tooltip 说明)、按归属拆分采纳率、漏报率趋势、工作量 ROI 风险(触发次数 + 累计工作量变化)
效率:各环节耗时均值(分析时长、人工确认时长、产物生成时长、TAPD 同步时长)、完成趋势、流程漏斗(发起→完成→评论→写回)
AI 效果:曝光 → 操作 → 完成漏斗,推荐项 vs 非推荐项点击率,rewriteDescription 直接使用率(区分”直接使用”和”编辑再替换”)
埋点事件:suggestion_shown(曝光,按 session+澄清点 sessionStorage 去重)、reply_and_confirm、rewrite_and_confirm(直接使用)、rewrite_fallback(编辑再替换/无 rewriteDescription 的兜底)、complete_now(完成引导弹窗点击)。取消等未提交动作不记。
第五阶段:体验细节和 UX 打磨
主版本(Primary Run)
多次重新分析后,用户可能有多个 run,但之前确认过的那轮不应该因为”重新分析了一下”就从默认视图消失。
引入 is_primary 字段(clarification_runs.is_primary):
- 首次分析自动设为主版本
- 后续重新分析产生的新 run 默认
is_primary=0,不抢占主版本 - 用户可手动”设为主版本”或”取消主版本”
- 进入详情页时,优先打开主版本;没有主版本时回退到最新 run
顶部”重新分析”按钮增加确认弹窗,明确说明:若当前正在查看主版本,分析完成后不会自动切走当前页面,新 run 只出现在下拉选择器里。
首页筛选:我的需求澄清 / 全部澄清
随着使用增多,首页全量列表变得难以查找。新增双选切换:
- 我的需求澄清:
clarification_runs.created_by = 当前用户,默认选中 - 全部澄清:保持原有全量列表
接口扩展:GET /api/story-clarifications?scope=mine&username=xxx
UI 从工具页升级为 Dashboard
原来详情页是一个功能完整但视觉朴素的”工具页”。本轮参照设计稿做了改版,核心变化:
- Sticky Header:需求标题、当前状态、澄清进度(已确认/总项)、右侧主操作区
- Quick Nav:页内锚点导航(需求总览 → AI 分析摘要 → 澄清点矩阵 → 生成产物 → 需求更新)
- Section 分组:白色卡片 + 浅灰背景 + 统一圆角阴影
- 采纳率指标卡:已采纳 / 不采纳 / 搁置 / 待确认 的计数和简化采纳率
- 澄清点分组:按归属或状态分组折叠,高优先级标签醒目
改版策略是”套设计稿的页面叙事和视觉层级,保留我们现有更强的真实能力”——run/version 双层浏览、item 历史记录、sourceRefs 引用来源、完成澄清后的产物区都保留,不为了贴近设计稿而删掉。
知识沉淀
完成澄清后,异步将已决策条目写入 clarification_knowledge_records,沉淀:
- 澄清点描述、归属、最终决策(adopted/rejected/shelved)
- 决策原因、谁确认的、什么时间
- 工作量变化、ROI 是否触发
- 参考来源 refs
后续可基于这些沉淀做相似需求召回、高频模糊点归类、采纳模式分析。
几个关键设计决策的反思
JSON 是唯一真源,MD 是展示格式:MD 导入导出存在格式损失,不能成为系统真源。版本链、并发控制、历史重建都依赖 JSON 结构。
不允许物理删除澄清项:所有”不处理”的澄清点通过 confirmResult=rejected 表达,在列表中显示删除线。多人协作时如果有人删了一条其他人正在讨论的澄清点,会造成上下文丢失,很难恢复。所有确认状态都应可撤销。
Knot 只做分析,协议是例外:产物生成只用内部 LLM,避免引入未确认内容。唯一例外是接口协议,它需要结合真实协议仓上下文,允许单独调用 Knot 生成。技术文档后来也改走 Knot,但改为手动触发。
完成澄清不是终点,是第二阶段入口:所有澄清点非 pending → 可以完成澄清 → 触发产物生成 → 评论回写 TAPD → 更新需求正文。这条链路里每个节点都需要持久化状态和可恢复设计,不能只靠前端 UI 状态。
小结
这个方向从最初的”AI 找问题列表”演进到”AI 辅助决策工具”,核心变化:
- 澄清点从问题态变为结论态,推荐答案 + ABCD 选项 + rewriteDescription,产品可以直接选择采纳
- 每端输出推荐开发确认人(基于代码提交历史,不是需求单处理人)
- 上报专项检查自动发现需求里缺失的埋点说明
- 协议和技术文档改由 Knot 生成,质量更接近真实
- 澄清项下有独立评论区,总结生成时吸收评论内容
- 工作量变化 + ROI 提醒给产品早预警
- 数据看板让采纳率、漏报率、各环节耗时可量化追踪
最大的教训:功能上线不等于体验完成。第一版打通了主链路,但产品真正拿到 AI 输出后,”这句话告诉我了什么,但我还是不知道怎么决策”的反馈,才是后续所有体验升级的起点。