AI 辅助需求分析——从模糊需求到清晰 Spec
AI 辅助需求分析——从模糊需求到清晰 Spec
去年参与一个项目,产品经理写了一份需求文档,大家评审了两个小时,感觉没问题,开发下去了。做到一半发现一个严重问题:文档里对"用户"的定义有两种理解——有时指"注册用户",有时指"付费用户",有些功能写的是"用户可以...",但到底是哪种用户,没说清楚。
导致前端和后端对同一个接口的理解不一样,做出来不对,返工。
后来我开始用 AI 来辅助需求分析。不是替代产品经理,而是用 AI 做一遍"完整性和矛盾检查"。这篇文章把我的方法和代码分享出来。
需求分析的痛点
先说问题在哪。
产品经理的视角是站在"整体功能"上的,他们知道系统要做什么,用什么来实现,用户旅程是怎样的。
但他们不擅长的是:
- 边界条件:正常流程描述清楚了,但异常流程、极端情况没写
- 歧义识别:某个词语在不同上下文里可能有不同含义,没有定义
- 逻辑矛盾:文档篇幅大的时候,不同章节之间可能产生矛盾
- 缺失场景:某些使用场景压根没想到
这些不是产品经理的失职,这是人的认知局限。写几十页文档,很难在脑子里同时保持所有细节,做到完全一致。
而 AI 恰好擅长做这类系统性检查:找矛盾、找漏洞、找歧义。它没有先入之见,不会因为"大家都这么理解"就放过一个不一致的地方。
四类检测:漏洞、矛盾、歧义、边界
1. 漏洞检测
"漏洞"是指需求描述里缺失的必要信息。
常见漏洞类型:
- 描述了功能,但没描述权限(谁可以用这个功能)
- 描述了输入,但没描述所有可能的输出
- 描述了成功路径,但没描述失败处理
- 描述了触发条件,但没描述触发后的完整流程
2. 矛盾检测
同一份文档里,不同地方对同一件事的描述有冲突。
常见矛盾类型:
- 数据约束矛盾(第3章说密码最少6位,第7章说最少8位)
- 业务规则矛盾(第2章说用户删除后数据保留30天,第5章说立即删除)
- 权限矛盾(第4章说普通用户可以导出数据,第6章说只有管理员可以导出)
3. 歧义识别
同一个词语或表述有多种合理解释,没有明确定义。
常见歧义:
- "用户"到底是注册用户还是付费用户
- "实时"到底是毫秒级、秒级还是分钟级
- "大量数据"没有具体数字
- "超时"没有定义超时时间
4. 边界条件补全
边界条件是测试工程师最关心的,但产品文档经常漏写。
常见缺失边界:
- 数量边界(0个、1个、最大值时怎么处理)
- 状态边界(已取消的订单还能评价吗?已删除的用户的评论怎么显示)
- 时间边界(过期的优惠券能不能用,结算中的订单突然取消怎么处理)
需求分析 Prompt 的设计
Prompt 设计是这件事的核心。我用了几个月时间打磨,分享最终版本。
结构化 Prompt 设计
@Component
public class RequirementsAnalysisPrompts {
// 整体分析 Prompt
public static final String OVERALL_ANALYSIS_PROMPT = """
你是一个专业的需求分析专家,擅长发现需求文档中的问题。
请对以下需求文档进行全面分析。
分析维度:
1. 【完整性检查】:是否有缺失的必要信息?
- 功能描述是否完整(有输入没输出、有正常流没异常流)
- 权限控制是否明确(每个功能点谁能用、谁不能用)
- 数据约束是否完整(长度限制、格式要求、取值范围)
2. 【矛盾检测】:文档不同位置是否有相互冲突的描述?
- 同一概念是否有不同的描述
- 业务规则是否自洽
- 数据约束是否一致
3. 【歧义识别】:是否有可以被多种方式理解的表述?
- 关键术语是否有清晰定义
- 数量描述是否有明确边界
- 时间描述是否有明确定义
4. 【边界条件】:是否缺少对极端情况的处理描述?
- 空值/空集合的处理
- 最大值/最小值场景
- 状态转换的边界情况
请以结构化方式输出,每个问题包含:
- 问题类型(完整性/矛盾/歧义/边界条件)
- 严重程度(P0-阻断开发/P1-影响理解/P2-细节待确认)
- 具体描述(引用原文位置,说明问题所在)
- 建议补充或修改的内容
需求文档:
{requirements}
""";
// 专项矛盾检测 Prompt
public static final String CONTRADICTION_DETECTION_PROMPT = """
请专门检查以下需求文档中的逻辑矛盾。
检查重点:
1. 同一个业务概念在不同章节是否有不同定义
2. 业务规则A和业务规则B是否能同时成立
3. 数据约束在不同描述中是否一致
4. 权限设置是否有冲突
输出格式(JSON):
{
"contradictions": [
{
"id": "C001",
"severity": "P0/P1/P2",
"description": "矛盾描述",
"location1": "第一处引用(原文片段)",
"location2": "第二处引用(原文片段)",
"suggestion": "建议的解决方案"
}
],
"totalCount": 数量
}
需求文档:
{requirements}
""";
// 歧义词汇检测 Prompt
public static final String AMBIGUITY_DETECTION_PROMPT = """
请识别以下需求文档中所有可能引起歧义的表述。
关注点:
1. 专业术语是否有明确定义(如"用户"、"订单"、"权限"等)
2. 数量描述是否模糊("大量"、"少量"、"适当"等)
3. 时间描述是否模糊("及时"、"尽快"、"实时"等)
4. 条件描述是否完整("在某些情况下"没有说明是哪些情况)
对于每个歧义点,请:
1. 列出可能的不同理解方式
2. 分析哪种理解最可能是产品意图
3. 建议如何澄清
需求文档:
{requirements}
""";
// 边界条件补全 Prompt
public static final String BOUNDARY_COMPLETION_PROMPT = """
请分析以下需求文档,识别所有可能遗漏的边界条件和异常场景。
系统化地检查每个功能点:
1. 输入边界:空值、最小值、最大值、非法格式
2. 数量边界:0条数据、1条数据、超大数据量
3. 状态边界:初始状态、终态、已删除、已过期
4. 并发场景:同一资源被多人同时操作
5. 网络异常:请求超时、中断重试
对于每个功能点,列出应该但没有明确描述的边界场景,以及建议的处理方式。
需求文档:
{requirements}
""";
}需求分析服务
@Service
@Slf4j
public class RequirementsAnalysisService {
private final ChatClient chatClient;
private final RequirementsAnalysisPrompts prompts;
public RequirementsAnalysisService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public RequirementsAnalysisReport analyze(String requirementsDocument) {
RequirementsAnalysisReport report = new RequirementsAnalysisReport();
report.setAnalyzedAt(LocalDateTime.now());
report.setDocumentLength(requirementsDocument.length());
// 1. 整体分析(找出所有问题的概览)
log.info("开始需求文档整体分析...");
OverallAnalysisResult overall = runOverallAnalysis(requirementsDocument);
report.setOverallSummary(overall);
// 2. 专项矛盾检测
log.info("开始矛盾检测...");
ContradictionResult contradictions = runContradictionDetection(requirementsDocument);
report.setContradictions(contradictions);
// 3. 歧义识别
log.info("开始歧义识别...");
AmbiguityResult ambiguities = runAmbiguityDetection(requirementsDocument);
report.setAmbiguities(ambiguities);
// 4. 边界条件补全
log.info("开始边界条件分析...");
BoundaryResult boundaries = runBoundaryAnalysis(requirementsDocument);
report.setBoundaryIssues(boundaries);
// 5. 统计汇总
report.setTotalIssues(
contradictions.getTotalCount() +
ambiguities.getTotalCount() +
boundaries.getTotalCount()
);
report.setP0Issues(countP0Issues(report));
report.setP1Issues(countP1Issues(report));
return report;
}
private ContradictionResult runContradictionDetection(String doc) {
String prompt = RequirementsAnalysisPrompts.CONTRADICTION_DETECTION_PROMPT
.replace("{requirements}", truncateIfNeeded(doc));
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
return parseContradictionResult(response);
}
private ContradictionResult parseContradictionResult(String response) {
try {
// 提取JSON
String json = extractJson(response);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, ContradictionResult.class);
} catch (Exception e) {
log.error("解析矛盾检测结果失败: {}", e.getMessage());
// 降级:返回原始文本
ContradictionResult fallback = new ContradictionResult();
fallback.setRawResponse(response);
return fallback;
}
}
private AmbiguityResult runAmbiguityDetection(String doc) {
String prompt = RequirementsAnalysisPrompts.AMBIGUITY_DETECTION_PROMPT
.replace("{requirements}", truncateIfNeeded(doc));
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
return parseAmbiguityResult(response);
}
private BoundaryResult runBoundaryAnalysis(String doc) {
String prompt = RequirementsAnalysisPrompts.BOUNDARY_COMPLETION_PROMPT
.replace("{requirements}", truncateIfNeeded(doc));
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
return parseBoundaryResult(response);
}
private OverallAnalysisResult runOverallAnalysis(String doc) {
String prompt = RequirementsAnalysisPrompts.OVERALL_ANALYSIS_PROMPT
.replace("{requirements}", truncateIfNeeded(doc));
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
OverallAnalysisResult result = new OverallAnalysisResult();
result.setRawAnalysis(response);
return result;
}
// 文档过长时截断(GPT-4 的 context window 有限制)
private String truncateIfNeeded(String doc) {
int maxChars = 15000; // 约 4000 tokens
if (doc.length() <= maxChars) {
return doc;
}
// 如果文档太长,分段分析
log.warn("文档较长 ({}字),已截断至{}字进行分析", doc.length(), maxChars);
return doc.substring(0, maxChars) + "\n\n[文档较长,以上为前半部分内容]";
}
private String extractJson(String text) {
int start = text.indexOf('{');
int end = text.lastIndexOf('}') + 1;
if (start >= 0 && end > start) {
return text.substring(start, end);
}
throw new IllegalArgumentException("响应中未找到JSON: " + text.substring(0, Math.min(100, text.length())));
}
}长文档分段分析
对于超长需求文档,需要分段处理:
@Service
public class LargeDocumentAnalyzer {
private final RequirementsAnalysisService analysisService;
private final ChatClient chatClient;
private static final int SEGMENT_SIZE = 3000; // 每段最多3000字
private static final int OVERLAP_SIZE = 500; // 段间重叠500字(保持上下文连续)
public RequirementsAnalysisReport analyzeWithSegmentation(String document) {
if (document.length() <= SEGMENT_SIZE * 2) {
// 文档不太长,直接分析
return analysisService.analyze(document);
}
// 分段
List<String> segments = splitDocument(document);
log.info("文档已分成 {} 段进行分析", segments.size());
// 分别分析每段
List<RequirementsAnalysisReport> segmentReports = new ArrayList<>();
for (int i = 0; i < segments.size(); i++) {
log.info("分析第 {}/{} 段...", i + 1, segments.size());
RequirementsAnalysisReport segReport = analysisService.analyze(segments.get(i));
segReport.setSegmentIndex(i);
segmentReports.add(segReport);
}
// 合并报告,并做跨段矛盾检测
RequirementsAnalysisReport merged = mergeReports(segmentReports);
// 额外做一次跨段一致性检查
runCrossSegmentConsistencyCheck(segments, merged);
return merged;
}
private void runCrossSegmentConsistencyCheck(List<String> segments, RequirementsAnalysisReport report) {
// 从每段提取关键概念和规则,再做整体一致性检查
List<String> keyRules = new ArrayList<>();
for (String segment : segments) {
String rulesPrompt = """
从以下文本中提取所有明确的业务规则(每条规则一行,格式:【规则描述】):
%s
""".formatted(segment);
String rules = chatClient.prompt().user(rulesPrompt).call().content();
keyRules.add(rules);
}
// 检查提取出的规则之间的一致性
String allRules = String.join("\n\n=== 下一段规则 ===\n\n", keyRules);
String consistencyPrompt = """
以下是从一份需求文档不同部分提取的业务规则,请检查这些规则之间是否有矛盾:
%s
如果发现矛盾,请列出:矛盾的两条规则原文 + 矛盾说明。
""".formatted(allRules);
String consistencyResult = chatClient.prompt().user(consistencyPrompt).call().content();
report.setCrossSegmentConsistencyCheck(consistencyResult);
}
private List<String> splitDocument(String document) {
List<String> segments = new ArrayList<>();
int start = 0;
while (start < document.length()) {
int end = Math.min(start + SEGMENT_SIZE, document.length());
// 尽量在段落边界处切分
if (end < document.length()) {
int paragraphBreak = document.lastIndexOf("\n\n", end);
if (paragraphBreak > start + SEGMENT_SIZE / 2) {
end = paragraphBreak + 2;
}
}
segments.add(document.substring(start, end));
start = end - OVERLAP_SIZE; // 重叠部分保持上下文
}
return segments;
}
}真实案例:一次发现了 7 个逻辑矛盾
这是我最近做的一个项目,电商平台的积分系统需求文档,大概 80 页。
把文档丢给 AI 分析,来自动发现的问题里,我挑 7 个最典型的:
矛盾 1(P0):
- 第 12 页:"购买商品后 7 天内不可兑换积分,防止退换货后积分损失"
- 第 28 页:"购买成功后立即获得积分,可用于下次购物抵扣"
- 分析:两处对"何时获得积分"的描述直接矛盾
矛盾 2(P0):
- 第 18 页:"积分永不过期"
- 第 35 页:"距上次消费超过 12 个月,积分自动清零"
- 分析:对积分有效期的描述矛盾
矛盾 3(P1):
- 第 22 页:"1000积分兑换10元优惠券,最少可兑换1000积分"
- 第 44 页:"积分兑换最低门槛为5000积分"
- 分析:兑换门槛数字矛盾
矛盾 4(P1):
- 第 19 页:"普通会员和VIP会员获取积分的比例相同,均为1元=1积分"
- 第 33 页:"VIP会员享受双倍积分"
- 分析:VIP权益描述矛盾
矛盾 5(P1):
- 第 25 页:"退款后积分原路退回"
- 第 31 页:"使用积分抵扣的部分不支持退款"
- 分析:对积分退款处理的描述矛盾
矛盾 6(P2):
- 第 14 页:"邀请好友注册成功可获得50积分"
- 第 40 页:"新用户注册奖励50积分,邀请人获得20积分"
- 分析:邀请积分奖励数字矛盾
矛盾 7(P2):
- 第 29 页:"积分余额在账户注销时退还"
- 第 36 页:"账户注销后数据不可恢复"
- 分析:注销后积分处理不明确(退还给谁、怎么退还)
这 7 个矛盾,如果不处理直接开发,后期必然返工。而且这种跨页面的矛盾,评审会议里很难发现——因为大家不会同时在脑子里记住第 12 页和第 28 页的内容。
AI 就不一样了,它把整篇文档都"记住"了,矛盾一眼就看出来。
生成结构化 Issue 列表
分析完了,需要输出可以被追踪的 Issue 列表:
@Service
public class RequirementsIssueExporter {
public String exportToMarkdown(RequirementsAnalysisReport report) {
StringBuilder md = new StringBuilder();
md.append("# 需求分析报告\n\n");
md.append("**分析时间:**").append(report.getAnalyzedAt()).append("\n");
md.append("**文档长度:**").append(report.getDocumentLength()).append(" 字符\n");
md.append("**发现问题总计:**").append(report.getTotalIssues()).append(" 个\n");
md.append(" - P0(阻断开发):").append(report.getP0Issues()).append(" 个\n");
md.append(" - P1(影响理解):").append(report.getP1Issues()).append(" 个\n\n");
md.append("---\n\n");
// 矛盾列表
if (report.getContradictions() != null && !report.getContradictions().getItems().isEmpty()) {
md.append("## 逻辑矛盾\n\n");
for (ContradictionItem item : report.getContradictions().getItems()) {
md.append("### ").append(item.getId()).append(" [").append(item.getSeverity()).append("]\n\n");
md.append("**问题描述:**").append(item.getDescription()).append("\n\n");
md.append("**矛盾处1:**\n> ").append(item.getLocation1()).append("\n\n");
md.append("**矛盾处2:**\n> ").append(item.getLocation2()).append("\n\n");
md.append("**建议:**").append(item.getSuggestion()).append("\n\n");
md.append("---\n\n");
}
}
// 歧义列表
if (report.getAmbiguities() != null) {
md.append("## 歧义问题\n\n");
md.append(report.getAmbiguities().getRawAnalysis()).append("\n\n");
}
// 边界条件
if (report.getBoundaryIssues() != null) {
md.append("## 遗漏边界条件\n\n");
md.append(report.getBoundaryIssues().getRawAnalysis()).append("\n\n");
}
return md.toString();
}
public void exportToJira(RequirementsAnalysisReport report, JiraClient jiraClient) {
// 把每个 P0/P1 问题创建成 Jira Story
for (ContradictionItem item : report.getContradictions().getItems()) {
if ("P0".equals(item.getSeverity()) || "P1".equals(item.getSeverity())) {
JiraIssue jiraIssue = new JiraIssue();
jiraIssue.setTitle("[需求问题] " + item.getId() + " - " + item.getDescription().substring(0, 50));
jiraIssue.setDescription(buildJiraDescription(item));
jiraIssue.setPriority(item.getSeverity());
jiraIssue.setLabels(List.of("requirements-issue", "contradiction"));
jiraClient.createIssue(jiraIssue);
}
}
}
}总结
用 AI 辅助需求分析,本质上是让 AI 扮演一个不知疲倦的、没有先入之见的审查者。
它不会因为"大家评审时都没问题"就放过矛盾,不会因为"这个太细节了"就跳过边界条件。
实践下来,我觉得最有价值的是矛盾检测。一份 80 页的需求文档,评审会议再怎么仔细,人脑很难同时保持对全文所有细节的记忆,跨页的矛盾很难发现。但 AI 能把整篇文档"看一遍",把矛盾都找出来。
这个工具现在已经融入我们团队的需求评审流程:需求文档定稿之前,先跑一遍 AI 分析,把 P0/P1 问题解决掉,再开评审会。效果很明显,评审会议里技术性争议少多了,大家可以把注意力放在更有价值的架构和方案讨论上。
