📚 产品定制无人化 系列文章

Spec提取:LLM驱动的语义结构化与OpenSpec

方法论回答"为什么",工程路径回答"怎么做",Skill回答"如何落地"。

一、为什么Spec提取必须以Prompt为核心

1.1 传统方案的问题

传统Spec提取方案:

┌─────────────┐
│   开发者   │  →  写代码解析器  →  提取结果
└─────────────┘
       ↓
  问题:
  1. 工具开发成本高
  2. 扩展性差(新语言/格式需要新工具)
  3. 无法处理模糊/语义性内容

1.2 LLM改变了什么

LLM驱动的方案:

┌─────────────┐
│  原始材料   │  →  Prompt  →  LLM  →  结构化结果
└─────────────┘

优势:
1. 一个方案处理所有语言/格式
2. 能理解语义,不只是语法
3. 能处理模糊和不确定性
4. 零工程成本,只需写Prompt

1.3 为什么是Prompt而不是脚本

脚本:处理确定性的结构化数据

文件读取、JSON解析、数据库查询——输入输出都是明确的。

Prompt:处理模糊的语义性内容

理解代码意图、推断业务规则、补充业务语义——输入可能是残缺的,输出需要"合理推断"。

结论:Prompt是Spec提取的最佳工具,脚本只做必要的辅助工作。


二、Spec提取的Skill化设计

2.1 Skill是什么

Skill是Agent的"专项能力包":

┌─────────────────────────────────────────────────────────────┐
│  Skill/                                                 │
│  │                                                      │
│  ├── SKILL.md          ← 技能定义(Agent读取)         │
│  ├── prompts/          ← Prompt模板(LLM执行)          │
│  ├── config.yaml       ← 配置(参数、路径)             │
│  └── README.md         ← 说明(可选)                   │
│                                                          │
│  Agent调用Skill:                                        │
│  1. 读取SKILL.md理解任务                                 │
│  2. 按顺序执行prompts                                    │
│  3. 每次调用LLM处理一步                                   │
│  4. 输出结构化结果                                       │
└─────────────────────────────────────────────────────────────┘

2.2 为什么Skill比脚本更适合

脚本方式的问题:

1. LLM调用分散在代码中,维护困难
2. Prompt无法单独测试
3. 逻辑硬编码

Skill方式的优势:

1. Prompt独立管理,单独测试、单独迭代
2. Agent直接执行,无需解析代码逻辑
3. 可组合,Prompt可以复用

三、Spec提取Skill的工作流程

┌─────────────────────────────────────────────────────────────┐
│                   SpecExtract Skill 工作流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Step 1: 材料采集                                             │
│  → 扫描项目结构 → 收集代码/文档                             │
│                                                              │
│  Step 2: LLM分类提取                                         │
│  → Entity提取Prompt                                         │
│  → Action提取Prompt                                         │
│  → Rule提取Prompt                                           │
│                                                              │
│  Step 3: Spec融合                                           │
│  → 多来源结果 → 融合 → 冲突检测                            │
│                                                              │
│  Step 4: 核心澄清                                           │
│  → 生成核心澄清问题 → 人工确认                             │
│                                                              │
│  Step 5: OpenSpec生成                                       │
│  → 融合后的Spec → OpenSpec格式                            │
│                                                              │
└─────────────────────────────────────────────────────────────┘

四、Skill实现样例

4.1 目录结构

SpecExtract/
│
├── SKILL.md
│
├── prompts/
│   ├── 01_collect.md           # 材料采集
│   ├── 02_extract_entity.md     # 实体提取
│   ├── 03_extract_action.md     # 操作提取
│   ├── 04_extract_rule.md       # 规则提取
│   ├── 05_merge.md             # Spec融合
│   ├── 06_clarify.md           # 澄清生成
│   └── 07_generate_openspec.md  # OpenSpec生成
│
└── config.yaml

4.2 SKILL.md

# SKILL.md - SpecExtract Skill

## 技能名称
spec-extract

## 技能描述
从产品代码库中提取结构化的产品规格信息,生成OpenSpec格式的Spec文档。

## 输入
- project_path: 项目路径
- product_context: 产品类型描述(可选)
- last_spec_path: 上次Spec路径(用于增量更新,可选)

## 输出
- specs/: OpenSpec格式的Spec目录
- clarification.md: 澄清问题清单

## 工作流程

按顺序执行以下Prompt:

01_collect → 02_extract_entity → 03_extract_action → 04_extract_rule
                                                          ↓
                                                    05_merge
                                                          ↓
                                              06_clarify → 07_generate_openspec

4.3 Entity提取Prompt

# 实体提取 Prompt

## 角色
你是一个专业的业务分析师,擅长从代码中提取产品规格信息。

## 任务
分析以下代码材料,提取产品中的核心业务实体。

## 输出格式

```json
{
  "entities": [
    {
      "name": "Client",
      "display_name": "客户",
      "description": "购买产品或服务的公司或个人",
      "attributes": [
        {
          "name": "name",
          "type": "string",
          "business_type": "名称",
          "required": true,
          "description": "客户名称"
        }
      ],
      "relationships": [
        {
          "target": "Order",
          "type": "1:N",
          "description": "一个客户可以有多个订单"
        }
      ],
      "confidence": 0.95,
      "uncertain_aspects": [],
      "source_files": ["models/client.py"]
    }
  ]
}
```

五、核心观点总结

观点一:Prompt是Spec提取的核心工具。

代码解析需要工程实现,Prompt解析只需要LLM调用。Prompt的灵活性远超代码。

观点二:Skill化设计让Agent直接可用。

Agent读取SKILL.md → 按顺序执行Prompt → 得到结构化结果。整个过程无需人工介入。

观点三:分阶段Prompt设计便于优化。

每个Prompt独立,输入输出明确。可以单独测试、单独优化,不用改动其他部分。

观点四:澄清是质量保障,不是可选环节。

LLM提取有不确定性,澄清是确保Spec正确性的最后一道关卡。


六、完整提取示例

为了更好地理解SpecExtract Skill的实际工作流程,下面以一个简化的CRM系统为例,展示完整的提取过程。

6.1 输入:原始代码

# models/client.py
from django.db import models

class Client(models.Model):
    """客户模型"""
    name = models.CharField(max_length=200)
    phone = models.CharField(max_length=20, unique=True)
    level = models.CharField(max_length=1, choices=[('A', 'B', 'C')])
    owner = models.ForeignKey('User', on_delete=models.CASCADE)
    status = models.CharField(max_length=20, default='active')
    created_at = models.DateTimeField(auto_now_add=True)

# services/discount.py
def calculate_discount(client, order_amount):
    """计算折扣"""
    base = {'A': 0.15, 'B': 0.10, 'C': 0}
    discount = base.get(client.level, 0)
    
    if order_amount > 100000:
        discount += 0.05
    
    return min(discount, 0.20)

# api/clients.py
@router.post("/api/clients/")
def create_client(request):
    """创建客户"""
    if Client.objects.filter(phone=request.data['phone']).exists():
        return Error("手机号已被使用")
    
    client = Client.objects.create(...)
    send_welcome_email(client.owner)
    
    return {"client_id": client.id}

6.2 Step 1:材料采集输出

{
  "project_info": {
    "path": "/project/crm",
    "type": "CRM",
    "language": "Python",
    "framework": "Django"
  },
  "code_files": [
    {
      "path": "models/client.py",
      "category": "models",
      "summary": "Client实体模型,包含name, phone, level, owner, status等字段"
    },
    {
      "path": "services/discount.py",
      "category": "services",
      "summary": "calculate_discount函数,根据客户等级和订单金额计算折扣"
    },
    {
      "path": "api/clients.py",
      "category": "api",
      "summary": "create_client接口,POST /api/clients/,验证手机号唯一性后创建客户"
    }
  ]
}

6.3 Step 2:Entity提取输出

{
  "entities": [
    {
      "name": "Client",
      "display_name": "客户",
      "description": "购买产品或服务的公司或个人",
      "attributes": [
        {"name": "name", "type": "string", "business_type": "名称", "required": true},
        {"name": "phone", "type": "string", "business_type": "联系方式", "required": true},
        {"name": "level", "type": "enum", "business_type": "状态", "required": true}
      ],
      "relationships": [
        {"target": "User", "type": "N:1", "description": "客户归属于负责销售"}
      ],
      "confidence": 0.95,
      "uncertain_aspects": [],
      "source_files": ["models/client.py"]
    }
  ]
}

6.4 Step 2:Rule提取输出

{
  "rules": [
    {
      "name": "discount_by_level_and_amount",
      "display_name": "客户等级折扣规则",
      "type": "discount",
      "description": "根据客户等级和订单金额计算折扣",
      "priority": "HIGH",
      "logic": "基础折扣:A级=15%,B级=10%,C级=0%;大订单额外折扣:订单>10万+5%;上限:总折扣≤20%",
      "decision_table": [
        {"level": "A", "amount": "<=10万", "discount": "15%"},
        {"level": "A", "amount": ">10万", "discount": "20%"},
        {"level": "B", "amount": "<=10万", "discount": "10%"},
        {"level": "B", "amount": ">10万", "discount": "15%"},
        {"level": "C", "amount": "任意", "discount": "0%"}
      ],
      "confidence": 0.95
    },
    {
      "name": "client_phone_unique",
      "display_name": "客户手机号唯一性",
      "type": "validation",
      "description": "同一手机号不能创建多个客户",
      "priority": "HIGH",
      "confidence": 0.95
    }
  ]
}

6.5 Step 3:融合输出

{
  "merged_entities": [...],
  "merged_actions": [...],
  "merged_rules": [...],
  "conflicts": [],
  "gaps": [
    {
      "type": "missing_relationship",
      "description": "Client和Order的关系未在代码中明确定义"
    }
  ],
  "statistics": {
    "total_entities": 1,
    "total_actions": 1,
    "total_rules": 2,
    "conflicts": 0,
    "gaps": 1
  }
}

6.6 Step 4:澄清输出(最少问题)

{
  "clarifications": [
    {
      "id": "CLAR-001",
      "priority": "MEDIUM",
      "type": "clarification",
      "target": "Client.level",
      "question": "客户等级A/B/C的具体判定标准是什么?",
      "options": [
        "A=重点客户(年交易>100万),B=普通客户(10-100万),C=潜在客户(<10万)",
        "A=战略客户(合作>1年),B=活跃客户(近3月有交易),C=其他"
      ],
      "impact": "影响折扣规则和客户管理策略"
    }
  ],
  "statistics": {
    "total": 1,
    "high_priority": 0,
    "medium_priority": 1
  },
  "estimated_review_time": "5分钟"
}

6.7 Step 5:OpenSpec生成输出

# Entity: 客户 (Client)

## Overview
客户是CRM系统的核心实体,代表购买产品或服务的公司或个人。

## Attributes

| Name | Type | Business Type | Required | Description |
|------|------|---------------|----------|-------------|
| id | UUID | ID | Yes | 唯一标识 |
| name | string | 名称 | Yes | 客户名称 |
| phone | string | 联系方式 | Yes | 联系电话 |
| level | enum | 状态 | Yes | 客户等级:A/B/C |

## Relationships
- Client → N:1 → User(负责销售)

## Scenarios

### Scenario: 正常创建
- GIVEN 用户已登录且有客户创建权限
- WHEN 填写客户名称、电话并提交
- THEN 创建客户,返回客户ID

### Scenario: 手机号重复
- GIVEN 要创建的客户手机号已存在
- WHEN 提交创建
- THEN 返回错误"该手机号已被使用"

---

# Rule: 客户等级折扣规则 (discount_by_level_and_amount)

## Overview
根据客户等级和订单金额计算折扣。

## Logic
基础折扣:A级=15%,B级=10%,C级=0%;大订单额外折扣:订单>10万+5%;上限:总折扣≤20%

## Decision Table

| Level | Amount | Base | Extra | Total |
|-------|--------|------|-------|-------|
| A | ≤10万 | 15% | 0% | 15% |
| A | >10万 | 15% | 5% | 20% |
| B | ≤10万 | 10% | 0% | 10% |
| B | >10万 | 10% | 5% | 15% |
| C | 任意 | 0% | 0% | 0% |

## Constraints
- 折扣上限20%
- 仅适用于直接订单

📚 产品定制无人化 系列文章