All skills
cat-xierluo avatar

/legal-ocr

@efe1541

本技能应在用户需要 OCR、扫描识别、图片文字识别、文档识别,或将 PDF、图片、Office 文档、URL 转换为 Markdown 时使用。检测到法律材料时可进行保守的法律术语与文书结构优化。不要用于法律事实判断、补写缺失内容、语义改写、印章深度识别或图表实体分析。

Use this Skill: https://skilld.dev/gh/cat-xierluo/legal-skills/legal-ocr

This session only. Nothing lands on disk.

CHANGELOG.md

≈5.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

变更记录

[1.6.0] - 2026-09-18

新增:本地 RapidOCR 后端(--backend rapid)

背景:本技能此前没有任何本地 OCR 引擎——两套云端 API 都未配置时走 MinerU 轻量接口(仍是云端),敏感材料"不出本机"场景无解。RapidOCR(PP-OCR 系模型的 onnx 本地推理)中文行级识别质量已接近云端,补齐了这个缺口。

  • 新模块 scripts/rapid_ocr.py:本地 PDF(pypdfium2 渲染,默认 220 DPI,LEGAL_OCR_RAPID_DPI 可调)与常见图片输入;行级识别结果按几何规则排成视觉行(同行按 x 拼接),CJK/数字间 OCR 空格与全角数字统一归一。
  • 段落重建交给统一后处理链的硬换行整理(与 PDF 文本层直读分支行为对称),其编号/法律标签/标题保留规则比纯几何段落判定可靠;页间插空行阻断跨页串段。
  • 路由:--backend rapid 显式指定;auto 模式下本地 PDF/图片把 rapid 作为云端 API 失败后的最后一级候选;两套 API 都未配置且本机已装 RapidOCR 时优先本地识别(材料不出本机),MinerU 轻量接口退为兜底。
  • 依赖可选:未安装时 auto 不受影响;显式 --backend rapid 给出安装提示(uv run --with rapidocr --with onnxruntime ... 或 pip install rapidocr 后用 python3 直跑)。
  • 能力边界(写入 metadata.limitations):无版面分析,单栏文书可靠、多栏可能错序;不提取图片资源(印章/签名/图表不出现在 Markdown);Office/URL 不支持(仍走 MinerU)。

改进

  • linebreaks.py 法律标签表补充 具状人|答辩人|证据[一二三四五六七八九十\d]+:——起诉状落款与证据列表条目不再被硬换行整理粘进上一段;该修复同时惠及文本层直读与云端后端输出。

[1.5.0] - 2026-07-10

新增:PDF 原生文本层双路径(先直读,不达标再 OCR)

背景:法律场景里法院电子送达的判决书、电子合同、政府公文等相当一部分 PDF 本就带可靠的原生文本层。直读比 OCR 更准(避免识别错误)、更快(无网络往返)、 更省(不耗 PaddleOCR / MinerU 额度)。参考同类工具用文本密度阈值 重判 is_scanned 决定走文本管线 vs OCR 管线。

行为
  • 进入 OCR 后端之前,新增「文本层直读分支」(仅本地 .pdf):
    1. 用 pypdfium2 逐页 get_textpage().get_text_range() 抽取文字;
    2. 计算质量指标(文本页覆盖率、平均 CJK / 页、乱码比例、总字符数);
    3. 全部阈值达标 → 直接转 Markdown,复用既有 post-process(法律术语 → 硬换行整理 → 基础清理);
    4. 任一不达标 → 落回原有 OCR 候选(PaddleOCR → MinerU)。
  • 文本层分支排在 所有 OCR 后端之前;不可用时透明回退,旧工作流完全兼容。
新参数
  • --text-layer auto|never|always(CLI)
  • LEGAL_OCR_TEXT_LAYER(env,等价)
  • LEGAL_OCR_TEXT_LAYER_MIN_COVERAGE(默认 0.8,文本页覆盖率下限)
  • LEGAL_OCR_TEXT_LAYER_MIN_CHARS_PER_PAGE(默认 50,文本页平均 CJK 字符下限)
  • LEGAL_OCR_TEXT_LAYER_MAX_GARBLE_RATIO(默认 0.05,PUA + 替换字符 + 非常见字符占比上限)
  • LEGAL_OCR_TEXT_LAYER_MIN_TOTAL_CHARS(默认 100,非空白字符总数下限)

阈值默认值依据见 DECISIONS.md「文本层质量阈值(v1.5.0)」段,待真实卷宗校准。

与 --backend 的优先级
  • --backend auto(默认)+ --text-layer auto(默认):先文本层、不达标再 OCR,最优路径。
  • --backend paddle|mineru:视为用户显式想要 OCR,跳过文本层分支;除非同时设 --text-layer always 强制覆盖。
  • --text-layer never:完全禁用文本层,回到旧版纯 OCR 行为。
  • --text-layer always:强制文本层,PDF 没有可用文本层时直接报错退出(exit=2),便于排障。
实现要点
  • 新增 scripts/text_layer.py:探测 + 抽取 + 阈值加载,独立可测。
  • scripts/convert.py 提取 run_postprocess_pipeline / finalize_conversion 两个共享函数; OCR 分支与文本层分支共用同一套 post-process + archive,输出风格保持一致。
  • 文本层结果以合成的 BackendResult(backend='text_layer', mode='native_pdf_text', provider='pypdfium2') 形式进入既有 archive 流水线;metadata.json 多出 text_layer 字段,记录 probe 全量指标, 方便复盘为什么走了文本层 / 为什么没走。
  • 借鉴同类工具的文本密度判定思路,未引入任何第三方代码。
自造样本烟测

造两份本地样本验证分流正确(详见 PR 描述):

样本 期望 实际
clean_text_layer.pdf(reportlab + STHeiti,3 页中文判决片段) --text-layer auto 直读,后端=text_layer ✅ coverage=1.0、cjk/page=112、garbled=0、走文本层
scanned_no_text_layer.pdf(PIL 渲染图片转 PDF,1 页) --text-layer auto 回退到 OCR 后端 ✅ reason=no_text_layer、回退 MinerU light
scanned_no_text_layer.pdf + --text-layer always 直接失败、exit=2 ✅ 报错文案清晰、exit=2
clean_text_layer.pdf + --text-layer never 强制走 OCR ✅ 跳过文本层、回退 MinerU light

文档完善

  • SKILL.md 新增「PDF 文本层双路径」章节、参数表加 --text-layer、版本号 → 1.5.0。
  • 新增 references/text-layer-detection.md:探测算法、字符分类、阈值理由、失败模式。
  • .env.example 顶部加 LEGAL_OCR_TEXT_LAYER* 配置块。

关联

  • 任务源:legal-ocr TASKS.md「PDF 文本层双路径」条目(local-only,不在 PR diff 内)。
  • 调研缘由:参考同类工具的文本密度判定实践。
  • 决策记录:DECISIONS.md「文本层质量阈值(v1.5.0)」段(local-only)。

[1.4.3] - 2026-06-14

🔥 真修:httpx.Client(..., trust_env=False) 全 5 处加固 — cron 死循环 root cause 治本

经过 1.4.1(3 处 except 探针)+ 1.4.2(入口兜底)+ book-ocr-manager 0.6.9(信道扩宽) 三层诊断铺路,用户第七次手测拿到完整 traceback,锁定真因:httpx 默认 trust_env=True, 会从环境变量 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY 读 proxy URL 构建 mounts。 cron / Agent 沙箱子进程下,proxy env 可能含畸形值(用户 ~/.zshrc 写: HTTPS_PROXY=http://127.0.0.1:$_opencode_proxy_port,变量未定义时展开为 http://127.0.0.1:), httpx 在 _urlparse.py:411 normalize_port 抛 InvalidURL: Invalid port: ':1]', 100% 阻塞 OCR 调用。

修复

5 处 httpx.Client(...) / httpx.get(...) 全部加 trust_env=False:

  • paddle_ocr.py:413 _make_request._post(同步提交)
  • paddle_ocr.py:526 _submit_async_job._post(异步提交)
  • paddle_ocr.py:558 _poll_async_job 客户端(轮询 + JSONL 下载)
  • mineru_ocr.py:218 _self_test 直调 httpx.get(token 自检)
  • mineru_ocr.py:245 _client()(MinerU 所有请求公用客户端)

为什么 trust_env=False 合适

  • legal-ocr 调的是 公网 API(paddleocr.aistudio-app.com / mineru.net),不应该走本机代理
  • 代理对内网 / 翻墙服务才有意义;公网 API 走 socks/http proxy 只会带来污染风险
  • trust_env=False 是 httpx 推荐的"程序化 client"标准做法
  • 不影响手动跑(手动跑用户已无 proxy env 时本来就 OK);只在 cron / Agent 沙箱下提供保护

决定性验证(本地复现)

# 修复前:必抛
$ HTTPS_PROXY="http://127.0.0.1:1]" python3 -c "import httpx; httpx.Client(timeout=30)"
InvalidURL: Invalid port: '1]'

# 修复后:exit=0,2 个 backend 自检通过
$ HTTPS_PROXY="http://127.0.0.1:1]" uv run --script scripts/convert.py checktoken
legal-ocr 配置自检
===============================================
MinerU: Token 自检通过…
PaddleOCR: 已检测到必要配置。

建议用户做的清理

修复后,之前所有被 Invalid port: ':1]' 击败的 segments(SQLite segments.status='failed' AND last_error LIKE '%Invalid port%')都不再是真失败,可以批量重置为 planned(本机统计约 7264 段 / 1501 本书)。详见 book-ocr-manager 同步 bump 到 0.6.10 的 CHANGELOG。

关联

  • TASKS.md(book-ocr-manager)5 号问题第 127-153 行:用户第七次手测的 traceback 是真因锁定的关键证据
  • 1.4.1 / 1.4.2 的探针都保留(覆盖未来其他类型错误);本版加 trust_env=False 是定向治本
  • DEC-025(待写):本版决策矩阵 + 复现剧本 + 7264 段重置建议

[1.4.2] - 2026-06-14

入口级兜底 catch — 1.4.1 探针的补完

背景:1.4.1 在 3 处 httpx 调用点装的 except httpx.InvalidURL 在 cron 跑批时全没触发。 说明真因更早(可能在 PaddleOCRBackend.__init__ / MinerUBackend.__init__ / route.choose_backend 等阶段就抛了),绕过了内部 catch。

  • convert.py:__main__ — raise SystemExit(main()) 改为 try ... except BaseException: traceback.print_exc(file=sys.stderr); raise SystemExit(1),入口层兜底:不管哪行抛错,完整 stack trace 必进 stderr。
  • 与 1.4.1 的 traceback dump 行为重叠时不冲突(SystemExit 直接 re-raise,不进 catch 块;真正异常都被入口 catch 兜底)。
  • 配合下游 book-ocr-manager 0.6.9 的 run_ocr.py:326 信道扩宽(从只抓 stderr 最后一行改为抓后 30 行),下次 cron 触发的 events.message 必含完整 traceback。

用户感知

下次 OCR 失败时,SQLite events.message 不再是 24 字符短文案,而是含完整 stack trace,例如:

convert.py 入口异常:InvalidURL: Invalid port: ':1]'
Traceback (most recent call last):
  File ".../convert.py", line ..., in <module>
    ...
  File ".../paddle_ocr.py", line ..., in __init__
    ...
httpx.InvalidURL: Invalid port: ':1]'

直接锁定真正抛错的文件 + 行号 + 完整调用栈。

关联

  • 1.4.1 加的 3 处 except 仍保留(覆盖典型场景),本版只在入口加兜底
  • book-ocr-manager 同步 bump 到 0.6.9,加配套信道扩宽
  • 烟雾测试:convert.py --help / convert.py checktoken(本机 + 模拟空 PATH 两种 env) 均正常 exit=0

[1.4.1] - 2026-06-14

诊断性 logging 增强(不改业务逻辑)

背景:book-ocr-manager cron 大批 OCR 失败,所有 events.message 都是 24 字符短文案 "最后错误:Invalid port: ':1]'", 无法定位是 PaddleOCR / MinerU 哪个 backend、哪一行 httpx 调用、哪个 url 抛的。 根因是 convert.py 把 str(last_error) dump 到 stderr,抹平了 type 和 traceback。

  • convert.py:print(f"最后错误:{last_error}") 改为先打印完整 traceback.format_exception(...),末行保留旧前缀但补 type(last_error).__name__(让上游 run_ocr.py:326 抓 stderr 最后一行时能看到异常类,如 httpx.InvalidURL)
  • paddle_ocr.py:_poll_async_job:client.get(jsonl_url) 增加 except httpx.InvalidURL,失败时 raise RuntimeError(f"... jsonUrl={jsonl_url!r} ...") — jsonl_url 来自 PaddleOCR 服务端响应 data.resultUrl.jsonUrl 字段,是可疑的畸形 URL 来源之一
  • mineru_ocr.py:_download_zip_and_extract:client.get(result_url) 增加 except httpx.InvalidURL,失败时带 full_zip_url 真值 raise
  • mineru_ocr.py:_upload_via_ticket:client.put(upload_url, ...) 增加 except httpx.InvalidURL,失败时带 file_urls[0] 原值 + normalize 后 同时输出 — _normalize_upload_url 不做 URL 校验,是 最高嫌疑点

用户感知

下次 Invalid port 复现时,events.message 末尾会变成类似:

最后错误:RuntimeError: MinerU 上传 URL 非法:file_urls[0]='...' normalized='...' (httpx.InvalidURL: Invalid port: ':1]')

或 PaddleOCR 路径:

最后错误:RuntimeError: PaddleOCR JSONL 下载 URL 非法:jsonUrl='...' (httpx.InvalidURL: Invalid port: ':1]')

直接锁定后端、字段、真实 URL,无需再追代码。

关联

  • 触发分析:book-ocr-manager TASKS.md 第 83-106 行(2026-06-14 cron 第四次跑批 1154 段 100% Invalid port)
  • 此版本只增强 logging、不改业务路径;真修复(URL 校验 / _normalize_upload_url 加 httpx.URL 检查)等下次 cron 触发拿到真 URL 后定向修
  • 烟雾测试:convert.py --help / convert.py checktoken 均正常,语法 + import 链路无破坏

[1.4.0] - 2026-06-06

改进

  • 新增瞬态网络错误自动重试:所有 HTTP 调用(同步提交、异步提交、异步轮询、MinerU 上传/轮询/下载、Token 自检)通过统一的 retry_with_backoff 包装;DNS 失败、连接失败、读取超时等 httpx.RequestError 会被指数退避重试 2-3 次。
  • 退避参数通过环境变量控制:LEGAL_OCR_RETRY_ATTEMPTS(默认 3)、LEGAL_OCR_RETRY_BASE_DELAY(默认 1.0s)、LEGAL_OCR_RETRY_MAX_DELAY(默认 30.0s);PADDLEOCR_RETRY_* 与 MINERU_RETRY_* 可覆盖单后端。
  • 重试前向 stderr 输出一行 PaddleOCR/MinerU 瞬态错误 … 日志,便于排查真实网络问题。

文档完善

  • SKILL.md 新增「瞬态错误自动重试」章节,说明范围、瞬态定义、默认参数和配置项。
  • .env.example 顶部与各后端小节均补充 RETRY_* 变量。

[1.3.3] - 2026-06-05

修复

  • PaddleOCR 同步接口新增返回页数校验:当 result.dataInfo.numPages 或 result.dataInfo.pages 长度少于本地 PDF 批次页数时,转换直接失败并提示降低 PADDLEOCR_BATCH_PAGES 或使用 --pages 重跑。

改进

  • PaddleOCR 批次元数据新增 expected_pages 和 returned_pages,便于排查大 PDF 缺页、服务端单次返回上限等问题。

文档完善

  • 在 SKILL.md 故障排除中补充“PaddleOCR 返回页数不足”的处理方式。

[1.3.2] - 2026-06-03

优化

  • archive 不再单独保存输入副本:移除 archive/<时间戳>_<名称>/input/ 目录及对应的 shutil.copy2 / source_url.txt 写入逻辑。
  • 输入文件元信息继续保留在 metadata.json 的 source 字段:本地文件记录 path / sha256 / size_bytes;远程 URL 记录原始字符串。
  • 同步更新 SKILL.md 与 references/output_schema.md 的 archive 结构说明。

Reason

  • 现状:skills/legal-ocr/archive/ 累计 649 MB;6 个 archive 平均 100 MB+,主要来自 input/ 下的原 PDF 副本。
  • 风险:随着 OCR 任务增多本地磁盘会持续膨胀;archive 已在 .gitignore 内,不会被 push,但本地占用无法控制。
  • 取舍:放弃 archive 内"可重放原文件"的能力,换取固定占用;如需重新转换,使用 metadata.json.source.path(本地)或 metadata.json.source.raw(URL)重跑即可。

[1.3.1] - 2026-05-20

文档完善

  • 精简 SKILL.md frontmatter description,仅保留 OCR/扫描识别/文档识别等功能触发条件和必要边界,不再描述后端路由等实现细节。
  • 同步 README 与 marketplace 中的 legal-ocr 简介。

[1.3.0] - 2026-05-20

改进

  • 将技能定位调整为“通用 OCR + 法律材料自动增强”:用户需要 OCR、扫描识别、图片文字识别或文档识别时可直接调用,非法律材料默认保持通用 OCR 输出。
  • LEGAL_OCR_LEGAL_TERMS 默认改为 auto,仅在检测到法院文书、案号、当事人标签、判决/裁定结构等法律信号时启用法律术语优化。
  • 新增 --legal-terms auto|always|never 参数,支持单次自动、强制或关闭法律术语优化。
  • result.json 与 metadata.json 新增法律上下文检测记录,便于复核为什么启用或跳过法律增强。

文档完善

  • 更新 SKILL.md 触发条件,明确可替代普通 OCR 场景,并说明法律增强的自动检测机制。
  • 更新 .env.example、references/output_schema.md 和 references/legal_terms.md,同步 auto 模式说明。

[1.2.2] - 2026-05-20

技术优化

  • 按 skill-lint 规范扁平化 scripts/ 目录,移除 scripts/backends/ 与 scripts/postprocess/ 子目录。
  • 优化脚本依赖防护,缺少 httpx 或 pypdfium2 时给出清晰安装提示。
  • 优化 SKILL.md frontmatter 描述,明确触发场景和不适用边界。

文档完善

  • 补充 Python 包依赖表和输入/输出说明。

[1.2.1] - 2026-05-20

改进

  • 法律术语断字合并支持跨单个换行,处理 本院认\n为、人\n民\n法\n院 等 OCR 断行场景。
  • 新增保守型 OCR 硬换行整理,合并明显属于同一中文段落的物理换行,同时保留标题、当事人标签、编号、表格和 Markdown 结构。
  • 扩充常见法律词表,补充审理经过、争议焦点、执行标的、案件受理费、迟延履行期间等常见文书词。
  • 新增 LEGAL_OCR_LINE_MERGE 与 --no-line-merge,可关闭硬换行整理。

文档完善

  • 补充换行整理边界说明,强调不做事实改写和过度纠错。

[1.2.0] - 2026-05-20

新增

  • 新增保守型法律术语优化后处理,默认修正常见 OCR 断字、异体字和法律文书标签格式。
  • 新增自定义术语文件支持,可通过 LEGAL_OCR_CUSTOM_TERMS_PATH 加载 JSON 替换表。
  • 新增 --no-legal-terms 参数,可单次跳过法律术语优化。
  • 新增 references/legal_terms.md 与 config/legal_terms.example.json,说明默认处理范围和自定义格式。

改进

  • 后处理顺序调整为先做法律术语优化,再做标题和条文结构整理,提升 本院认为、判决如下 等文书结构识别稳定性。
  • 法律术语替换记录写入 postprocess_log.json,并保留 result_raw.md 便于人工复核。

[1.1.1] - 2026-05-20

改进

  • 自动路由调整为配置优先:只配置 PaddleOCR 时优先使用 PaddleOCR,只配置 MinerU Token 时所有支持输入统一走 MinerU,两套 API 都配置时再按材料类型选择最优后端。
  • 两套 API 都配置时保留候选后端;首选后端失败后可自动尝试下一后端。
  • 转换失败记录新增错误分类,支持识别额度/频率限制、鉴权失败、轻量接口超限、不支持类型、超时和网络问题。

文档完善

  • 补充自动分流说明,明确当前额度判断来自 API 响应码和错误信息,暂未接入独立额度预检接口。

[1.1.0] - 2026-05-20

新增

  • 复制旧 paddle-ocr 与 mineru-ocr 的本地 .env 配置到 legal-ocr/config/.env,保留原 Token 和后端设置;该文件继续被 Git 忽略。
  • PaddleOCR 后端新增 pdf-processor 同源能力:兼容 PADDLE_OCR_API_ENDPOINT / PADDLE_OCR_API_KEY / API_URL / TOKEN 变量别名。
  • PaddleOCR 后端新增异步任务协议支持,可调用 /api/v2/ocr/jobs 并轮询 JSONL 结果。
  • 新增 PP-OCRv5 与 PaddleOCR-VL-1.5 模型选择,支持 --paddle-model 参数和 PADDLEOCR_MODEL 配置。
  • 新增 PaddleOCR optionalPayload 扩展:支持 VL 版面检测、图表识别、方向/去畸变、文本行方向、可视化和额外 JSON payload。

改进

  • checktoken / smoke test 可识别 PaddleOCR 旧变量和 pdf-processor 变量别名。
  • PaddleOCR archive 中记录 API 协议、模型、轮询参数和 payload 配置。

[1.0.0] - 2026-05-20

新增

  • 新增 legal-ocr 统一 OCR Skill,整合 PaddleOCR 与 MinerU 双后端。
  • 新增自动路由:本地 PDF/图片默认走 PaddleOCR,Office 文档、远程文档 URL 和网页 URL 默认走 MinerU。
  • 新增统一 Python 主入口 scripts/convert.py,支持 --backend、--output、--pages、--archive-name、--no-archive、--no-post-process、--model。
  • 新增 MinerU Python 后端,覆盖 local/token、local/light、remote/token、remote/light 四条转换链路。
  • 新增 PaddleOCR Python 后端,保留本地 PDF 自动分批、图片提取和 Markdown 输出能力。
  • 新增统一 archive 结构,保留输入、最终 Markdown、原始 Markdown、结构化 JSON、后端原始结果和元数据。
  • 新增基础法律后处理,支持空行清理和简单法律标题结构识别。
  • 新增 scripts/smoke_test.py 和 JXA 兼容入口 scripts/convert.js。

文档完善

  • 新增 SKILL.md、references/output_schema.md、TASKS.md、DECISIONS.md 和 LICENSE.txt。
  • 新增统一 .env.example,保留 PADDLEOCR_* 与 MINERU_* 变量名并补充 LEGAL_OCR_* 设置。

待办事项

  • 增加真实法律 PDF、病历、票据和网页 URL 的回归样本。
  • 评估复杂法律词典纠错、印章标注和图表标注规则。

Source: SKILL.md on GitHub

2 warnings12d3 checks · Risk SAFE
  • Gen Agent Trust Hub12d

    The legal-ocr skill is a robust document conversion tool that supports multiple OCR backends (PaddleOCR, MinerU, and local RapidOCR). It includes specialized post-processing for legal documents. The skill presents a surface for indirect prompt injection by processing untrusted documents and URLs, and it performs network downloads for image assets and processing results.

  • Socket12d

    1 alert: gptAnomaly

  • Snyk12d

    Risk: MEDIUM · 1 issue

Signed by skilld at efe1541. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 2 weeks ago
version
1.6.0
author
杨卫薪律师(微信ywxlaw)
homepage
https://github.com/cat-xierluo/legal-skills

README badge

README badge for cat-xierluo/legal-skills/legal-ocr