故事点是否可以用AI自动估算?

       本文介绍一个基于 LLM 特征提取 + FAISS 向量检索 + 加权平均的故事点自动估算系统的内部实现原理。

一、故事点估算的问题

故事点(Story Point)是敏捷开发中衡量用户故事相对复杂度的单位。一个团队在做 Sprint Planning 时,需要对每个用户故事给出 1、2、3、5、8、13 这样的 Fibonacci 点数。

传统的做法是Planning Poker——团队成员各自出牌,讨论分歧,最终达成共识。这个过程依赖的是人类的经验和直觉:你之所以认为这个故事是"5 个点",是因为你以前做过类似的故事,心中有一个参照系。

但这个参照系有两个问题:

  1. 不一致:不同成员对"类似"的理解不同,给的点数可能差 2-3 倍。
  2. 不可传承:老员工的经验装在自己脑子里,新人来了无从参考。

那么,能否让 AI 来替代这个"有经验的团队成员",自动给出故事点的估算呢?

答案是:可以,前提是给 AI 建立一个结构化的参照系。

二、核心思想:从"类比估算"到向量化

人类估算故事点的本质是类比——把新故事和记忆中做过的旧故事比较,找到最相似的,参考它的点数。

我们把这个过程建模为三个步骤:

新故事 → 理解复杂度 → 找到最相似的基准故事 → 综合估算点数

系统的核心架构如下:

下面我们逐步拆解每个环节。

三、第一步:让 AI 理解故事的复杂度

3.1 为什么不直接用文本 Embedding

最初版的系统尝试过一种简单方案:把用户故事的标题和描述拼接成一段文本,调用 Embedding 模型(如 text-embedding-3-small)生成一个 1536 维的向量,然后在向量空间中做相似度检索。

这个方案的问题是:

  • 语义不等于复杂度:"用户登录"和"用户注销"的文本语义完全不同,但开发复杂度其实差不多。
  • 高维向量的信噪比低:1536 维里大量维度跟复杂度无关,反而引入了噪音。
  • 不可解释:你无法向用户解释"为什么这个故事是 5 点",因为向量只是数字。

3.2 转向 LLM 特征提取

所以我们换了一个思路:不直接对文本做 Embedding,而是 LLM 先理解故事内容,提取出与开发复杂度相关的结构化特征

我们设计了 10 个复杂度特征维度:

维度

类型

值域

含义

frontend_pages

数值

0-3

涉及的前端页面/弹窗/表单数量

backend_interfaces

数值

0-3

新增或修改的 API 接口数

db_change

布尔

是/否

是否需要 DDL 操作(建表/加字段)

external_dependency

布尔

是/否

是否对接外部系统(微信/支付/短信等)

async_processing

布尔

是/否

是否涉及 MQ/定时任务/Webhook

transaction_required

布尔

是/否

是否需要事务一致性保证

business_branches

数值

1-5+

主流程中的 if-else/状态机分支数

permission_control

布尔

是/否

是否需要 RBAC 权限控制

data_migration

布尔

是/否

是否需要历史数据迁移脚本

cache_design

布尔

是/否

是否需要引入 Redis 缓存

LLM 的 Prompt 中包含详细的判断标准参考,并要求只输出结构化 JSON:

{

  "frontend_pages"2,

  "backend_interfaces"3,

  "db_change""",

  "external_dependency""",

  "async_processing""",

  "transaction_required""",

  "business_branches"4,

  "permission_control""",

  "data_migration""",

  "cache_design"""

}

关键设计决策:这 10 个维度是通过对大量企业级项目的开发任务进行归纳总结得出的。它们覆盖了影响开发工作量的主要因素:界面数量、接口数量、数据变更、外部集成、异步处理、事务要求、业务分支、权限、迁移、缓存。维度数量选择 10 是在"足够表达能力""避免维度灾难"之间的平衡。

3.3 容错设计

特征提取是最依赖 LLM 的环节,所以做了多层容错:

  • 指数退避重试:LLM 调用失败时自动重试 3 次(间隔 1s / 2s / 4s)
  • JSON 解析兼容:支持直接 JSON 和 markdown 代码块包裹的 JSON(```json ... ```)
  • 布尔值兼容:接受 7 种布尔表达方式(是/否、true/false、True/False、1/0)
  • 温度控制:temperature=0.1,保证输出稳定性

四、第二步:将理解转为数学——特征向量编码

LLM 输出的特征字典仍然是"人类可读"的,需要转为机器可计算的向量。

这个转换由 FeatureEncoder 完成,规则非常直接:

连续型特征:除以上限归一化到 [0, 1]

v[0= frontend_pages / 3.0        前端页面数

v[1= backend_interfaces / 3.0    后端接口数

业务分支:线性映射 (1-5+) → [0.0, 1.0]

v[6= (business_branches - 1/ 4.0   # 1→0, 2→0.25, 3→0.5, 4→0.75, 5+→1.0

布尔型特征:直接映射

v[2..5,7..9= ""1.0""0.0

最终得到一个 10 维归一化向量,每个维度都在 [0, 1] 区间。10 维远小于传统 Embedding 的 1536 维,但每个维度都有明确的业务含义:

  • 维度越接近 1.0,代表该方面的复杂度越高
  • 两个向量的距离可以直接解释为"复杂度特征的差异程度"

五、第三步:找到最相似的基准故事——FAISS 检索

5.1 为什么用 FAISS

基准故事库(Baseline Stories)是团队历史上已完成的故事,每个故事都有一个确定的故事点数。我们需要在新故事入库时,快速找到与之最相似的几个基准故事。

FAISS(Facebook AI Similarity Search)是 Meta 开源的高性能向量检索库,专为此类场景设计。

5.2 索引类型选择

我们使用 IndexFlatIP(内积索引)而非更复杂的 IndexIVF 等近似索引,原因很简单:

  • 基准故事数量少(通常 12-36 条),暴力搜索的耗时可以忽略不计
  • IndexFlatIP 是精确搜索,不会引入近似误差
  • 简单可靠,没有额外的参数需要调优

5.3 相似度计算

检索前,系统会对所有向量做 L2 归一化(将向量长度缩放到 1)。归一化后,两个向量的内积就等于它们的余弦相似度:

cos_sim(a, b) = (a · b) / (|a| × |b|) = a · b  (因为 |a| = |b| = 1

检索时取 TopK(默认 K=3)个最相似的基准故事,返回它们的索引和相似度分数。

5.4 索引的持久化

FAISS 索引在内存中运行。为了避免每次重启都重建索引,系统在每次基准故事变更后自动将索引序列化到磁盘:

data/faiss_baseline.index

启动时优先加载已有索引文件,加载失败则从 SQLite 重建。

六、第四步:综合估算——加权平均 + LLM 裁决

6.1 加权平均

拿到 TopK 个相似基准故事及其相似度后,先做一个纯数值的加权平均:

w_avg = Σ(similarity_i × points_i) / Σ(similarity_i)

以相似度作为权重的含义很直观:越相似的故事,其点数对新故事的参考价值越大

举例:找到了 3 个相似故事——

基准故事

点数

相似度

"修改用户头像"

3

0.92

"重置密码功能"

5

0.78

"多角色权限管理"

8

0.45

加权平均:(3×0.92 + 5×0.78 + 8×0.45) / (0.92+0.78+0.45) = 10.26 / 2.15 ≈ 4.77

6.2 Fibonacci 舍入

加权平均的结果通常不是整数,需要舍入到 Fibonacci 刻度。舍入策略是"距离优先,平局取大":

FIB_SCALES = [1235813]

result = min(FIB_SCALESkey=lambda x: (abs(x - value), -x))

  • 4.77 → 最近的是 5(距离 0.23 < 距离 3 的 1.77),结果是 5
  • 4.5 → 到 3 和 5 的距离相等(都是 1.5),-x 使 5 排在前面,结果是 5

"平局取大"是一个保守策略——宁可略微高估,也不要低估导致 Sprint 承诺无法完成。

6.3 LLM 二次裁决

纯数值计算虽然精准,但缺乏"全局判断"。一个故事可能数值上与某条基准相似,但在业务上下文上有重要差异。

因此,在加权平均计算出结果后,系统会再调用一次 LLM 做综合裁决。Prompt 包含以下信息:

  • 新需求的标题、描述、验收准则
  • 复杂度特征(翻译为中文标签)
  • 加权平均的数值结果
  • TopK 匹配详情(名称、点数、相似度)

LLM 被要求输出结构化 JSON:

{

  "estimate"5,

  "confidence_min"3,

  "confidence_max"8,

  "reasoning""1. 复杂度评估:前端2+后端3接口+4分支+DML变更,复杂度中等偏高\n2. 基准对比:与'用户管理'相似度92%但缺少外部依赖,估5而非8\n3. 参数权衡:w_avg=4.7735边界,因DB变更取5",

  "risk_notes""外部系统对接时间不可控需提前确认;权限改造可能影响现有功能回归测试"

}

注意 reasoning 要求覆盖三个维度:复杂度评估、基准对比、参数权衡——这确保了估算结果的可解释性

6.4 降级机制

LLM 可能因为网络故障、API 限流、返回格式错误等原因失败。此时系统不会崩溃,而是降级为纯数值报告

degraded_result = {

    "estimate": round_to_fibonacci(weighted_avg),

    "reasoning""基于加权平均的数值估算结果",

    "degraded"True,

}

降级报告明确标记 degraded=true,让用户知道这是"兜底估算"。

七、质量保障:基准故事评价

估算的准确性严重依赖基准故事的质量。如果把一个实际上是 8 点的故事标成了 3 点,那么所有和它相似的新故事都会被低估。

系统在上传基准故事时,会自动进行双通道质量评价

通道一: 统计层评价

不依赖 LLM,纯数值分析,速度快、结果确定:

梯度检测:按点数(1, 2, 3, 5, 8, 13)分组,计算每组的特征向量质心(centroid)的 L2 范数。理论上,点数越大的故事,其质心范数应该越大(因为复杂度更高)。如果出现"8 点组的质心范数反而小于 5 点组"这种倒挂,说明标定可能有问题。

  • 1 处倒挂 → warning(提示但不阻断)
  • 2 处及以上 → error(阻断入库)

归属检测:对每条故事,计算其特征向量到各组质心的距离。如果一条"3 点故事"的特征向量,距离"5 点组"的质心比距离"3 点组"的质心更近,说明它可能被标低了。

  • 归属异常比例 > 20% → error
  • 有异常但 <= 20% → warning

综合评分:起评 100 分,每个 error 扣 25 分,每个 warning 扣 10 分。评分 >= 60 且无 error 级问题才算通过。

通道二: LLM 深度分析

当统计层发现异常时,会触发 LLM 做定性分析。LLM 看到的是按点数分组的故事情息,它可以从业务角度判断"为什么这两组故事的特征接近但标了不同的点"。

两通道评价体现了"能用简单方法解决就不用复杂方法"的原则。统计层覆盖了 90% 的质量问题,LLM 只在有异常时才介入做深度分析——既保证了效率,又弥补了纯数值方法在语义理解上的不足。

八、批量估算与拆分建议

8.1 批量估算

除了逐条估算,系统支持通过 Excel 批量导入多个用户故事做一次性估算:

  1. 下载批量估算模板(含 ID / 标题 / 描述 / 验收标准四列)
  2. 填入待估算的故事
  3. 上传后系统逐条处理,生成包含估算结果的 Excel
  4. 结果 Excel 包含:估算点数、置信区间、估算依据、风险提示

8.2 拆分建议

当估算结果触及上限时,系统会自动建议拆分:

  • 硬上限:加权平均值 w_avg > 13 → 必提示拆分
  • 软上限:LLM 裁决结果为 13 且 w_avg >= 10.5 → 建议考虑拆分

拆分建议会明确指出当前加权平均值和推荐方向,但不会自动拆分故事——拆分是产品决策,应该由人来完成。

九、关键设计决策总结

回顾整个系统,有以下几个关键的设计选择值得强调:

1 特征提取 vs 文本 Embedding

放弃通用的文本 Embedding,选择 LLM 提取 10 维业务特征。代价是一次 LLM 调用,换来的是:

  • 10 维向量远小于 1536 维,FAISS 检索快得多
  • 每个维度有明确的业务含义,结果可解释
  • 维度与复杂度直接相关,信噪比高

2 数值计算 + LLM 裁决的双层架构

没有直接让 LLM 端到端地"看故事给点数"(那样结果不稳定),也没有纯靠数值计算(那样缺乏语义判断)。加权平均提供数值锚点,LLM 在此基础上做语义调整——两者互补。

3 质量门禁

不比简单的 KNN 分类器,系统对输入数据做了严格的质量把控。梯度检测和归属检测在上传时就能发现标定问题——这源自一个朴素的认识:Garbage In, Garbage Out

4 容错降级

每个依赖外部服务的环节都有降级路径:LLM 特征提取失败 → 自动重试;LLM 报告生成失败 → 降级为纯数值报告。系统永远不会因为 AI 服务不可用而"白屏"。

十、总结

回到标题的问题:故事点是否可以用 AI 自动估算?

答案是:在一个有良好基准故事库的团队中,可以。

本系统不是要替代 Planning Poker 中团队的讨论和共识过程,而是提供一种结构化的参考——在新人经验不足、团队对某个故事的复杂度有分歧时,给出一个基于历史数据的、有理有据的估算建议。

核心思路可以概括为一句话: LLM 理解故事的复杂度,让 FAISS 找到最相似的参照,让加权平均给出数值锚点,再让 LLM 做最终的语义裁决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值