用 GPT 进行设计系统知识蒸馏:从文档到结构化规则的自动提取

一、100 页设计规范 PDF 躺在共享文件夹里吃灰

公司有一套完整的设计规范:PDF 文档 100 页,包含了按钮规格、间距规则、色彩使用指南、状态机定义……每个新入职的前端都需要读一遍。事实是:没人读完过。真实开发流程是"凭感觉写 → Code Review 时被设计走查打回 → 翻 PDF 找对应规则 → 修改"。

问题不在文档的内容质量,在于文档的形态。人类阅读 PDF 很舒服,但机器无法消费。如果每一条设计规则都能被解析为结构化的可执行约束(JSON 格式),就能在 CI 中自动检查、在 IDE 中实时提示。

知识蒸馏(Knowledge Distillation)在 ML 领域指"大模型的知识压缩到小模型",但在设计系统场景中,它的意思是:把非结构化的设计文档蒸馏为结构化的规则库

二、文档 → 规则的蒸馏管道

蒸馏的核心不是"用 LLM 重新写文档",而是让 LLM 从已有文档中提取规则。LLM 擅长做的是:从一段自然语言描述中识别出"主语-谓语-宾语",映射为"设计元素-约束类型-约束值"。

三、蒸馏工具实现

// knowledge-distillation/distiller.ts
// 设计系统知识蒸馏引擎
// 从设计文档中自动提取结构化规则

import fs from 'fs';
import path from 'path';

/** 单条设计规则的结构化表示 */
interface DesignRule {
  /** 规则 ID,唯一标识 */
  id: string;
  /** 规则类型 */
  category: 'color' | 'spacing' | 'typography' | 'component' | 'layout' | 'animation';
  /** 规则适用对象:'.btn', 'h1', 'input' 等 */
  target: string;
  /** 约束的属性:'font-size', 'border-radius', 'color' 等 */
  property: string;
  /** 约束的运算符 */
  operator: 'eq' | 'in' | 'range' | 'gte' | 'lte' | 'oneOf';
  /** 约束值 */
  value: string | string[] | { min: number; max: number };
  /** 原始文档引用(可追溯) */
  source: string;
  /** 蒸馏置信度 0~1 */
  confidence: number;
  /** 能否自动执行 */
  autoEnforceable: boolean;
}

/**
 * 从 Markdown 设计文档中蒸馏规则
 * 
 * 策略:分三步走——
 * 1. 按章节分割文档(每个 ## 标题为一个规则域)
 * 2. 每段送入 LLM 进行结构化提取
 * 3. 合并、去重、评分
 */
async function distillFromMarkdown(markdownPath: string): Promise<DesignRule[]> {
  const content = fs.readFileSync(markdownPath, 'utf-8');
  const sections = splitIntoSections(content);

  const allRules: DesignRule[] = [];

  for (const section of sections) {
    const prompt = buildDistillationPrompt(section);
    // 调用 LLM API(此处为接口示意,实际使用 OpenAI/Anthropic API)
    const rules = await callLLMForRules(prompt);
    allRules.push(...rules);
  }

  return mergeAndDeduplicate(allRules);
}

/**
 * 将 Markdown 按 ## 标题分割为章节
 */
function splitIntoSections(markdown: string): Array<{ title: string; content: string }> {
  const sections: Array<{ title: string; content: string }> = [];
  const lines = markdown.split('\n');
  let currentTitle = '';
  let currentContent: string[] = [];

  for (const line of lines) {
    if (line.startsWith('## ') && !line.startsWith('### ')) {
      if (currentTitle) {
        sections.push({ title: currentTitle, content: currentContent.join('\n') });
      }
      currentTitle = line.replace('## ', '').trim();
      currentContent = [];
    } else {
      currentContent.push(line);
    }
  }

  if (currentTitle) {
    sections.push({ title: currentTitle, content: currentContent.join('\n') });
  }

  return sections;
}

/**
 * 构建蒸馏 Prompt
 * 
 * 核心指令:从设计文档片段中提取结构化规则
 * 输出格式:JSON 数组
 * 
 * Prompt 设计要点:
 * 1. 明确输出 schema——约束 LLM 只能输出合法 JSON
 * 2. 提供 few-shot 示例——帮助 LLM 理解期望格式
 * 3. 要求置信度——让下游能区分"明确的规则"和"推测的规则"
 */
function buildDistillationPrompt(section: { title: string; content: string }): string {
  return `
你是一个设计系统知识蒸馏助手。请从以下设计规范文本中提取结构化规则。

## 输出格式
每条规则为 JSON 对象,包含以下字段:
{
  "category": "color|spacing|typography|component|layout|animation",
  "target": "规则适用的设计元素(如 .btn, h1, input, .card 等)",
  "property": "约束的 CSS 属性(如 font-size, border-radius, color)",
  "rule": "具体的约束描述(如 字体大小 14px, 圆角 4px, 间距使用 8px 倍数)",
  "confidence": 0.0-1.0 之间的置信度(原文明确提及=1.0,从上下文推断=0.5-0.8)
}

## Few-shot 示例

输入:
"所有主按钮的圆角为 4px,hover 时背景色加深 10%"

输出:
[
  {
    "category": "component",
    "target": ".btn-primary",
    "property": "border-radius",
    "rule": "border-radius: 4px",
    "confidence": 1.0
  },
  {
    "category": "component",
    "target": ".btn-primary:hover",
    "property": "background-color",
    "rule": "background-color 需比默认态深 10%",
    "confidence": 0.9
  }
]

## 章节标题
${section.title}

## 章节内容
${section.content}

请仅输出 JSON 数组,不要输出其他内容。
  `.trim();
}

/**
 * 调用 LLM 提取规则(示意实现)
 * 实际需要替换为 OpenAI/Anthropic SDK 调用
 */
async function callLLMForRules(prompt: string): Promise<DesignRule[]> {
  // 实际实现中,这里调用 GPT-4/Claude API
  // const response = await openai.chat.completions.create({
  //   model: 'gpt-4',
  //   messages: [{ role: 'user', content: prompt }],
  //   temperature: 0.1, // 低温度确保输出稳定
  //   response_format: { type: 'json_object' }
  // });
  // return JSON.parse(response.choices[0].message.content);

  // 示意返回
  return [];
}

/**
 * 合并去重:多条规则指向同一个约束时,保留置信度最高的
 */
function mergeAndDeduplicate(rules: DesignRule[]): DesignRule[] {
  const map = new Map<string, DesignRule>();

  for (const rule of rules) {
    // 生成去重 key:category + target + property 的组合
    const key = `${rule.category}:${rule.target}:${rule.property}`;

    if (!map.has(key) || map.get(key)!.confidence < rule.confidence) {
      map.set(key, { ...rule, id: key });
    }
  }

  return Array.from(map.values());
}
// knowledge-distillation/rule-to-lint.ts
// 将蒸馏出的规则转换为可执行的 Stylelint/ESLint 规则

interface LintRuleGenerator {
  generateStylelintRule(rule: DesignRule): string;
  generateESLintRule(rule: DesignRule): string;
}

/**
 * 将设计规则转换为 Stylelint 自定义规则代码
 */
function designRuleToStylelint(rule: DesignRule): string {
  switch (rule.category) {
    case 'color':
      return generateColorLintRule(rule);
    case 'spacing':
      return generateSpacingLintRule(rule);
    case 'typography':
      return generateTypographyLintRule(rule);
    default:
      return `// TODO: ${rule.category} 类型的规则暂不支持自动生成`;
  }
}

/**
 * 生成颜色规则(示例:检查是否使用了 Tokens 之外的颜色值)
 */
function generateColorLintRule(rule: DesignRule): string {
  return `
// 自动生成于设计系统知识蒸馏
// 规则来源:${rule.source}
// 置信度:${rule.confidence}
module.exports = {
  rules: {
    'color-no-untokenized': {
      meta: {
        messages: {
          rejected: '颜色值应使用设计 Token,不允许硬编码颜色'
        }
      },
      create(context) {
        return {
          Declaration(node) {
            if (node.prop.includes('color') || node.prop === 'background') {
              // 检查值是否为 hex 或 rgb(非 var() 形式)
              if (/^#[0-9a-fA-F]{3,8}$/.test(node.value) ||
                  /^rgb/.test(node.value)) {
                context.report({
                  node,
                  message: \`颜色 "\${node.value}" 应替换为设计 Token\`
                });
              }
            }
          }
        };
      }
    }
  }
};
  `.trim();
}

function generateSpacingLintRule(rule: DesignRule): string {
  return `
// 间距必须是 4px 的倍数
// 来源:${rule.source}
module.exports = {
  rules: {
    'spacing-multiple-of-4': {
      create(context) {
        return {
          Declaration(node) {
            const spacingProps = ['margin', 'padding', 'gap'];
            if (spacingProps.some(p => node.prop.includes(p))) {
              const match = node.value.match(/(\\d+)px/g);
              if (match) {
                for (const val of match) {
                  const num = parseInt(val);
                  if (num % 4 !== 0 && num !== 0) {
                    context.report({
                      node,
                      message: \`间距 \${num}px 不是 4px 的倍数,建议使用 \${Math.round(num/4)*4}px\`
                    });
                  }
                }
              }
            }
          }
        };
      }
    }
  }
};
  `.trim();
}

function generateTypographyLintRule(rule: DesignRule): string {
  return '';
}

四、蒸馏的准确性边界

LLM 会"发明"文档中不存在的规则。这是蒸馏最大的风险。降低幻觉的策略:

  • 温度参数设 0.1(接近确定性输出)
  • 要求引用原文(每条规则附带 source 字段,指向原始文档的具体段落)
  • 置信度 < 0.8 的规则必须人工 Review,不能直接入库

公式和数值关系 LLM 处理得很差。"主色加深 10%"这种规则,LLM 可能正确提取为 darken(primary, 10%),也可能理解为 primary 的 L 分量减少 10——完全是不同的结果。数值型规则只适合做"提取",不适合让 LLM 做"计算"。

文档更新后蒸馏的增量处理。不是每次文档变更都全量蒸馏。更好的方案:对比新旧文档,只对变更的章节重新蒸馏,避免"改了一个按钮的圆角 → 重新蒸馏整个 100 页文档"。

五、总结

设计系统的知识蒸馏是从"人可以读懂"到"机器可以执行"的翻译过程。三个步骤:

  1. 文档分段——按章节切分设计规范文档
  2. LLM 结构化提取——将自然语言规则转为 JSON 格式的约束对象
  3. 规则编译器——将 JSON 约束编译为 Stylelint/ESLint 规则、CI 脚本、IDE 提示

蒸馏不是替代设计文档。文档依然是人阅读的"真理源",蒸馏出的规则库是文档的"可执行镜像"。两者不是竞争关系,而是主从关系——文档变更 → 重新蒸馏 → 规则库同步更新。

更多推荐