199 lines
6.2 KiB
Markdown
199 lines
6.2 KiB
Markdown
---
|
||
name: regex-vs-llm-structured-text
|
||
description: 解析結構化文字時,選擇使用正規表示式 (Regex) 或大型語言模型 (LLM) 的決策框架 — 优先使用 Regex,僅針對低信心度的邊緣案例引入 LLM。
|
||
---
|
||
|
||
# 結構化文字解析:Regex 與 LLM 之爭
|
||
|
||
這是一個在解析結構化文字(如測驗、表單、發票、文件)時的實踐決策框架。核心洞察在於:正規表示式 (Regex) 能以極低成本且具備確定性地處理 95-98% 的案例。應將昂貴的 LLM 呼叫保留給剩餘的邊緣案例 (Edge cases)。
|
||
|
||
## 何時啟用
|
||
|
||
- 解析包含重複模式的結構化文字(題目、表單、表格)。
|
||
- 在文字擷取任務中抉擇應使用 Regex 還是 LLM。
|
||
- 建構同時結合兩者優點的混合處理管線 (Hybrid Pipelines)。
|
||
- 優化文字處理過程中的成本與準確度權衡。
|
||
|
||
## 決策框架
|
||
|
||
```
|
||
文字格式是否具備一致性且重複出現?
|
||
├── 是 (>90% 遵循特定規律) → 優先使用 Regex
|
||
│ ├── Regex 能處理 95% 以上案例 → 完成,不需要 LLM
|
||
│ └── Regex 能處理比例 < 95% → 僅針對邊緣案例添加 LLM
|
||
└── 否 (非結構化、高度變動) → 直接使用 LLM
|
||
```
|
||
|
||
## 架構模式
|
||
|
||
```
|
||
原始文字內容
|
||
│
|
||
▼
|
||
[Regex 解析器] ─── 擷取結構化數據 (95-98% 準確度)
|
||
│
|
||
▼
|
||
[文字清理器] ─── 移除噪音(標記、頁碼、人工痕跡)
|
||
│
|
||
▼
|
||
[信心度評分器] ─── 標記低信心度的擷取結果
|
||
│
|
||
├── 高信心度 (≥0.95) → 直接輸出
|
||
│
|
||
└── 低信心度 (<0.95) → [LLM 驗證器] → 最終輸出
|
||
```
|
||
|
||
## 實作範例
|
||
|
||
### 1. Regex 解析器 (處理絕大部分案例)
|
||
|
||
```python
|
||
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]:
|
||
"""使用 Regex 模式解析結構化文字。"""
|
||
pattern = re.compile(
|
||
r"(?P<id>\d+)\.\s*(?P<text>.+?)\n"
|
||
r"(?P<choices>(?:[A-D]\..+?\n)+)"
|
||
r"答案:\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. 信心度評分 (Confidence Scoring)
|
||
|
||
標記需要 LLM 介入審查的項目:
|
||
|
||
```python
|
||
@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),
|
||
)
|
||
```
|
||
|
||
### 3. LLM 驗證器 (僅針對邊緣案例)
|
||
|
||
```python
|
||
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. 混合管線 (Hybrid Pipeline)
|
||
|
||
```python
|
||
def process_document(
|
||
content: str,
|
||
*,
|
||
llm_client=None,
|
||
confidence_threshold: float = 0.95,
|
||
) -> list[ParsedItem]:
|
||
"""完整管線:Regex -> 信心度檢查 -> 針對邊緣案例使用 LLM。"""
|
||
# 步驟 1:Regex 擷取 (處理 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 驗證
|
||
result = []
|
||
for item in items:
|
||
if item.id in {f.item_id for f in low_confidence}:
|
||
result.append(validate_with_llm(item, content, llm_client))
|
||
else:
|
||
result.append(item)
|
||
|
||
return result
|
||
```
|
||
|
||
## 實踐之最佳實踐
|
||
|
||
- **從 Regex 開始**:即便是未臻完美的 Regex,也能為你提供優化的基準線。
|
||
- **使用信心度評分**:透過程式化方式識別哪些部分真正需要 LLM 的協助。
|
||
- **選用最廉價的 LLM**:驗證任務通常使用輕量級模型(如 Haiku 系列)即已足夠。
|
||
- **不可變性 (Immutability)**:在清理或驗證步驟中始終回傳新的實例。
|
||
- **測試驅動開發 (TDD)**:優先為已知模式編寫測試,再逐步加入邊緣案例。
|
||
- **記錄指標**:追蹤 Regex 成功率與 LLM 呼叫次數,以監控管線的健康狀況。
|
||
|
||
## 應避免的反模式
|
||
|
||
- 在 Regex 能處理 95% 以上案例時,依然將所有文字傳送給 LLM(既昂貴又緩慢)。
|
||
- 對於非結構化、變動極大的文字強行使用 Regex。
|
||
- 跳過信心度評估,一味寄望 Regex「總是有效」。
|
||
- 在清理或驗證過程中直接修改原始解析物件。
|
||
- 未測試邊緣案例(如格式錯誤的輸入、缺失欄位、編碼問題)。
|
||
|
||
## 適用情境
|
||
|
||
- 測驗/考試題目解析。
|
||
- 表單數據擷取。
|
||
- 發票/收據處理。
|
||
- 文件結構解析(標題、章節、表格)。
|
||
- 任何具有重複模式且對成本敏感的結構化文字處理。
|