Quick Parse 两阶段解析与流式方案¶
- 版本:v1.0
- 日期:2026-04-11
- 作者:Codex
- 适用项目:
Tender_Documents
1. 背景¶
当前 parse-events 曾尝试直接把全文按 chunk 切开后,让模型逐段输出 JSON,再把原始 token 流直接返回前端。
这种方式有两个明显问题:
- 前端直接拼接多个 chunk 的原始 JSON 文本,天然不是一个稳定、完整的 JSON 结构。
overview / scoring / requirements混在同一条全文流里,准确性和展示体验都较差。
从中国标书的结构特征看,overview 与 scoring 通常具备较强的章节稳定性,适合先做范围锁定,再分别解析。
2. 目标¶
2.1 目标¶
- 将解析链路拆为“范围锁定”与“目标解析”两阶段。
confirm阶段只做轻量操作,不堆重模型调用。requirements放到 worker 中优先落库。parse-events只负责overview + scoring两路并行 AI 流式。- 前端不再拼多个全文 raw JSON 对象,而是按目标区域分别展示。
2.2 非目标¶
- 本期不要求
requirements也做流式展示。 - 本期不要求彻底重写 worker 架构,只需按现有任务链路平滑接入。
- 本期不要求对所有非标准企业标书做到 100% 规则命中,允许 fallback。
3. 总体结构¶
3.1 最终结构¶
confirm¶
输出并缓存:
source_markdownoverview_rangescoring_rangerequirements_range
worker¶
负责:
requirements_range -> requirements- 落库到
TenderRequirement或TenderContent JSONB
parse-events¶
负责:
overview_range -> overviewscoring_range -> scoring- 两路并行 SSE
3.2 一句话结论¶
可以拆成两步,而且这是更好的方向:
confirm:只做全文 Markdown 提取和范围锁定requirements:放 worker 先做并落库parse-events:只并行做overview + scoring的 AI 流式
这样既轻量,又符合“重模型调用不要堆在 API confirm 上”的设计。
4. 为什么这样拆¶
4.1 为什么 confirm 不直接做重 AI¶
confirm 已经能拿到全文 Markdown,但它仍然属于 API 主链路。
如果在这里直接跑多个 AI 任务,会带来:
- API 请求时间被拉长。
- 多用户并发时容易把 API 线程/协程资源拖满。
- 和现有“worker 承担重解析任务”的设计初衷冲突。
因此,confirm 更适合做:
- Markdown 提取
- 章节范围识别
- 结果缓存
4.2 为什么 parse-events 只做 overview + scoring¶
前端最关注的首屏信息通常是:
- 项目名称、预算、工期、资格条件等概况
- 评分项与分值
而 requirements 往往:
- 文本更长
- 更分散
- 对首屏体验不如
overview / scoring重要
所以把 requirements 后置到 worker,更适合当前体验目标。
5. 范围锁定设计¶
5.1 规则优先,模型兜底¶
优先基于全文 Markdown 做本地范围识别:
- 章节标题
- 页码标记(如
-第X页) - 目录关键词
- 表格/评分特征词
只有规则无法锁定时,才考虑模型兜底。
5.2 overview_range¶
优先锁定这些章节:
第一章 投标邀请招标公告项目概况投标人须知投标人须知前附表
目标范围通常在文档前部。
建议输出:
5.3 scoring_range¶
优先锁定这些章节:
评标评标办法评审标准综合评分法详细评审- 含明显“分值/评分项/评审因素/得分”表格的章节
建议输出:
5.4 requirements_range¶
优先锁定这些章节:
招标内容与技术要求采购需求主要商务要求技术标准与要求合同与验收
注意:
requirements可能跨多个章节。- 允许识别为多个范围片段,后续 worker 合并处理。
6. 缓存与数据输出¶
6.1 confirm 阶段建议缓存内容¶
建议缓存到 TenderContent 或专用缓存结构中的字段:
source_markdownoverview_rangescoring_rangerequirements_rangerange_locator_version
推荐形态:
{
"source_markdown": "...",
"range_map": {
"overview": { "page_start": 2, "page_end": 6, "anchors": ["第一章 投标邀请", "投标人须知前附表"] },
"scoring": { "page_start": 28, "page_end": 66, "anchors": ["第五章 评标", "详细评审"] },
"requirements": { "page_start": 16, "page_end": 27, "anchors": ["第三章 招标内容与技术要求"] }
}
}
6.2 为什么要缓存范围而不是每次重算¶
parse-events首连更快。- worker 和 API 可以复用同一份定位结果。
- 便于排查错位问题。
- 同一文件多次重连 SSE 时无需重复定位。
7. Worker 设计¶
7.1 Worker 负责内容¶
worker 读取:
source_markdownrequirements_range
执行:
- 提取
requirements所需文本 - 可按要求范围继续 chunk
- 调用 AI 提取
project_requirements - 落库
7.2 落库建议¶
当前项目已有 TenderRequirement 表,字段设计支持条目化结构:
categorycontentsort_ordermeta_data
因此当前阶段优先建议:
requirements -> TenderRequirement
如果后续发现:
- 读取需求条目的场景很少
- 更偏向“整体展示而非条目级操作”
则可以考虑改为先落 TenderContent 的 JSONB 字段。
8. parse-events 设计¶
8.1 输入¶
parse-events 不再直接吃全文,而是优先吃缓存好的:
overview_range.textscoring_range.text
8.2 执行方式¶
建议并行:
但实际 SSE 输出要通过统一事件队列合流,避免两个协程直接同时 yield 导致时序混乱。
推荐内部结构:
- 两个子任务分别解析
- 子任务把事件推入
asyncio.Queue - SSE 主协程统一从队列取出并
yield
8.3 SSE 字段设计¶
建议前端不再接一个全文 raw JSON 串,而是按目标接收。
推荐事件:
pending¶
section_started¶
section_delta¶
section_result¶
done¶
9. 前端展示建议¶
前端维护两个独立状态区:
overviewStatescoringState
处理方式:
section_delta(target=overview)只更新概况区域section_delta(target=scoring)只更新评分区域section_result用于最终校准
这样前端不再需要:
- 拼多个全文 JSON 对象
- 推断 chunk 边界
- 从混合流中再拆结构
10. fallback 设计¶
范围锁定不是 100% 成功,所以需要兜底。
10.1 overview_range 兜底¶
如果规则未命中:
- 取前
N页 - 或取前部固定字符数
- 再做概况提取
10.2 scoring_range 兜底¶
如果规则未命中:
- 优先搜索“分值/评分/评审/综合评分法”等关键词密集区
- 若仍失败,再退回全文较后部范围
10.3 requirements_range 兜底¶
如果规则未命中:
- 退回第三章附近范围
- 若仍失败,则 worker 走全文 chunk 提取
11. 推荐执行顺序¶
建议按以下顺序实施:
阶段 1:范围锁定基础能力¶
- 新增范围定位函数
- 基于全文 Markdown 输出
overview/scoring/requirements范围 - 将范围缓存到可复用位置
阶段 2:worker 接管 requirements¶
- worker 读取
requirements_range - 提取
requirements - 落库到
TenderRequirement
阶段 3:parse-events 双路流式¶
overview独立提示词与解析函数scoring独立提示词与解析函数- 并行执行并统一 SSE 合流输出
阶段 4:前端联调¶
- 按
target分区展示 - 加入
section_result最终校准 - 不再拼全文 raw JSON 串
12. 当前实现与目标实现的差异¶
当前 parse-events 更接近:
- 全文切 chunk
- 单一路模型流
- 原始 JSON token 直出前端
目标实现应调整为:
confirm产出source_markdown + range_map- worker 处理
requirements parse-events仅处理overview + scoring- 前端按目标区分展示
13. 最终建议¶
这是当前阶段最推荐的结构:
confirm:只做 Markdown 提取与范围锁定worker:先落requirementsparse-events:并行流式overview + scoring- 前端:按
target分区域展示,不再拼全文 raw JSON
这套方案兼顾了:
- API 轻量
- worker 承担重活
- SSE 更适合前端展示
- 中国标书相对稳定格式下的高命中率规则定位