轉換工具強化:失敗診斷訊息 + 舊版格式升版 + 掃描 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:
LittleYellow
2026-07-21 07:00:32 +08:00
parent 1d1454e1b9
commit cfd1876359
7 changed files with 428 additions and 37 deletions

48
tools/convert/to_image.py Normal file
View File

@@ -0,0 +1,48 @@
"""PDF 頁 → PNG 影像pymupdfdeterministic、全程本地
供 vision fallback 使用文字抽取失敗needs_ocr的掃描/圖片型 PDF 逐頁 render
成影像後,交給 from_vision.py 以本地 vision 模型判讀。render 本身不含任何 LLM
維持「deterministic 工具做結構、LLM 只做語意」的分工AGENTS.md §3 稽核原則)。
可獨立執行python tools/convert/to_image.py <file.pdf> --out DIR [--dpi 150]
"""
import argparse
import pathlib
import fitz # pymupdf
# ponytail: 預設 150 DPI 是「可讀性 vs 影像大小/推論成本」的折衷;掃描件文字偏小時
# 可調高。升級路徑:依頁面尺寸/內容自適應 DPI。
DEFAULT_DPI = 150
def render_pdf_pages(src, out_dir, dpi=DEFAULT_DPI, prefix="page"):
"""將 src 每頁 render 成 PNG回傳影像路徑列表依頁序"""
src = pathlib.Path(src)
out_dir = pathlib.Path(out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
paths = []
doc = fitz.open(str(src))
try:
for i, page in enumerate(doc, 1):
pix = page.get_pixmap(dpi=dpi)
p = out_dir / f"{prefix}{i}.png"
pix.save(str(p))
paths.append(p)
finally:
doc.close()
return paths
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("src")
ap.add_argument("--out", required=True, help="影像輸出目錄")
ap.add_argument("--dpi", type=int, default=DEFAULT_DPI)
a = ap.parse_args(argv)
paths = render_pdf_pages(a.src, a.out, a.dpi)
print(f"{len(paths)} 頁 → {a.out}")
if __name__ == "__main__":
main()