|
|
před 3 týdny | |
|---|---|---|
| models | před 2 měsíci | |
| scripts | před 3 týdny | |
| src | před 3 týdny | |
| .env.example | před 3 týdny | |
| .gitattributes | před 3 týdny | |
| .gitignore | před 3 týdny | |
| .python-version | před 3 týdny | |
| CHANGELOG.md | před 3 týdny | |
| README.md | před 3 týdny | |
| deploy.sh | před 3 týdny | |
| pyproject.toml | před 3 týdny | |
| uv.lock | před 3 týdny |
AI 驱动的投标书生成服务:解析招标文件、采购需求、澄清公告、公司资料和历史参考投书,
经 Step1-6 工作流生成最终 DOCX 投标书。HTTP API 是主要对接入口,后台直接执行与人工
验证相同的 scripts/test_step1.py 至 scripts/test_step6.py。
proposa-ai/
├── src/ # 正式功能代码
│ ├── config.py # .env 与全局配置
│ ├── models.py # 数据模型
│ ├── chapter_policy.py # 章节结构策略
│ ├── doc_reader/ # PDF/DOCX/Markdown 读取
│ ├── llm_client/ # LLM 调用与缓存
│ ├── pdf_table_to_docx/ # PDF 表格提取
│ ├── step1_parsing/ ... step6_exporting/
│ └── templates/ # 正式 DOCX 模板资源
├── scripts/ # 阶段脚本与快速回归
│ ├── test_step1.py ... test_step6.py
│ └── tests/
├── data/ # 项目输入数据
├── test_data/ # 手工测试数据
├── output/ # 生成产物与缓存
├── models/ # 可选本地 NLP 模型资源
├── deploy.sh # Linux 部署脚本
├── .env # 本地配置,Git 忽略
├── .env.example # 环境变量示例
└── pyproject.toml # uv 依赖与安装配置
uv sync
项目已在 pyproject.toml 的 [tool.uv] 中配置清华 PyPI 镜像
(https://pypi.tuna.tsinghua.edu.cn/simple)加速依赖下载;需要更换镜像时,
设置环境变量 UV_INDEX_URL 覆盖即可。
复制 .env.example 为 .env:
Copy-Item .env.example .env
至少填写:
DEEPSEEK_API_KEY=你的密钥
操作系统环境变量的优先级高于 .env。
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
DEEPSEEK_API_KEY |
是 | 无 | DeepSeek API 密钥。仅保存在本地 .env,不得提交到 Git。 |
DEEPSEEK_BASE_URL |
否 | https://api.deepseek.com |
DeepSeek 兼容 API 的基础地址。 |
DEEPSEEK_MODEL |
否 | deepseek-v4-flash |
分析、生成和审核使用的模型名称。 |
BID_OUTPUT_DIR |
否 | output |
工作流默认输出目录;相对路径以仓库根目录为基准。 |
BID_WRITER_CONCURRENCY |
否 | 5 |
Step4 并发撰写章节的最大线程数。 |
BID_ANALYZER_CONCURRENCY |
否 | 5 |
Step2 并发分析文档分块的最大线程数。 |
BID_HEADING_CONCURRENCY |
否 | 5 |
Step6 标题层级分析的最大 LLM 并发数。 |
PROPOSA_API_HOST |
否 | 0.0.0.0 |
HTTP API 监听地址;默认监听所有网卡。 |
PROPOSA_API_PORT |
否 | 8000 |
HTTP API 监听端口。 |
PROPOSA_WORK_DIR |
否 | output/api_jobs |
HTTP API 和 test_step1-6 默认工作目录;每个请求的内容放在 PROPOSA_WORK_DIR/<request_id>/ 下,中间文件在 work/,最终 DOCX 在请求目录根。 |
REFERENCE_DIR |
否 | /data/shenqin/ |
JSON 模式下参考投标文件相对路径的全局目录。 |
TPC_DIR |
否 | 无 | JSON 模式下招标文件、采购需求和澄清公告相对路径的拼接前缀。 |
PROPOSA_API_MAX_UPLOAD_MB |
否 | 512 |
单个上传文件的大小上限,单位 MB。 |
PROPOSA_API_TIMEOUT_SECONDS |
否 | 28800 |
Step1-6 后台子进程超时秒数,默认 8 小时。 |
PROPOSA_API_CONCURRENCY |
否 | 1 |
同时执行的后台生成任务数;大型 DOCX 场景建议保持 1。 |
PROPOSA_CALLBACK_TIMEOUT_SECONDS |
否 | 30 |
单次回调 HTTP 请求的超时秒数。 |
PROPOSA_CALLBACK_MAX_RETRIES |
否 | 3 |
回调失败时的最大尝试次数,包含首次请求。 |
仓库提供一键部署脚本 deploy.sh,按顺序完成:
uv 时自动安装(默认开启,AUTO_INSTALL_UV=0 可关闭);gpt2-chinese-cluecorpussmall-onnx 下载到 models/
目录(模型文件已存在时跳过);uv sync --frozen 安装锁定依赖;proposa-api,写入 PID/日志文件并做 /health 健康检查。cp .env.example .env
编辑 .env 填写真实配置,至少设置 DEEPSEEK_API_KEY;PROPOSA_API_PORT、
PROPOSA_WORK_DIR、REFERENCE_DIR 等可按需调整。
sh deploy.sh # 完整部署并后台启动(可重复执行)
sh deploy.sh status # 查看运行状态与健康检查
sh deploy.sh stop # 停止后台服务
sh deploy.sh restart # 停止后重新后台启动
日志位于 output/api.log,PID 位于 output/api.pid。
可用环境变量:MODEL_REPO(默认
Maiteka/gpt2-chinese-cluecorpussmall-onnx)、MODEL_DIR(默认
models/gpt2-chinese-cluecorpussmall-onnx)、UV_CACHE_DIR(默认
<仓库根>/.uv-cache)、UV_INDEX_URL(默认清华 PyPI 镜像
https://pypi.tuna.tsinghua.edu.cn/simple)、PROPOSA_API_PORT(默认读取
.env,缺省 8000)、
RESTART=1(服务已运行时强制重启)、AUTO_INSTALL_UV=0(uv 缺失时只提示、
不自动安装)。
uv run proposa-api
| 接口 | 方法 | 说明 |
|---|---|---|
/health |
GET | 健康检查。 |
/api/v1/final-review |
POST | 提交生成任务,返回 HTTP 202。 |
/api/v1/jobs/{request_id} |
GET | 查询任务状态。 |
/api/v1/jobs/{request_id}/progress |
GET | 查询 Step1-6 进度。 |
/api/v1/jobs/{request_id}/file |
GET | 下载最终 DOCX。 |
接口同时支持 multipart/form-data 文件上传和 application/json 服务端本地路径。
curl.exe -X POST "http://127.0.0.1:8000/api/v1/final-review" `
-F "CALLBACK_URL=http://121.43.55.7:10026/shenqin/tender/callback" `
-F "txbId=投续表的DMS-ID" `
-F "TENDER_FILE=@招标文件.pdf" `
-F "PROCUREMENT_FILE=@采购需求.docx" `
-F "REFERENCE_BID=@参考投标文件.docx" `
-F "CLA_FILE=@澄清公告.docx" `
-F "CLEAN_INTERMEDIATE=false" `
-F "REFERENCE_IS_ABSOLUTE=false"
curl.exe -X POST "http://127.0.0.1:8000/api/v1/final-review" `
-H "Content-Type: application/json" `
--data '{
"CALLBACK_URL": "http://121.43.55.7:10026/shenqin/tender/callback",
"txbId": "投续表的DMS-ID",
"TENDER_FILE": "E:/CODE/proposa-ai/test_data/171-上海群众艺术馆/上海市群众艺术馆物业管理服务采购项目招标文件.pdf",
"PROCUREMENT_FILE": "E:/CODE/proposa-ai/test_data/171-上海群众艺术馆/采购需求.docx",
"TPC_IS_ABSOLUTE": true,
"REFERENCE_BID": "E:/CODE/proposa-ai/test_data/171-上海群众艺术馆/参考投标文件/物业管理费项目投标文件.docx",
"REFERENCE_IS_ABSOLUTE": true,
"CLA_FILE": "",
"CLEAN_INTERMEDIATE": false
}'
| 参数 | 必填 | 说明 |
|---|---|---|
CALLBACK_URL |
是 | 处理成功或失败后接收 JSON 的回调地址。 |
txbId |
是 | 投续表的 DMS ID,回调时原样返回。 |
TENDER_FILE |
是 | 招标文件;multipart 上传 PDF,JSON 传服务端本地路径。 |
PROCUREMENT_FILE |
是 | 采购需求;multipart 上传 DOCX,JSON 传服务端本地路径。 |
REFERENCE_BID |
是 | 参考投标文件;multipart 上传 DOCX,JSON 传服务端本地路径。 |
CLA_FILE |
否 | 澄清公告文件;当前仅接收并记录,不参与生成。 |
TPC_IS_ABSOLUTE |
JSON 是 | 仅 JSON 路径模式生效,必须传布尔值;false 时 TENDER_FILE、PROCUREMENT_FILE、非空 CLA_FILE 与 TPC_DIR 拼接,true 时原样读取。接口不会根据路径文本自行判断。 |
CLEAN_INTERMEDIATE |
否 | 默认 true;设为 false 时保留中间文件并返回 intermediateDir。 |
REFERENCE_IS_ABSOLUTE |
JSON 是 | 仅 JSON 路径模式生效,必须传布尔值;false 时 REFERENCE_BID 与 REFERENCE_DIR 拼接,true 时原样读取。接口不会根据路径文本自行判断。 |
JSON 模式下:
REFERENCE_BID 为绝对路径时直接使用。REFERENCE_BID 为相对路径时,设置 "REFERENCE_IS_ABSOLUTE": false,接口按
REFERENCE_DIR + REFERENCE_BID 拼接为绝对路径,再复制到隔离任务目录。REFERENCE_IS_ABSOLUTE 的布尔值处理,不根据路径是否以 / 开头自行判断;
相对模式下会先去掉文件值开头的路径分隔符再与配置目录拼接。multipart 模式下 REFERENCE_BID 是上传文件本身,REFERENCE_IS_ABSOLUTE 仅兼容接收,
不参与路径拼接。
TENDER_FILE 必填且不能为空,只接受能正常打开并提取到文字的 PDF;纯扫描或损坏的
PDF 会返回 HTTP 400。PROCUREMENT_FILE 和 REFERENCE_BID 只接受 DOCX;服务验证 ZIP 容器、必需 OOXML
部件和正文 XML。CLA_FILE 可选;当前仅保存并在回调中记录原始文件信息,不参与生成或校验。提交成功后,后台按顺序执行 test_step1.py 至 test_step6.py。
每个请求的内容都放在:
PROPOSA_WORK_DIR/<request_id>/
├── work/ # Step1-6 中间文件与缓存
└── <招标文件名>.docx # 最终 DOCX
最终文件名规则:招标 PDF 原文件名去掉 .pdf 后追加 .docx。
CLEAN_INTERMEDIATE=true(默认)时,处理结束后只清理 work/,最终 DOCX 保留;
false 时 work/ 也保留。
{
"request_id": "501c42526b5b4bb6ad68f0d736ef5884",
"txbId": "1",
"status": "processing",
"message": "三份文件校验通过,已开始处理",
"resultPath": "E:/CODE/proposa-ai/api_output_v1/501c42526b5b4bb6ad68f0d736ef5884/招标文件.docx",
"output_path": "E:/CODE/proposa-ai/api_output_v1/501c42526b5b4bb6ad68f0d736ef5884/招标文件.docx",
"progress_url": "http://127.0.0.1:8000/api/v1/jobs/501c42526b5b4bb6ad68f0d736ef5884/progress"
}
| 字段 | 说明 |
|---|---|
request_id |
任务唯一 ID,用于查询进度、状态和下载最终文件。 |
txbId |
请求传入的投续表 DMS ID,原样回传。 |
status |
当前任务状态;202 响应中为 processing。 |
message |
处理提示信息。 |
resultPath |
最终 DOCX 的绝对路径,位于 PROPOSA_WORK_DIR/<request_id>/ 下。 |
output_path |
与 resultPath 相同的兼容字段。 |
progress_url |
轮询步骤进度的 URL。 |
intermediate_dir |
可选;仅当 CLEAN_INTERMEDIATE=false 时返回,指向 work/ 目录。 |
| 状态值 | 含义 |
|---|---|
queued |
任务已创建,等待后台 worker 开始处理。 |
processing |
正在执行 Step1-6,可通过 progress_url 查看当前步骤。 |
completed |
处理成功,resultPath 指向最终 DOCX,可下载。 |
failed |
处理失败,resultPath 为 error: 具体错误信息。 |
curl.exe "http://127.0.0.1:8000/api/v1/jobs/任务ID/progress"
{
"updated_at": "2026-08-26T14:29:00+00:00",
"current_step": 3,
"total_steps": 6,
"current_step_name": "Step 3 投书大纲生成",
"status": "running",
"message": "Step 3 投书大纲生成 正在处理",
"completed_steps": [1, 2]
}
进度接口中的 status:
| 状态值 | 含义 |
|---|---|
running |
当前步骤正在执行。 |
completed |
当前步骤已完成;当 current_step 为 6 且状态为 completed 时,表示全部步骤完成。 |
curl.exe "http://127.0.0.1:8000/api/v1/jobs/任务ID"
curl.exe "http://127.0.0.1:8000/api/v1/jobs/任务ID/file" --output 投标文件.docx
GET /api/v1/jobs/{request_id}/file 仅在任务 completed 且文件存在时返回 DOCX。
回调只在任务成功或失败后发送,不在提交时发送。
成功回调:
{
"request_id": "任务ID",
"txbId": "投续表的DMS-ID",
"status": "completed",
"resultPath": "output/api_jobs/<request_id>/招标文件.docx",
"files": {
"TENDER_FILE": {"filename": "招标文件.pdf", "size": 123, "sha256": "..."},
"PROCUREMENT_FILE": {"filename": "采购需求.docx", "size": 456, "sha256": "..."},
"REFERENCE_BID": {"filename": "参考投书.docx", "size": 789, "sha256": "..."}
},
"final_review_url": "http://API地址/api/v1/jobs/任务ID/file"
}
失败回调:
{
"request_id": "任务ID",
"txbId": "投续表的DMS-ID",
"status": "failed",
"resultPath": "error: Step1-6 处理进程异常退出(退出码 7)",
"error": "Step1-6 处理进程异常退出(退出码 7)"
}
回调说明:
callback_delivered=false 和
callback_error。CLA_FILE 时,files.CLA_FILE 会包含 filename/size/sha256。CLEAN_INTERMEDIATE=false 时,成功和失败回调都会额外返回 intermediateDir。| 状态码 | 含义 |
|---|---|
| 202 | 校验通过,任务已异步开始。 |
| 400 | 文件、路径、参数或回调地址校验失败。 |
| 413 | 上传文件超过大小限制。 |
| 415 | 不支持的 Content-Type。 |
| 422 | 缺少必填字段。 |
| 500 | 后台任务提交失败。 |
# 快速回归
uv run python -m unittest discover -s scripts/tests -v
# 按阶段手工测试(会使用 test_data 并可能调用 LLM)
uv run python scripts/test_step3.py
uv run python scripts/test_step6.py
SC-xx 等内部编号。第{中文序号}章 和 Heading 1,并映射到最终大纲。%%表名%% 占位符允许创建或整表替换;Markdown 表格不能绕过授权生成 DOCX 表格。