履歷素材入庫:精確去重與相似變體檢測
履歷素材入庫:精確去重與相似變體檢測
當你持續累積履歷素材時,如何避免重複?如何處理「幾乎一樣」的變體版本?本文分享
ingest命令的去重策略與實作細節。
問題場景
隨著時間累積,你的素材庫會遇到:
- 無意間貼入重複內容 - 同一段文字入庫兩次
- 微調版本過多 - 同一個案例的 5 個不同寫法
- 搜尋結果充斥相似項 - 降低有效資訊密度
我們需要一個多層次去重策略來處理這些情況。
去重策略設計
輸入素材
↓
┌────────────────────────┐
│ Layer 1: 精確去重 │ text_hash (SHA256)
│ 完全相同 → 拒絕入庫 │
└────────────────────────┘
↓ 不同
┌────────────────────────┐
│ Layer 2: 相似檢測 │ cosine similarity ≥ 0.94
│ embedding 向量比對 │ AND
│ + 技能標籤重疊度 │ skills overlap ≥ 0.5
└────────────────────────┘
↓ 相似 ↓ 不相似
┌──────────────┐ ┌──────────────┐
│ 標記為變體 │ │ 正常入庫 │
│ parent_id │ │ │
│ pending_review│ │ │
└──────────────┘ └──────────────┘
實作細節
Layer 1: 精確去重 (text_hash)
對文字做正規化後計算 SHA256:
def normalize_text(text: str) -> str:
"""正規化:小寫 + 壓縮空白"""
return " ".join(text.lower().split())
def compute_text_hash(text: str) -> str:
normalized = normalize_text(text)
return hashlib.sha256(normalized.encode()).hexdigest()
為什麼用 SHA256 而非直接比對?
- 效率:32 bytes hash vs 數百 bytes 文字
- 索引友善:可以在 hash 欄位建立索引
- 正規化處理:忽略空白差異
Layer 2: 相似檢測
兩個條件同時滿足才判定為「相似」:
COSINE_SIMILARITY_THRESHOLD = 0.94 # 語意相似度
SKILLS_OVERLAP_THRESHOLD = 0.5 # 技能重疊度
2.1 Cosine Similarity
使用 embedding 向量計算語意相似度:
def distance_to_cosine_similarity(distance: float) -> float:
"""LanceDB L2 距離轉換為 cosine similarity"""
# 對於正規化向量:cosine_sim ≈ 1 - (distance² / 2)
return max(0.0, 1.0 - (distance ** 2) / 2)
# 搜尋 Top-5 相似項
similar_results = table.search(vector).limit(5).to_list()
for sim in similar_results:
cosine_sim = distance_to_cosine_similarity(sim["_distance"])
# 0.94 → 非常相似但不完全相同
為什麼是 0.94?
| 相似度 | 意義 |
|---|---|
| 1.00 | 完全相同(但通常會被 text_hash 攔截) |
| 0.98 | 只改了幾個詞 |
| 0.94 | 同一件事的不同寫法 |
| 0.85 | 相關但不同的案例 |
| 0.70 | 主題相近 |
2.2 Skills Overlap
單靠向量相似度可能誤判,加入技能標籤驗證:
def compute_skills_overlap(skills1: list[str], skills2: list[str]) -> float:
"""Jaccard 係數:交集 / 聯集"""
set1 = {s.lower() for s in skills1}
set2 = {s.lower() for s in skills2}
intersection = len(set1 & set2)
union = len(set1 | set2)
return intersection / union if union > 0 else 0.0
範例:
素材 A: ["Python", "RAG", "LangChain"]
素材 B: ["Python", "RAG", "FastAPI"]
交集 = {"python", "rag"} = 2
聯集 = {"python", "rag", "langchain", "fastapi"} = 4
重疊度 = 2/4 = 0.5 ✓ 達門檻
變體處理
當判定為相似時,不是拒絕入庫,而是:
if cosine_sim >= 0.94 and skills_overlap >= 0.5:
dedupe_action = "similar_variant"
parent_id = similar["id"] # 關聯到原始素材
status = "pending_review" # 需要人工審核
這樣做的好處:
- 保留變體 - 不同寫法可能各有優點
- 建立關聯 - 知道哪些是同一案例的不同版本
- 人工決定 - 讓使用者選擇保留哪個
回傳結構
{
"status": "pending_review",
"id": "new-uuid",
"dedupe_action": "similar_variant",
"parent_id": "original-uuid",
"existing_id": "original-uuid",
"text_hash": "sha256...",
"skills": ["Python", "RAG"],
"type": "project",
...
}
dedupe_action 類型
| 值 | 意義 | 處理方式 |
|---|---|---|
exact_match |
精確重複 | 拒絕入庫 |
similar_variant |
相似變體 | 入庫但標記 parent_id |
none |
全新素材 | 正常入庫 |
CLI 使用
# 入庫新素材
uv run career-kb ingest \
--text "Built RAG system with 95% accuracy" \
--type project \
--skills "RAG,Python,LangChain" \
--role Lead \
--impact 4
# 輸出
# ✓ Added material: abc123
# Status: pending_review
# 嘗試入庫相似內容
uv run career-kb ingest \
--text "Built a RAG pipeline achieving 95% retrieval accuracy" \
--type project \
--skills "RAG,Python" \
--role Lead \
--impact 4
# 輸出
# Similar material found (cosine: 0.96, skills overlap: 0.67)
# Marking as variant of: abc123
# ✓ Added as variant: def456
# Parent: abc123
# Status: pending_review
進階應用
查詢變體樹
-- 找出某素材的所有變體
SELECT * FROM resume_chunks
WHERE parent_id = 'abc123'
-- 找出有最多變體的素材(可能需要優化)
SELECT parent_id, COUNT(*) as variant_count
FROM resume_chunks
WHERE parent_id IS NOT NULL
GROUP BY parent_id
ORDER BY variant_count DESC
清理變體
# 人工審核後,保留最好的版本
# 刪除其他變體
table.delete(f"parent_id = '{chosen_id}' AND id != '{chosen_id}'")
門檻調整建議
| 場景 | cosine_threshold | skills_threshold |
|---|---|---|
| 寬鬆(更多變體) | 0.90 | 0.4 |
| 預設 | 0.94 | 0.5 |
| 嚴格(更激進去重) | 0.97 | 0.6 |
根據你的使用習慣調整:
- 如果你的寫作風格變化大 → 降低 cosine_threshold
- 如果你常用不同技能標籤描述同一件事 → 降低 skills_threshold
總結
| 層級 | 檢測方式 | 結果 |
|---|---|---|
| Layer 1 | text_hash 精確比對 | 拒絕入庫 |
| Layer 2 | cosine + skills 相似檢測 | 標記為變體 |
| - | 通過兩層 | 正常入庫 |
這個多層次策略確保:
- 不浪費儲存 - 完全相同的不重複存
- 保留變體價值 - 相似但不同的寫法都保留
- 建立關聯 - 可以追蹤和管理變體版本
Career Knowledge Base 是一個本地優先的履歷知識庫系統,使用 Python + LanceDB + Voyage AI 建構。