仓库里的 examples/example.pdf 是一份 10 页政府采购文件。当前流水线可以从中得到类似下面的结构化结果:
项目名称 合肥市公安局瑶海分局雪亮工程支网一期、二期、三期运维服务采购项目
项目编号 2024BFFFZ01583
采购人 合肥市公安局瑶海分局
金额 437.677万元
日期 2024年7月1日17时30分
联系电话 055166223642
它不是简单的 key: value。字段可以同时保留来源证据:
{
"project_number": {
"primary_value": "2024BFFFZ01583",
"confidence": 0.99,
"values": [
{
"location": {
"document_id": "example.pdf",
"page": 1,
"source_text": "项目编号:2024BFFFZ01583"
}
}
]
}
}对于能够可靠定位的 PDF 文本,还可以保留页面坐标,因此上层系统可以继续实现:
字段 → 来源文件 → 页码 → 原文片段 → PDF 坐标 / 高亮
直接使用最新发布镜像,不需要先准备 Python 环境:
IMAGE=ghcr.io/inupedia/tender-extract-server:latest
docker pull "$IMAGE"
docker run --rm \
-p 8000:8000 \
-v tender-extract-cache:/data/cache \
"$IMAGE"上传一份 PDF:
curl -s \
-F "file=@example.pdf" \
"http://localhost:8000/v1/extract?llm_provider=none"启动后可直接打开 http://localhost:8000/docs 查看 OpenAPI 文档。
latest适合快速体验和跟随最新正式版本。生产环境需要可复现部署时,建议固定到ghcr.io/inupedia/tender-extract-server:<version>。Git tagvX.Y.Z会发布 Docker tag:X.Y.Z,并同步更新:latest。
PDF / DOCX / Markdown / TXT
│
▼
文档解析与分块
│
▼
规则 / 结构抽取
│ │
高置信直接输出 低置信 / 冲突 / 缺失
│ │
│ ▼
│ LLM 复核
│ │
└─────┬─────┘
▼
合并 + 证据定位
│
▼
结构化 JSON
默认先用规则与结构化方法完成可确定字段;只有低置信、冲突或缺失字段才进入可选的 LLM 复核流程。
| 能力 | 做什么 |
|---|---|
| 混合抽取 | 规则、词典、NER 与可选 LLM 复核组合使用 |
| 证据定位 | 保留文件、页码、原文片段,PDF 可进一步保留 bbox |
| 项目级多文件 | 招标文件、补遗、澄清、多个投标人文件统一处理,同时保留版本与投标人边界 |
| 人工复核 | 接受、修改、驳回低置信结果,并回流为标注数据 |
| 质量评测 | Precision / Recall / F1 与 CI 质量门禁 |
| 多种入口 | Python CLI、HTTP API、Docker / GHCR |
| 项目 | 支持情况 |
|---|---|
| 文档格式 | PDF、DOCX、Markdown、TXT |
| 扫描件 | 可选 OCR;轻量 Server 镜像默认不内置 PaddleOCR |
| LLM | OpenAI、Azure OpenAI、Claude、Gemini、DeepSeek、通义千问、Kimi、GLM、Doubao、SiliconFlow、OpenRouter、Groq、Together、Mistral、xAI、NVIDIA NIM、Ollama、vLLM、LM Studio,以及任意 OpenAI-compatible endpoint |
| 隐私 | 默认不返回敏感个人信息;HTTP 服务可选 API Key |
GET /healthz 健康检查
GET /v1/info 版本与能力
GET /v1/providers Provider 发现与配置元数据
POST /v1/extract 上传文档并返回结构化结果
给服务增加 API Key
IMAGE=ghcr.io/inupedia/tender-extract-server:latest
docker run --rm -p 8000:8000 \
-e TENDER_SERVER_API_KEY=your-secret \
"$IMAGE"调用时增加:
X-API-Key: your-secret
在容器中启用 LLM Provider
以 DeepSeek 为例:
IMAGE=ghcr.io/inupedia/tender-extract-server:latest
docker run --rm -p 8000:8000 \
-e DEEPSEEK_API_KEY=your-key \
-e TENDER_SERVER_LLM_PROVIDER=deepseek \
-e TENDER_SERVER_LLM_MODEL=deepseek-chat \
-v tender-extract-cache:/data/cache \
"$IMAGE"SiliconFlow、OpenAI、Gemini、Claude 等 Provider 只需替换 provider/model 与对应环境变量。也可以通过 X-LLM-API-Key 为单次 HTTP 请求提供上游模型密钥。
要求 Python 3.12+。
git clone https://github.com/Inupedia/tender-extract.git
cd tender-extract
uv sync --extra pdf
uv run tender-extract extract examples/example.pdf --out out批量处理目录:
uv run tender-extract extract ./documents --pattern "*.pdf" --out outLLM Provider 不绑定 SiliconFlow。内置 Registry 覆盖主流云模型、本地运行时和任意 OpenAI-compatible endpoint:
uv run tender-extract providers例如 DeepSeek:
export DEEPSEEK_API_KEY=your-key
uv run tender-extract extract examples/example.pdf \
--llm deepseek \
--model deepseek-chat \
--out out任意 OpenAI-compatible 服务:
uv run tender-extract extract examples/example.pdf \
--llm openai_compat \
--base-url http://127.0.0.1:8000/v1 \
--model Qwen/Qwen3-8B \
--out outSiliconFlow Qwen/Qwen3-8B 已完成真实接口验收:冷启动测试 4/4 网络调用成功;同一文档第二次运行 4 次全部命中缓存、0 次新增网络调用。当前这组 live Gold acceptance case 的 Micro F1 / Macro F1 均为 1.000。
这里的 F1 是当前验收样本结果,不代表所有招投标文档上的通用准确率,也不代表其他 Provider 已完成同等 live acceptance。
更完整的 Provider、环境变量、HTTP 和本地模型配置见 docs/llm-providers.md。
真实项目经常不只有一份 PDF:
某项目/
├── 招标文件.pdf
├── 补遗01.pdf
├── 澄清01.pdf
├── A公司投标文件.pdf
└── B公司投标文件.pdf
tender-package 可以把它们作为一个项目处理,并管理版本替代关系与投标人隔离:
uv run tender-package validate package.yaml
uv run tender-package extract package.yaml --out out/package.json补遗中的新值可以覆盖对应旧值,但历史证据仍然保留;A 公司的字段也不会覆盖 B 公司。
uv run tender-review run examples/example.pdf --queue .review/queue.jsonl
uv run tender-review export --queue .review/queue.jsonl --out eval/gold-reviewed.jsonl
uv run tender-extract eval eval/gold-reviewed.jsonlCI 也可以直接设置最低 F1:
uv run tender-extract eval eval/gold.jsonl --fail-under 0.95仓库提交了一套真实采购 / 招投标 PDF 验收集,覆盖安徽、北京、陕西、河南、上海等地区,共 13 份 / 911 页。当前 GitHub Actions 基准在关闭 LLM 时记录为:
| 指标 | 结果 |
|---|---|
| 文档 | 13 份 |
| 页数 | 911 页 |
| 总耗时 | 26.31 秒 |
| 吞吐 | 34.62 页/秒 |
| 运行失败文档 | 0 |
速度来自 GitHub Actions。硬件、OCR、存储和 PDF 版式都会影响实际吞吐。
来源 URL、页数与 SHA256 记录在 examples/public-corpus.lock.json。复现:
uv run python scripts/acceptance_corpus.py \
--examples examples \
--min-pdfs 13 \
--report artifacts/real-pdf-acceptance.jsonMIT