正則表達式 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]:
"""使用正則表達式模式解析結構化文本。"""
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:
"""評分提取信心並標記問題。"""
reasons = []
score = 1.0
if len(item.choices) < 3:
reasons.append("選擇項過少")
score -= 0.3
if not item.answer:
reasons.append("缺少答案")
score -= 0.5
if len(item.text) < 10:
reasons.append("文字過短")
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]:
"""回傳低於信心門檻的項目。"""
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:
"""使用 LLM 修復低信心提取的項目。"""
response = client.messages.create(
model="claude-haiku-4-5-20251001", # 用於驗證的最便宜模型
max_tokens=500,
messages=[{
"role": "user",
"content": (
f"從以下文字中提取問題、選項和答案。\n\n"
f"文字:{original_text}\n\n"
f"目前提取結果:{item}\n\n"
f"如果需要,回傳修正後的 JSON,如果正確則回傳 'CORRECT'。"
),
}],
)
# 解析 LLM 回應並回傳修正後的項目...
return corrected_item
4. 混合管線
def process_document(
content: str,
*,
llm_client=None,
confidence_threshold: float = 0.95,
) -> list[ParsedItem]:
"""完整管線:正則表達式 -> 信心檢查 -> 僅對邊緣情況使用 LLM。"""
# 步驟 1:正則表達式提取(處理 95-98%)
items = parse_structured_text(content)
# 步驟 2:信心評分
low_confidence = identify_low_confidence(items, confidence_threshold)
if not low_confidence or llm_client is None:
return items
# 步驟 3:LLM 驗證(僅對標記的項目)
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 在這裡表現更好)
- 跳過信心評分,指望正則表達式「自動生效」
- 在清理/驗證步驟中修改已解析的物件
- 不測試邊緣情況(格式錯誤的輸入、缺少欄位、編碼問題)
使用時機
- 測驗/考試題目解析
- 表單資料提取
- 發票/收據處理
- 文件結構解析(標題、章節、表格)
- 任何具有重複模式且成本重要的結構化文本