用 LanceDB 儲存 JD 分析:避開 Arrow 巢狀結構的陷阱
用 LanceDB 儲存 JD 分析:避開 Arrow 巢狀結構的陷阱
在建立履歷知識庫系統時,我們需要將 JD(職位描述)的分析結果結構化儲存。本文分享使用 LanceDB + Python 實作
save-jd功能時的設計決策與踩坑經驗。
問題背景
我們的系統需要:
- 接收使用者貼上的 JD 文字
- 用 LLM 提取結構化資訊(技能、職責、資歷等級)
- 儲存至本地向量資料庫,供後續履歷匹配使用
聽起來很簡單,但 LanceDB(基於 Apache Arrow)對巢狀資料結構有特殊限制。
Schema 設計
# jd_analysis 表結構
{
"id": str, # UUID 主鍵
"company": str, # 公司名稱
"position": str, # 職位名稱
"raw_jd": str, # 原始 JD 文字
"focus_items": str, # ⚠️ JSON 字串(非 object array)
"required_skills": list[str], # 技能清單
"seniority_level": str, # Junior/Mid/Senior/Lead
"domain": str, # 產業領域
"status": str, # applied/interviewed/rejected/offered
"created_at": str, # ISO 時間戳記
}
關鍵設計:focus_items 序列化
原本我們想這樣儲存 focus_items:
# ❌ 這樣會出問題
record["focus_items"] = [
{
"description": "distributed systems ownership",
"skill_tags": ["Kafka", "AWS"],
"responsibility": "設計分散式系統",
"seniority_signal": "5+ 年經驗"
},
...
]
但 LanceDB/Arrow 對 巢狀 object array 的處理有問題,會導致:
- 寫入時 schema 推斷錯誤
- 讀取時資料型別轉換失敗
- 無法正確索引和查詢
解決方案:JSON 字串化
# ✅ 正確做法:序列化為 JSON 字串
record["focus_items"] = json.dumps([
{
"description": "distributed systems ownership",
"skill_tags": ["Kafka", "AWS"],
...
}
])
讀取時再反序列化:
focus_items = json.loads(record["focus_items"])
核心實作流程
使用者輸入 JD
↓
┌─────────────────────────┐
│ 1. 產生 UUID │
│ id = uuid.uuid4() │
└─────────────────────────┘
↓
┌─────────────────────────┐
│ 2. LLM 提取結構化資訊 │
│ - focus_items │
│ - required_skills │
│ - seniority_level │
│ - domain │
└─────────────────────────┘
↓
┌─────────────────────────┐
│ 3. 序列化 focus_items │
│ json.dumps([...]) │
└─────────────────────────┘
↓
┌─────────────────────────┐
│ 4. 補上 metadata │
│ - status: "applied" │
│ - created_at: now() │
└─────────────────────────┘
↓
┌─────────────────────────┐
│ 5. 寫入 LanceDB │
│ table.add([record]) │
└─────────────────────────┘
↓
回傳 { id: "<uuid>" }
Python 實作
import json
import uuid
from datetime import datetime
def save_jd(company: str, position: str, raw_jd: str, auto_extract: bool):
# 1. 準備基本記錄
record = {
"id": str(uuid.uuid4()),
"company": company,
"position": position,
"raw_jd": raw_jd,
"focus_items": "[]", # 預設空 JSON 陣列字串
"required_skills": [],
"seniority_level": "Mid",
"domain": "",
"status": "applied",
"created_at": datetime.now().isoformat(),
}
# 2. AI 提取(可選)
if auto_extract:
analysis = extract_jd_analysis(raw_jd, company, position)
# 3. 關鍵:序列化 focus_items
record["focus_items"] = json.dumps([
item.model_dump() for item in analysis.focus_items
])
record["required_skills"] = analysis.required_skills
record["seniority_level"] = analysis.seniority_level
record["domain"] = analysis.domain
# 4. 寫入 LanceDB
db = lancedb.connect("data/lancedb")
if "jd_analysis" in db.table_names():
table = db.open_table("jd_analysis")
table.add([record])
else:
db.create_table("jd_analysis", [record])
# 5. 回傳 ID
return {"id": record["id"]}
LLM 提取 Prompt
prompt = f"""Analyze this job description and extract:
1. Focus items (key responsibilities and requirements)
2. Required skills (technical and soft skills)
3. Seniority level (Junior/Mid/Senior/Lead)
4. Domain (e.g., fintech, e-commerce, SaaS)
Company: {company}
Position: {position}
JD:
{raw_jd}
Respond in JSON format with:
- focus_items: array of {description, skill_tags[], responsibility, seniority_signal}
- required_skills: array of skill names
- seniority_level: one of Junior/Mid/Senior/Lead
- domain: string
"""
CLI 使用方式
# 儲存 JD 並自動提取
uv run career-kb save-jd \
--company "Google" \
--position "AI PM" \
--jd-file "./jd.txt" \
--auto-extract
# 輸出
# ✓ Saved JD analysis: a1b2c3d4-5678-90ab-...
# Company: Google
# Position: AI PM
# Seniority: Senior
# Skills: Python, Kubernetes, Leadership, ...
後續流程
儲存 JD 後,可以用回傳的 id 進行:
# 驗證技能覆蓋
uv run career-kb verify --jd-id "a1b2c3d4-..." --with-strategy
# 生成客製化履歷
uv run career-kb generate --jd-id "a1b2c3d4-..."
經驗總結
| 問題 | 解決方案 |
|---|---|
| Arrow 不支援巢狀 object array | 將複雜結構序列化為 JSON 字串 |
| Schema 推斷錯誤 | 使用簡單型別 (str, list[str]) |
| 讀取時型別問題 | 讀取後 json.loads() 反序列化 |
適用情境
這個模式適用於:
- LanceDB / DuckDB / Arrow-based 儲存
- 需要儲存複雜巢狀物件
- 不需要對巢狀欄位建立索引
不適用情境
如果需要:
- 對
focus_items.skill_tags建立索引 - 在巢狀欄位上做 SQL-like 查詢
則應考慮:
- 正規化為多張表
- 使用 PostgreSQL + JSONB
- 使用 MongoDB
Career Knowledge Base 是一個本地優先的履歷知識庫系統,使用 Python + LanceDB + Voyage AI 建構。