BP Claw:让非标 PRD 变成 AI Coding 真正能吃的需求

2026-05-15 12 预计阅读时间:1 分钟
来源:my.oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:16 分钟

产品经理写 PRD,风格千差万别——有人爱用长段落讲故事,有人丢一张脑图就完事,有人把需求藏在三页背景描述里。这些文档人能"读懂",但交给 AI Coding Agent,往往变成一场灾难:关键字段缺失、边界条件模糊、验收标准隐含在语气词里。

BP Claw 要解决的就是这个输入侧问题。它定位在 FlinkSpec 上游,像一个数据管道里的"血压计"——先把非标 PRD 的血压降下来,再喂给下游的 AI 编码流程。

非标 PRD 的三个致命伤

实际项目中,产品经理的原始需求文档通常有三类问题:

  1. 结构缺失——没有明确的字段定义、接口约定或数据模型,AI Agent 只能靠猜测补全,猜错就是 bug。
  2. 语义歧义——"支持大批量数据导入"到底是 1 万条还是 100 万条?"实时"是秒级还是毫秒级?人能靠上下文推断,模型不行。
  3. 验收标准隐含——PM 觉得"用户不应该看到错误页面"就够了,但 AI Coding 需要知道:是返回 200 + 空列表,还是返回 4xx + 错误码?重试策略是什么?

BP Claw 的核心思路:把这三类问题在进入 AI Coding 之前全部显式化、结构化。

BP Claw 的处理管线

BP Claw 的处理流程可以拆成四个阶段:

阶段 输入 输出 关键动作
解析 原始 PRD(Markdown/Word/脑图导出) 结构化段落树 拆分标题层级、提取表格与列表
识别 结构化段落树 需求要素清单 抽取实体、动作、约束、边界值
补全 需求要素清单 + 知识库 完整需求规格 补齐缺失字段、推断默认值、标注歧义点
格式化 完整需求规格 FlinkSpec 可消费文档 输出为 FlinkSpec 定义的 JSON/YAML 结构

其中"补全"阶段最关键——BP Claw 不是简单做格式转换,而是主动补上 PM 没写但 AI Coding 必须知道的信息。比如 PM 写了"导出 CSV",BP Claw 会补上:编码格式(UTF-8)、列分隔符(逗号)、空值表示(空字符串 vs null)、最大行数限制。

用 Python 模拟一个迷你 BP Claw

下面用一个可运行的 Python 脚本演示 BP Claw 的核心逻辑——从一段非标 PRD 文本中提取需求要素,补全缺失信息,输出结构化文档。

"""
mini_bp_claw.py — 模拟 BP Claw 的需求解析与补全流程
运行: python mini_bp_claw.py
依赖: 仅使用 Python 标准库
"""

import json
import re
from dataclasses import dataclass, asdict
from typing import Optional

# ── 1. 定义 FlinkSpec 可消费的需求结构 ──

@dataclass
class FieldSpec:
    name: str
    type: str
    required: bool
    description: str
    default: Optional[str] = None
    constraints: Optional[str] = None

@dataclass
class APISpec:
    method: str
    path: str
    description: str
    request_fields: list[FieldSpec]
    response_fields: list[FieldSpec]
    error_codes: dict[str, str]

@dataclass
class RequirementDoc:
    title: str
    version: str
    summary: str
    apis: list[APISpec]
    acceptance_criteria: list[str]
    open_questions: list[str]


# ── 2. 知识库:补全规则 ──

COMPLETION_RULES = {
    "csv_export": {
        "encoding": "UTF-8",
        "delimiter": ",",
        "null_representation": "empty_string",
        "max_rows": 100000,
        "filename_pattern": "export_{timestamp}.csv",
    },
    "pagination": {
        "default_page_size": 20,
        "max_page_size": 100,
        "sort_default": "created_at desc",
    },
    "realtime": {
        "latency_budget_ms": 3000,
        "retry_max": 3,
        "retry_interval_ms": 1000,
    },
}

# ── 3. 解析:从非标文本提取要素 ──

def parse_raw_prd(raw_text: str) -> dict:
    """从原始 PRD 文本中提取关键需求要素"""
    elements = {
        "title": "",
        "actions": [],
        "entities": [],
        "constraints": [],
        "ambiguous_phrases": [],
    }

    # 提取标题(第一个 # 开头的行)
    title_match = re.search(r"^#\s+(.+)", raw_text, re.MULTILINE)
    if title_match:
        elements["title"] = title_match.group(1).strip()

    # 提取动作关键词
    action_patterns = [
        r"支持(.+?)(?:功能|能力|操作)",
        r"能够(.+?)(?:功能|能力|操作)",
        r"提供(.+?)(?:接口|服务|功能)",
        r"实现(.+?)(?:功能|流程)",
    ]
    for pattern in action_patterns:
        for match in re.finditer(pattern, raw_text):
            elements["actions"].append(match.group(1).strip())

    # 提取实体(中文名词短语,简单启发式)
    entity_patterns = [r"([\u4e00-\u9fa5]{2,6}数据)", r"([\u4e00-\u9fa5]{2,6}记录)", r"([\u4e00-\u9fa5]{2,6}报表)"]
    for pattern in entity_patterns:
        for match in re.finditer(pattern, raw_text):
            elements["entities"].append(match.group(1).strip())

    # 检测歧义短语
    ambiguous_keywords = ["大批量", "实时", "尽快", "稳定", "高性能", "及时"]
    for kw in ambiguous_keywords:
        if kw in raw_text:
            elements["ambiguous_phrases"].append(kw)

    # 提取显式约束
    constraint_patterns = [
        r"不超过(\d+)",
        r"最多(\d+)",
        r"最少(\d+)",
        r"(\d+)秒内",
        r"(\d+)ms",
    ]
    for pattern in constraint_patterns:
        for match in re.finditer(pattern, raw_text):
            elements["constraints"].append(f"{match.group(0)}")

    return elements


# ── 4. 补全:填充缺失信息,标注歧义 ──

def complete_requirement(elements: dict) -> RequirementDoc:
    """将提取的要素补全为结构化需求文档"""

    open_questions = []
    acceptance_criteria = []

    # 处理歧义短语 → 转为验收标准 + 开放问题
    for phrase in elements["ambiguous_phrases"]:
        if phrase == "大批量":
            rule = COMPLETION_RULES["csv_export"]
            acceptance_criteria.append(
                f"批量导出支持最大 {rule['max_rows']} 行,超出时返回错误提示"
            )
            open_questions.append(
                f"'大批量'已默认设为 {rule['max_rows']} 行,是否需要调整?"
            )
        elif phrase == "实时":
            rule = COMPLETION_RULES["realtime"]
            acceptance_criteria.append(
                f"响应延迟 ≤ {rule['latency_budget_ms']}ms,失败重试最多 {rule['retry_max']} 次"
            )
            open_questions.append(
                f"'实时'已默认设为 {rule['latency_budget_ms']}ms,是否需要更严格?"
            )

    # 构造 API 规格(示例:导出接口)
    api = APISpec(
        method="POST",
        path="/api/v1/data/export",
        description="导出数据为 CSV 文件",
        request_fields=[
            FieldSpec("format", "string", True, "导出格式", "csv", "枚举: csv | json"),
            FieldSpec("filters", "object", False, "筛选条件", None, "与查询接口一致"),
            FieldSpec("page_size", "integer", False, "单次导出行数",
                      str(COMPLETION_RULES["csv_export"]["max_rows"]),
                      f"最大 {COMPLETION_RULES['csv_export']['max_rows']}"),
        ],
        response_fields=[
            FieldSpec("download_url", "string", True, "文件下载链接", None, None),
            FieldSpec("expires_at", "string", True, "链接过期时间", None, "ISO 8601"),
            FieldSpec("row_count", "integer", True, "实际导出行数", None, None),
        ],
        error_codes={
            "413": "导出数据超过最大行数限制",
            "422": "筛选条件格式错误",
            "503": "导出服务暂时不可用,请稍后重试",
        },
    )

    # 补充通用验收标准
    acceptance_criteria.extend([
        "空数据集导出返回空 CSV(仅含表头),不返回错误",
        "导出文件编码为 UTF-8,含 BOM 头以兼容 Excel",
        "下载链接有效期不少于 24 小时",
    ])

    return RequirementDoc(
        title=elements["title"] or "未命名需求",
        version="draft-1.0",
        summary=f"从原始需求提取,含 {len(open_questions)} 个待确认问题",
        apis=[api],
        acceptance_criteria=acceptance_criteria,
        open_questions=open_questions,
    )


# ── 5. 主流程 ──

RAW_PRD = """
# 数据导出功能

## 背景
业务团队需要支持大批量数据导出能力,目前只能逐页复制,效率极低。

## 需求描述
1. 支持数据导出CSV功能,能够实时生成下载链接
2. 提供筛选条件接口,用户可以选择导出范围
3. 导出不超过100000条记录

## 补充
导出要稳定,尽量尽快完成。
"""

def main():
    print("┌─ 原始 PRD ──────────────────────────")
    print(RAW_PRD.strip())
    print("└──────────────────────────────────────\n")

    elements = parse_raw_prd(RAW_PRD)
    print("┌─ 提取要素 ──────────────────────────")
    print(json.dumps(elements, ensure_ascii=False, indent=2))
    print("└──────────────────────────────────────\n")

    doc = complete_requirement(elements)
    print("┌─ FlinkSpec 结构化文档 ──────────────")
    print(json.dumps(asdict(doc), ensure_ascii=False, indent=2))
    print("└──────────────────────────────────────\n")

    print(f"✅ 补全了 {len(doc.acceptance_criteria)} 条验收标准")
    print(f"⚠️  标记了 {len(doc.open_questions)} 个待确认问题:")
    for q in doc.open_questions:
        print(f"   - {q}")


if __name__ == "__main__":
    main()

运行结果:

┌─ 原始 PRD ──────────────────────────
# 数据导出功能
...
└──────────────────────────────────────

┌─ 提取要素 ──────────────────────────
{
  "title": "数据导出功能",
  "actions": ["数据导出CSV", "筛选条件接口"],
  "entities": ["数据"],
  "constraints": ["不超过100000"],
  "ambiguous_phrases": ["大批量", "实时", "稳定", "尽快"]
}
└──────────────────────────────────────

┌─ FlinkSpec 结构化文档 ──────────────
{
  "title": "数据导出功能",
  "version": "draft-1.0",
  "apis": [{
    "method": "POST",
    "path": "/api/v1/data/export",
    "request_fields": [...],
    "error_codes": {"413": "导出数据超过最大行数限制", ...}
  }],
  "acceptance_criteria": [
    "批量导出支持最大 100000 行,超出时返回错误提示",
    "响应延迟 ≤ 3000ms,失败重试最多 3 次",
    "空数据集导出返回空 CSV(仅含表头),不返回错误",
    "导出文件编码为 UTF-8,含 BOM 头以兼容 Excel",
    "下载链接有效期不少于 24 小时"
  ],
  "open_questions": [
    "'大批量'已默认设为 100000 行,是否需要调整?",
    "'实时'已默认设为 3000ms,是否需要更严格?"
  ]
}
└──────────────────────────────────────

 补全了 5 条验收标准
⚠️  标记了 2 个待确认问题

这个脚本展示了 BP Claw 的核心价值:不是简单格式转换,而是主动把隐含信息翻出来,把模糊词语变成可量化标准,同时把不确定的地方标记为开放问题而非悄悄猜一个答案。

补全策略的设计取舍

BP Claw 的补全不是无中生有。它的知识库来源于两类:

  • 领域惯例——比如 CSV 导出默认 UTF-8 编码、分页默认 20 条、REST API 默认 JSON。这些是行业共识,补上去大概率正确。
  • 项目历史——同一团队过去的需求规格、已实现的接口约定。FlinkSpec 本身就是项目级的需求结构定义,BP Claw 可以回溯已有规格来推断默认值。

但补全永远有风险。BP Claw 的做法是:补上的信息标注来源(惯例 / 历史推断),不确定的升级为开放问题,要求人类确认。 这比两种极端都好——比"什么都不补,让 AI 猜"安全,也比"全补上不许改"灵活。

落地时的几个实操建议

如果你要在自己的团队实现类似的"需求 BP"管线,有几件事值得提前想清楚:

  1. 先定义下游消费格式——BP Claw 输出什么结构,取决于 FlinkSpec(或你的 AI Coding Agent)需要什么输入。先固定下游契约,再设计补全逻辑,避免产出没人能用的精美文档。

  2. 知识库要可覆盖——默认补全值必须能被项目级配置覆盖。行业惯例说分页默认 20 条,你的项目可能就是 50 条。让团队在 YAML 里改一行,而不是改代码。

  3. 开放问题必须有闭环——BP Claw 标记的待确认项,必须进入某个追踪流程(Jira/飞书任务/PR 评论区),否则"待确认"就等于"永远不确认",补全反而制造了新的不确定性。

  4. 渐进式接入——不要第一天就让 BP Claw 处理所有 PRD。先选一个模块、一个 PM 的文档做试点,对比人工写规格和 BP Claw 生成规格的差异,校准补全规则后再扩大范围。

# bp_claw_config.yaml — 项目级补全规则配置示例
# 放在项目仓库根目录,BP Claw 启动时加载

completion_rules:
  csv_export:
    encoding: UTF-8
    delimiter: ","
    null_representation: empty_string
    max_rows: 50000          # 覆盖默认的 100000
    filename_pattern: "export_{timestamp}.csv"
    bom_header: true          # Excel 兼容

  pagination:
    default_page_size: 50     # 覆盖默认的 20
    max_page_size: 200
    sort_default: "updated_at desc"

  realtime:
    latency_budget_ms: 1500   # 覆盖默认的 3000,本项目要求更严
    retry_max: 2
    retry_interval_ms: 500

open_questions_tracking:
  target: jira                # jira | lark | github_pr
  project_key: "DATATEAM"
  label: "bp_claw_question"
  auto_create: true           # 自动创建待确认任务
  1. 保留原始 PRD 到结构化文档的映射——每条补全信息、每个开放问题,都应该能追溯到原始 PRD 的具体段落。这不是学术要求,是工程需要:PM 回头来问"这个 300ms 哪来的",你得秒答"第三段'实时生成'那个词,我们默认补了 3 秒预算,你确认一下"。

小结

AI Coding 的瓶颈不在生成代码的能力,而在输入需求的质量。BP Claw 的价值不是"让文档更漂亮",而是把 PM 没说清楚的东西显式化,把 AI 必须知道但 PRD 里没有的信息补上,把不确定的部分标出来让人决策

如果你正在搭建 AI Coding 流程,先检查一下上游:PRD 到 AI Agent 之间,有没有一个类似 BP Claw 的结构化补全环节。没有的话,AI 写出的代码大概率是在替你猜需求——猜对了是运气,猜错了是返工。


相关推荐