From 3c1b2680744b40347d0a8db9ec13d813a1b73f5f Mon Sep 17 00:00:00 2001 From: LittleYellow Date: Thu, 23 Jul 2026 06:12:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=BE=A9=E5=90=AB=E7=A9=BA=E6=A0=BC/?= =?UTF-8?q?=E6=8B=AC=E8=99=9F=E6=AA=94=E5=90=8D=E7=9A=84=E5=9C=96=E7=89=87?= =?UTF-8?q?=E9=80=A3=E7=B5=90=E5=A4=B1=E6=95=88=EF=BC=8C=E8=87=AA=E6=AA=A2?= =?UTF-8?q?=E6=94=B9=E9=A9=97=E9=80=A3=E7=B5=90=E5=8F=AF=20render?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 來源檔名含空格或不平衡括號時,convert 產出的 Markdown 圖片連結會失效—— 圖檔明明存在於磁碟,整行卻退化成純文字(CommonMark 對未包住的空格與不平衡 括號視為分隔符)。用真 CommonMark parser 驗過:中文與成對括號正常,空格與 落單括號會壞。 - convert.py / from_docx.py / from_pdf.py / from_pptx.py:四處圖片連結目標 一律以 <> 包住(![name]()),同時涵蓋空格與不平衡括號。 - selfcheck.py:新增 assert_links_ok()——以 markdown_it 真 render,斷言圖片 連結數 == 數且目標檔存在,取代原本只檢查 "![" 字串在不在的表面斷言。 測資補內嵌圖片(docx/pdf/pptx 各一路徑)、檔名改含空格與不平衡括號。 - requirements.txt:明確宣告 markdown-it-py(原為 fastmcp 傳遞依賴,自檢直接 使用,宣告以免上游調整依賴樹後自檢失效)。 - AGENTS.md §13.4:新增「斷言要驗結果可用,不是驗字串存在」規則。 驗證:四個自檢全通過;移除任一處 <> 修法,assert_links_ok 即失敗。 ingest / lint / search 在含括號檔名下全鏈路已另行驗證正常(source_ref 走 YAML 純量,不受影響)。 Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 3 +++ requirements.txt | 2 ++ tools/convert/convert.py | 3 ++- tools/convert/from_docx.py | 4 +++- tools/convert/from_pdf.py | 3 ++- tools/convert/from_pptx.py | 3 ++- tools/convert/selfcheck.py | 49 +++++++++++++++++++++++++++++++++----- 7 files changed, 57 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 58db05d..09c0b2c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -276,6 +276,9 @@ output**)。單筆修正只治標;規則修正才治本。 語料要有跨頁共用的核心詞。便利值會變成盲點的形狀——ASCII 檔名讓 `git status` 的路徑轉義 bug 溜過,互不重疊的語料讓 BM25 的 idf 退化 bug 溜過。 新增檢查時先問:**這組測資和真實輸入差在哪?差異處就是沒被測到的地方。** +- **斷言要驗「結果可用」,不是驗「字串存在」**:`assert "![img](" in md` 只證明字串被 + 組出來,證明不了連結解得開——真正該做的是用解析器 render 後確認產出 `` + 且目標檔存在。測資照現實建了、斷言卻停在表面,bug 一樣會溜過去。 - 把任務轉成可驗證的目標,迭代到通過: - 「加驗證」→ 先寫無效輸入的測試,再讓它通過。 - 「修 bug」→ 先寫重現 bug 的測試,再讓它通過。 diff --git a/requirements.txt b/requirements.txt index a27987f..cb9040f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,5 @@ python-pptx # PowerPoint → Markdown trafilatura # 網頁快照 → Markdown 正文萃取 rank-bm25 # tools/search.py BM25(第一版不用向量庫) fastmcp # mcp/server.py 薄殼 +markdown-it-py # 自檢用:以 CommonMark 解析驗證產出的圖片連結真的能 render + # (原為 fastmcp 的傳遞依賴,明確宣告以免上游調整後自檢失效) diff --git a/tools/convert/convert.py b/tools/convert/convert.py index dcf556f..38143d5 100644 --- a/tools/convert/convert.py +++ b/tools/convert/convert.py @@ -125,7 +125,8 @@ def _vision_pdf(root, pdf_path, assets, conv_name): blocks = [] for i, (img, text) in enumerate(zip(images, texts), 1): rel = img.relative_to(md_dir).as_posix() - blocks.append(f"\n\n![page {i}]({rel})\n\n{text}") + # <> 包住:檔名含空格或不平衡括號時,未包住的連結會失效 + blocks.append(f"\n\n![page {i}](<{rel}>)\n\n{text}") return "\n\n".join(blocks), "ok", "from_vision" diff --git a/tools/convert/from_docx.py b/tools/convert/from_docx.py index c02ecb5..d284865 100644 --- a/tools/convert/from_docx.py +++ b/tools/convert/from_docx.py @@ -50,7 +50,9 @@ def _par_md(par, doc, assets_dir, md_dir, counter): rel = (assets_dir / name).relative_to(md_dir).as_posix() else: rel = name # dry-run:僅佔位 - parts.append(f"![{name}]({rel})") + # 連結目標以 <> 包住:來源檔名可能含空格或不平衡括號,兩者都會讓 + # Markdown 連結失效(圖片存在卻整行退化成純文字) + parts.append(f"![{name}](<{rel}>)") parts.append(run.text) text = "".join(parts).strip() lvl = _heading_level(par) diff --git a/tools/convert/from_pdf.py b/tools/convert/from_pdf.py index c450ff0..ffc375a 100644 --- a/tools/convert/from_pdf.py +++ b/tools/convert/from_pdf.py @@ -42,7 +42,8 @@ def convert(src, assets_dir=None, md_dir=None): assets_dir.mkdir(parents=True, exist_ok=True) pix.save(str(assets_dir / name)) rel = (assets_dir / name).relative_to(md_dir).as_posix() - blocks.append(f"![{name}]({rel})") + # <> 包住:檔名含空格或不平衡括號時,未包住的連結會失效 + blocks.append(f"![{name}](<{rel}>)") doc.close() return "\n\n".join(blocks), "ok" diff --git a/tools/convert/from_pptx.py b/tools/convert/from_pptx.py index 28ac585..ad43374 100644 --- a/tools/convert/from_pptx.py +++ b/tools/convert/from_pptx.py @@ -45,7 +45,8 @@ def convert(src, assets_dir=None, md_dir=None): rel = (assets_dir / name).relative_to(md_dir).as_posix() else: rel = name - blocks.append(f"![{name}]({rel})") + # <> 包住:檔名含空格或不平衡括號時,未包住的連結會失效 + blocks.append(f"![{name}](<{rel}>)") if slide.has_notes_slide: notes = slide.notes_slide.notes_text_frame.text.strip() if notes: diff --git a/tools/convert/selfcheck.py b/tools/convert/selfcheck.py index ad84361..bddd5fc 100644 --- a/tools/convert/selfcheck.py +++ b/tools/convert/selfcheck.py @@ -26,13 +26,39 @@ import to_image # 測資檔名一律用「中文 + 空格」——真實來源檔就長這樣,而 git status --porcelain # 對這兩者都會加引號轉義。ASCII 檔名的測資曾讓 ingest 的 preflight bug 溜過(§12)。 -DOCX, XLSX, PDF, SCAN = ("壓測 報告.docx", "測試案例 清單.xlsx", - "規格 說明.pdf", "掃描件 無文字層.pdf") +DOCX, XLSX, PDF, SCAN = ("壓測 報告(1).docx", "測試案例 清單.xlsx", + "規格 說明.pdf", "掃描件(2 期.pdf") PPTX, HTML, TXT, DOC = ("結案 簡報.pptx", "指引 快照.html", "不支援 筆記.txt", "舊版 報告.doc") +def assert_links_ok(md, base): + """每個圖片連結都要真的 render 成 、且目標檔存在。 + + 只斷言 "![" 在不在會讓損毀連結矇混過關——含空格/不平衡括號的檔名會產出 + 語法有效但解析失敗的連結,圖檔明明在磁碟上卻整行退化成純文字(§13.4)。 + """ + import re + import urllib.parse + from markdown_it import MarkdownIt + + want = md.count("![") + html = MarkdownIt("commonmark").render(md) + got = html.count("= 3, f"應至少驗到 docx/pdf/pptx 各一個圖片連結,實際 {linked}" + print(f"PASS: 圖片連結可 render 且目標存在({linked} 個,檔名含空格/括號)") + # 冪等:同檔再跑一次 → skip,manifest 不變 before = (root / "raw/manifest.json").read_text(encoding="utf-8") convert.main([str(fx / DOCX), "--root", str(root)]) @@ -228,6 +264,7 @@ def main(): assert len(conv_rel) == 1 and mv["files"][conv_rel[0]]["converter"] == "from_vision", mv md_v = (root_v / conv_rel[0]).read_text(encoding="utf-8") assert "可疑交易監控" in md_v and "(vision" in md_v and "![page 1]" in md_v, md_v + assert_links_ok(md_v, root_v / "raw/converted") # vision 頁的連結同樣要能 render print("PASS: convert.py --vision 端到端(needs_ocr → 本地 vision 轉錄)") print("ALL PASS")