跳转至

结构化文本解析中的正则表达式 vs LLM

用于解析结构化文本(测验、表单、发票、文档)的实用决策框架。核心洞察:正则表达式可以低成本且确定性地处理 95% 至 98% 的情况。将昂贵的 LLM 调用保留给其余边缘情况。

使用场景

  • 解析具有重复模式的结构化文本(题目、表单、表格)
  • 判断文本抽取应使用正则表达式还是 LLM
  • 构建结合两种方法的混合流水线
  • 优化文本处理中的成本/精度权衡

决策框架

テキスト形式は一貫していて繰り返しがあるか?
├── はい (>90% が何らかのパターンに従う) → 正規表現から始める
│   ├── 正規表現が 95%+ を処理 → 完了、LLM は不要
│   └── 正規表現が <95% を処理 → エッジケースのみ LLM を追加
└── いいえ (自由形式、高度に可変) → LLM を直接使用

架构模式

[正規表現パーサー] ─── 構造を抽出(95〜98% の精度)
    │
    ▼
[テキストクリーナー] ─── ノイズを除去(マーカー、ページ番号、アーティファクト)
    │
    ▼
[信頼度スコアラー] ─── 信頼度の低い抽出結果にフラグを立てる
    │
    ├── 高信頼度(≥0.95)→ 直接出力
    │
    └── 低信頼度(<0.95)→ [LLM バリデーター] → 出力

实现

1. 正则表达式解析器(处理大多数情况)

import re
from dataclasses import dataclass

@dataclass(frozen=True)
class ParsedItem:
    id: str
    text: str
    choices: tuple[str, ...]
    answer: str
    confidence: float = 1.0

def parse_structured_text(content: str) -> list[ParsedItem]:
    """Parse structured text using regex patterns."""
    pattern = re.compile(
        r"(?P<id>\d+)\.\s*(?P<text>.+?)\n"
        r"(?P<choices>(?:[A-D]\..+?\n)+)"
        r"Answer:\s*(?P<answer>[A-D])",
        re.MULTILINE | re.DOTALL,
    )
    items = []
    for match in pattern.finditer(content):
        choices = tuple(
            c.strip() for c in re.findall(r"[A-D]\.\s*(.+)", match.group("choices"))
        )
        items.append(ParsedItem(
            id=match.group("id"),
            text=match.group("text").strip(),
            choices=choices,
            answer=match.group("answer"),
        ))
    return items

2. 置信度评分

为可能需要 LLM 审查的项目设置标志:

@dataclass(frozen=True)
class ConfidenceFlag:
    item_id: str
    score: float
    reasons: tuple[str, ...]

def score_confidence(item: ParsedItem) -> ConfidenceFlag:
    """Score extraction confidence and flag issues."""
    reasons = []
    score = 1.0

    if len(item.choices) < 3:
        reasons.append("few_choices")
        score -= 0.3

    if not item.answer:
        reasons.append("missing_answer")
        score -= 0.5

    if len(item.text) < 10:
        reasons.append("short_text")
        score -= 0.2

    return ConfidenceFlag(
        item_id=item.id,
        score=max(0.0, score),
        reasons=tuple(reasons),
    )

def identify_low_confidence(
    items: list[ParsedItem],
    threshold: float = 0.95,
) -> list[ConfidenceFlag]:
    """Return items below confidence threshold."""
    flags = [score_confidence(item) for item in items]
    return [f for f in flags if f.score < threshold]

3. LLM 验证器(仅用于边缘情况)

def validate_with_llm(
    item: ParsedItem,
    original_text: str,
    client,
) -> ParsedItem:
    """Use LLM to fix low-confidence extractions."""
    response = client.messages.create(
        model="claude-haiku-4-5-20251001",  # Cheapest model for validation
        max_tokens=500,
        messages=[{
            "role": "user",
            "content": (
                f"Extract the question, choices, and answer from this text.\n\n"
                f"Text: {original_text}\n\n"
                f"Current extraction: {item}\n\n"
                f"Return corrected JSON if needed, or 'CORRECT' if accurate."
            ),
        }],
    )
    # Parse LLM response and return corrected item...
    return corrected_item

4. 混合流水线

def process_document(
    content: str,
    *,
    llm_client=None,
    confidence_threshold: float = 0.95,
) -> list[ParsedItem]:
    """Full pipeline: regex -> confidence check -> LLM for edge cases."""
    # Step 1: Regex extraction (handles 95-98%)
    items = parse_structured_text(content)

    # Step 2: Confidence scoring
    low_confidence = identify_low_confidence(items, confidence_threshold)

    if not low_confidence or llm_client is None:
        return items

    # Step 3: LLM validation (only for flagged items)
    low_conf_ids = {f.item_id for f in low_confidence}
    result = []
    for item in items:
        if item.id in low_conf_ids:
            result.append(validate_with_llm(item, content, llm_client))
        else:
            result.append(item)

    return result

实际指标

来自生产环境测验解析流水线(410 个项目):

指标 值
正则表达式成功率 98.0%
低置信度项目 8 (2.0%)
所需 LLM 调用次数 ~5
相比全部使用 LLM 的成本节省 ~95%
测试覆盖率 93%

最佳实践

  • 从正则表达式开始 — 即使是不完整的正则表达式也能成为改进的基线
  • 使用置信度评分,以程序化方式识别需要 LLM 帮助的项目
  • 使用最便宜的 LLM 进行验证(Haiku 级别模型即可)
  • 不修改已解析项目 — 从清理/验证步骤返回新实例
  • TDD 对解析器有效 — 先编写已知模式的测试,再编写边缘情况测试
  • 记录指标(正则表达式成功率、LLM 调用次数)以跟踪流水线健康状况

应避免的反模式

  • 当正则表达式能处理 95% 以上时,仍将全部文本发送给 LLM(成本高、速度慢)
  • 对自由格式且高度可变的文本使用正则表达式(LLM 更合适)
  • 跳过置信度评分,期望正则表达式“能正常工作”
  • 在清理/验证步骤中修改已解析对象
  • 不测试边缘情况(非法输入、缺失字段、编码问题)

适用场景

  • 解析测验/考试题目
  • 提取表单数据
  • 处理发票/收据
  • 解析文档结构(标题、章节、表格)
  • 任何具有重复模式且成本重要的结构化文本

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC