轉換工具強化:失敗診斷訊息 + 舊版格式升版 + 掃描 PDF vision fallback
- convert.py:轉換失敗改印例外型別、批次尾端彙總、--traceback 旗標; 空白轉換結果示警(避免純圖片 docx 等假成功靜默入庫) - from_legacy.py(B):.doc/.xls/.ppt 經本地 LibreOffice headless 升版為 現代格式後再走既有轉換器;raw/originals 仍保存真正的舊格式原始檔 - to_image.py + from_vision.py(A):needs_ocr 的掃描/圖片型 PDF 逐頁 render → 本地 vision 模型轉錄(--vision);:cloud/未設定防護, 模型設定走 config/models.yaml 的 tasks.vision(預設不啟用) - kb.ollama_chat 加 images 參數(向後相容);models.yaml 加 vision 範例 - selfcheck 補 legacy 路由、to_image 真測、from_vision 注入測、 --vision 端到端,全數 ALL PASS Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
61
tools/convert/from_vision.py
Normal file
61
tools/convert/from_vision.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""影像 → Markdown 轉錄(本地 vision 模型,AGENTS.md §1.1 資料落地)。
|
||||
|
||||
用於 vision fallback:文字抽取失敗的頁面 render 成影像後,逐頁交本地 vision
|
||||
模型忠實轉錄為 Markdown。模型設定讀 config/models.yaml 的 tasks.vision(§1.2),
|
||||
未設定即報錯、不使用預設值;:cloud 模型一律拒絕(影像不得離開本機)。
|
||||
"""
|
||||
import base64
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) # tools/ 供 import kb
|
||||
import kb
|
||||
|
||||
VISION_PROMPT = (
|
||||
"你是文件轉錄引擎。忠實轉錄此頁影像中的所有文字與表格內容為 Markdown:"
|
||||
"表格用 Markdown table,保留標題階層。只輸出轉錄內容本身,不要加任何說明或臆測。"
|
||||
"影像中沒有可辨識內容時,只回覆「(本頁無可辨識文字)」。")
|
||||
|
||||
|
||||
def _require_vision(cfg):
|
||||
"""取得並驗證 vision 模型設定。缺設定 / :cloud 皆報錯(信任邊界,§1.1/§1.2)。"""
|
||||
spec = (cfg.get("tasks") or {}).get("vision")
|
||||
if not isinstance(spec, dict) or not spec.get("model") or spec["model"] == "CHANGE_ME":
|
||||
raise SystemExit(
|
||||
"models.yaml 未設定 tasks.vision.model;--vision 需要合規本地 vision 模型"
|
||||
"(請自行確認非中國關聯、非 :cloud,AGENTS.md §1.1/§1.3)。")
|
||||
if str(spec["model"]).endswith(":cloud"):
|
||||
raise SystemExit("tasks.vision.model 為 :cloud 模型,禁止(文件影像不得離開本機,§1.1)。")
|
||||
return spec
|
||||
|
||||
|
||||
def _b64(path):
|
||||
return base64.b64encode(pathlib.Path(path).read_bytes()).decode("ascii")
|
||||
|
||||
|
||||
def transcribe(cfg, image_paths):
|
||||
"""逐頁轉錄影像,回傳 (每頁 markdown 列表, 實際使用的 model)。
|
||||
|
||||
單頁重試 max_retries 次仍失敗即拋 RuntimeError(vision 無文字 fallback 可用——
|
||||
fallback 模型非 vision,不切換)。
|
||||
"""
|
||||
spec = _require_vision(cfg)
|
||||
model = spec["model"]
|
||||
retries = int(spec.get("max_retries", 2))
|
||||
temperature = float(spec.get("temperature", 0.1))
|
||||
pages = []
|
||||
for idx, img in enumerate(image_paths, 1):
|
||||
b64 = _b64(img)
|
||||
last = None
|
||||
for _ in range(max(1, retries)):
|
||||
try:
|
||||
text = kb.ollama_chat(cfg, model, VISION_PROMPT, temperature, images=[b64])
|
||||
if text and text.strip():
|
||||
pages.append(text.strip())
|
||||
break
|
||||
last = "空回應"
|
||||
except Exception as e:
|
||||
last = f"{type(e).__name__}: {e}"
|
||||
else:
|
||||
raise RuntimeError(f"vision 轉錄失敗(第 {idx} 頁,model={model}):{last}")
|
||||
return pages, model
|
||||
Reference in New Issue
Block a user