"""Smoke: read_document(豆包文档理解)端到端走通 + 扫描件 OCR + save_md 落盘验证。 跑法: .venv/Scripts/python.exe scripts/smoke_read_document.py 依赖 .env 里 ARK_API_KEY / ZCBOT_DB_URL。**会真调豆包文档理解,产生 < ¥0.01 费用**。 校验: 1. 合成 3 页扫描件 PDF(复用 probe_ark_doc 的生成器,无文本层) 2. ReadDocumentTool.execute(save_md=...) 返回 banner + saved: + 预览 3. save_md 文件落盘且三页魔术串全命中(页覆盖 + OCR 保真) 4. usage_events 多一行 kind="vision",units 含 document 路径 5. _count_pdf_pages 软探页数 = 3(页数闸门的数据源) """ from __future__ import annotations import sys import uuid from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts")) try: sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[attr-defined] except Exception: pass # probe_ark_doc import 时自带 .env 加载 + sys.path 处理 from probe_ark_doc import _magic, make_scanned_pdf from sqlalchemy import text from core.ark_client import ArkConfig from core.storage import session_scope from core.storage.models import Task, User from tools.read_document import ReadDocumentTool, _count_pdf_pages def main() -> int: cfg = ArkConfig.load() if cfg is None: print("[SKIP] ARK_API_KEY 未设(或 doubao.yaml 缺失),无法测真接口") return 0 vision_cfg = (cfg.raw.get("vision") or {}) if not vision_cfg: print("[SKIP] doubao.yaml 无 vision 段") return 0 variant_key, variant_cfg = next(iter(vision_cfg.items())) print(f"[setup] variant={variant_key} model={variant_cfg.get('model_id')} " f"max_pdf_mb={variant_cfg.get('max_pdf_mb')} " f"max_pdf_pages={variant_cfg.get('max_pdf_pages')}") uid = uuid.uuid4() tid = uuid.uuid4() ws_user = ROOT / "workspace" / "users" / str(uid) wd = ws_user / "smoke_readdoc" pdf = wd / "upload" / "scan.pdf" pdf.parent.mkdir(parents=True, exist_ok=True) make_scanned_pdf(pdf, 3) print(f"[setup] 合成扫描件 {pdf.name}({pdf.stat().st_size} bytes, 3 页, 无文本层)") n = _count_pdf_pages(pdf) assert n == 3, f"_count_pdf_pages 应 3,实际 {n}" print(f"[OK] _count_pdf_pages = {n}") with session_scope() as s: s.add(User(user_id=uid)) with session_scope() as s: s.add(Task(task_id=tid, user_id=uid, name="smoke_readdoc", working_dir=str(wd))) tool = ReadDocumentTool( ark_cfg=cfg, vision_variant_cfg=variant_cfg, variant_key=variant_key, working_dir=wd, task_id=tid, user_id=uid, base_dir=wd, user_root=ws_user, ) print("[call] execute(document='upload/scan.pdf', save_md='source/scan.md')") result = tool.execute(document="upload/scan.pdf", save_md="source/scan.md") print(f"[tool result]\n{result}\n") if result.startswith("[Error]"): print("[FAIL] tool 返回错误") return 2 assert "saved:" in result, "返回里缺 saved: 行" saved = wd / "source" / "scan.md" assert saved.is_file(), f"save_md 未落盘: {saved}" body = saved.read_text(encoding="utf-8") flat = body.replace(" ", "").replace("-", "") magics = [_magic(i) for i in range(3)] hits = [m for m in magics if m.replace("-", "") in flat] assert len(hits) == 3, f"魔术串命中 {len(hits)}/3(漏 {set(magics) - set(hits)})" print(f"[OK] save_md 落盘 {len(body)} 字符,3/3 魔术串命中") with session_scope() as s: rows = s.execute(text( "SELECT kind, model_profile, units, cost_cny FROM usage_events " "WHERE task_id = :tid" ), {"tid": str(tid)}).all() assert len(rows) == 1, f"usage_events 行数应 1,实际 {len(rows)}" row = rows[0] assert row.kind == "vision", f"kind 应 vision,实际 {row.kind}" assert "document" in row.units, f"units 缺 document: {row.units}" print(f"[OK] usage_events: kind={row.kind} model={row.model_profile} " f"cost_cny={row.cost_cny} units={row.units}") print("\n[PASS] smoke_read_document 全部通过") return 0 if __name__ == "__main__": sys.exit(main())