# Proposa AI — 投标文件生成系统架构设计 > 版本: 1.0 > 日期: 2026-07-07 > 状态: 定稿 --- ## 1. 概述 ### 1.1 目标 构建 `bid_proposal` 包,实现"根据招标文件自动生成投标文件"的核心功能。系统读取招标 PDF,分析其中的评审标准、服务要求、资质要求等,逐项生成对应的投标响应内容,最终输出为格式化 DOCX 投标文件。 ### 1.2 设计原则 - **复用优先**:充分利用现有的 `pdf_table_to_docx` 包的表格提取能力,不重复造轮子。 - **模块隔离**:读取、分析、生成、输出四个阶段严格分离,每个阶段可独立测试和替换。 - **AI驱动**:需求分析和内容生成依赖 OpenAI API,设计上支持模型可配。 - **渐进式处理**:处理流程可分段执行(读取→分析→生成→输出),中间结果可缓存检查。 ### 1.3 与 `pdf_table_to_docx` 的关系 ``` bid_proposal (新包) │ ├── 依赖 pdf_table_to_docx.extractor.PDFTableExtractor 用于表格提取 ├── 依赖 pymupdf (已安装) 用于文本提取 └── 依赖 python-docx (已安装) 用于 DOCX 输出 ``` --- ## 2. 整体架构 ### 2.1 模块划分 ``` bid_proposal/ ├── __init__.py # 包入口 ├── models.py # 【共享】所有数据类定义(A1 编写,A2 引用) ├── config.py # 【共享】配置管理(A1 编写,A2 引用) │ ├── bid_reader.py # 【A1】招标文件读取 ├── requirement_analyzer.py # 【A1】需求分析(OpenAI) │ ├── proposal_generator.py # 【A2】投标内容生成(OpenAI) ├── proposal_writer.py # 【A2】投标文件 DOCX 输出 ├── pipeline.py # 【A2】流程编排 ├── cli.py # 【A2】命令行入口 ├── __main__.py # 【A2】python -m 支持 │ └── tests/ # 测试 ├── test_a1.py └── test_a2.py ``` ### 2.2 数据流 ``` 招标PDF文件 │ ▼ ┌──────────────┐ │ bid_reader │ pymupdf → 文本内容 │ │ pdf_table_to_docx → 表格内容 └──────┬───────┘ │ BidDocument (text + tables + pages) ▼ ┌──────────────┐ │ requirement │ OpenAI API → 需求结构化分析 │ _analyzer │ 识别评审项 / 服务要求 / 资质要求 └──────┬───────┘ │ AnalysisResult (requirements + summary) ▼ ┌──────────────┐ │ proposal │ OpenAI API → 逐项生成投标响应 │ _generator │ 根据需求内容 + 评分标准生成 └──────┬───────┘ │ ProposalDocument (title + sections) ▼ ┌──────────────┐ │ proposal │ python-docx → 格式化DOCX │ _writer │ 封面 / 目录 / 标题 / 正文 / 表格 └──────┬───────┘ │ ▼ 投标文件 DOCX ``` ### 2.3 模块职责 | 模块 | 职责 | 外部依赖 | |------|------|---------| | `models.py` | 所有数据类定义 | — | | `config.py` | 配置读取(API Key、模型名等) | — | | `bid_reader.py` | 读取招标PDF,提取文本+表格 | pymupdf, pdf_table_to_docx | | `requirement_analyzer.py` | 分析招标要求,结构化输出 | OpenAI API | | `proposal_generator.py` | 根据需求生成投标响应内容 | OpenAI API | | `proposal_writer.py` | 将投标内容输出为格式化DOCX | python-docx | | `pipeline.py` | 编排完整流程,处理异常和日志 | 以上所有模块 | | `cli.py` | 命令行入口 | pipeline | --- ## 3. 数据结构设计 (`models.py`) ### 3.1 数据类总览 ```python from dataclasses import dataclass, field from typing import Optional from pdf_table_to_docx.table_parser import TableInfo @dataclass class BidPage: """招标文件中的一页""" page_num: int # 页码(1-indexed) text: str # 页面文本内容 @dataclass class BidDocument: """招标文件结构化内容""" pdf_path: str # PDF 文件路径 file_name: str # 文件名 total_pages: int # 总页数 pages: list[BidPage] # 所有页面文本 full_text: str # 全文拼接文本(供 AI 分析使用) tables: list[TableInfo] # 从 PDF 提取的表格 table_count: int # 表格数量 @dataclass class Requirement: """单个招标要求项""" category: str # 类别(evaluation/service/qualification/other) title: str # 要求标题 description: str # 详细描述 score: Optional[float] = None # 分值(评分项专用) detail: str = "" # 评分标准详细说明 @dataclass class AnalysisResult: """招标文件分析结果""" summary: str # 招标概要 project_name: str = "" # 项目名称 evaluation_criteria: list[Requirement] = field(default_factory=list) # 评审标准 service_requirements: list[Requirement] = field(default_factory=list) # 服务要求 qualification_requirements: list[Requirement] = field(default_factory=list) # 资质要求 other_requirements: list[Requirement] = field(default_factory=list) # 其他要求 raw_response: str = "" # AI 原始响应(调试用) @dataclass class ProposalSection: """投标文件的一个章节""" title: str # 章节标题 level: int = 1 # 层级(1=一级标题, 2=二级标题) content: str = "" # 章节正文 requirement_ref: str = "" # 对应的招标要求引用 @dataclass class ProposalDocument: """完整的投标文件内容""" title: str # 文档标题 project_name: str = "" # 项目名称 sections: list[ProposalSection] = field(default_factory=list) # 所有章节 summary: str = "" # 投标概要 ``` ### 3.2 分类体系 `Requirement.category` 使用以下四类: | 分类值 | 说明 | 示例 | |--------|------|------| | `evaluation` | 评审标准/评分项 | "技术方案(30分)"、"服务团队(20分)" | | `service` | 服务要求/技术需求 | "物业管理服务范围"、"人员配置要求" | | `qualification` | 资质要求/资格条件 | "具有ISO9001认证"、"注册资本≥500万" | | `other` | 其他 | 投标须知、文件格式要求等 | --- ## 4. 模块详细设计 ### 4.1 `bid_reader.py` — 招标文件读取 **职责**: - 使用 **pymupdf (fitz)** 从 PDF 中提取纯文本内容(逐页提取,保留段落结构) - 使用 **pdf_table_to_docx.extractor.PDFTableExtractor** 提取所有表格 - 输出结构化的 `BidDocument` 对象 **关键设计决策**: - 文本提取使用 pymupdf 的 `page.get_text("text")`,对中文 PDF 提取质量较好 - 表格提取完全复用 `PDFTableExtractor`(含跨页表格合并逻辑) - `full_text` 为所有页面文本拼接(供 AI 分析的完整上下文) **接口定义**: ```python def read_bid_pdf(pdf_path: str) -> BidDocument: """读取招标 PDF,提取文本和表格。 Args: pdf_path: 招标文件 PDF 路径 Returns: BidDocument: 结构化招标内容 Raises: FileNotFoundError: PDF 文件不存在 BidReadError: PDF 读取/解析失败(如文件损坏、加密等) """ ``` ### 4.2 `requirement_analyzer.py` — 需求分析 **职责**: - 接收 `BidDocument`,使用 OpenAI API 分析其中的评审要求 - 识别并分类四类需求:评审标准、服务要求、资质要求、其他 - 提取关键信息:需求标题、详细描述、分值(如有) **关键设计决策**: - 使用 OpenAI Chat Completions API,通过 system prompt 指导输出 JSON 格式 - 输出结构化的 `AnalysisResult` - 对长文档可进行章节拆分处理,避免超过 token 限制 - API 调用内置重试(指数退避,最多 3 次) **接口定义**: ```python class RequirementAnalyzer: """招标文件需求分析器。""" def __init__(self, api_key: Optional[str] = None, model: str = "gpt-4o"): """初始化分析器。 Args: api_key: OpenAI API Key,默认从 OPENAI_API_KEY 环境变量读取 model: 使用的模型名称 """ def analyze(self, doc: BidDocument) -> AnalysisResult: """对招标文件进行 AI 分析,提取所有要求。 Args: doc: 招标文件结构化内容 Returns: AnalysisResult: 分析结果(结构化需求列表 + 摘要) Raises: AnalysisError: AI 分析失败(API 错误、响应格式异常等) """ ``` ### 4.3 `proposal_generator.py` — 投标内容生成 **职责**: - 接收 `AnalysisResult`,逐项生成投标响应内容 - 根据需求类型和评分标准,生成有针对性的响应 - 输出结构化的 `ProposalDocument` **关键设计决策**: - 按需求分类组织章节结构(评审响应、服务方案、资质证明等) - 每个需求独立生成,system prompt 中包含需求详情和评分标准 - 生成内容附带章节标题,供后续 writer 组织文档结构 **接口定义**: ```python class ProposalGenerator: """投标内容生成器。""" def __init__(self, api_key: Optional[str] = None, model: str = "gpt-4o"): """初始化生成器。 Args: api_key: OpenAI API Key,默认从 OPENAI_API_KEY 环境变量读取 model: 使用的模型名称 """ def generate(self, analysis: AnalysisResult) -> ProposalDocument: """根据招标要求生成投标内容。 Args: analysis: 招标分析结果 Returns: ProposalDocument: 生成的投标内容 Raises: GenerationError: 内容生成失败(API 错误、格式异常等) """ ``` ### 4.4 `proposal_writer.py` — 投标文件输出 **职责**: - 将 `ProposalDocument` 输出为格式化的 DOCX 文件 - 设置封面、目录、页眉页脚、章节标题、正文 - 样式设计符合投标文件的正式格式 **关键设计决策**: - 直接使用 python-docx(已在环境中安装) - 样式定义集中在模块常量中,便于调整 - 从 `pdf_table_to_docx/docx_writer.py` 借鉴经验,但投标文件有独立样式需求 **接口定义**: ```python def write_proposal_docx( content: ProposalDocument, output_path: str, *, page_width_cm: float = 21.0, page_height_cm: float = 29.7, margin_cm: float = 2.5, ) -> str: """将投标内容写入格式化的 DOCX 文件。 Args: content: 投标内容 output_path: 输出路径 page_width_cm: 页面宽度(默认 A4) page_height_cm: 页面高度(默认 A4) margin_cm: 页边距 Returns: str: 输出文件路径 Raises: WriteError: DOCX 写入失败 """ ``` ### 4.5 `pipeline.py` — 流程编排 **职责**: - 编排读取→分析→生成→输出的完整流程 - 管理配置、日志、异常处理 - 提供高级 API 一键执行 **接口定义**: ```python def run_pipeline( pdf_path: str, output_path: Optional[str] = None, api_key: Optional[str] = None, model: str = "gpt-4o", ) -> str: """运行完整的招标→投标生成流程。 Args: pdf_path: 招标文件 PDF 路径 output_path: 输出 DOCX 路径(默认与输入同目录) api_key: OpenAI API Key(默认从环境变量读取) model: AI 模型名 Returns: str: 输出文件路径 Raises: BidReadError: PDF 读取失败 AnalysisError: 需求分析失败 GenerationError: 内容生成失败 WriteError: DOCX 输出失败 """ ``` ### 4.6 `cli.py` — 命令行入口 **接口定义**: ```bash # 一键生成 python -m bid_proposal 招标文件.pdf # 指定输出路径 python -m bid_proposal 招标文件.pdf -o 投标文件.docx # 指定模型 python -m bid_proposal 招标文件.pdf --model gpt-4o # 详细日志 python -m bid_proposal 招标文件.pdf --verbose ``` --- ## 5. 与现有代码的集成点 ### 5.1 复用的现有能力 | 现有模块 | 复用方式 | 说明 | |---------|---------|------| | `pdf_table_to_docx.extractor.PDFTableExtractor` | 直接实例化 | 复用表格提取和跨页合并 | | `pdf_table_to_docx.table_parser.TableInfo` | 类型引用 | 在 `BidDocument.tables` 中使用 | ### 5.2 不直接复用的模块 | 现有模块 | 原因 | |---------|------| | `pdf_table_to_docx.docx_writer` | 投标文件的 DOCX 样式需求不同(封面、标题层级、页眉等) | | `pdf_table_to_docx.text_scorer` | 投标生成不需要 NLP 评分 | ### 5.3 新增依赖 - **openai**(已在 `pyproject.toml` 中声明)— OpenAI API 调用 - **pymupdf**(已在 `pyproject.toml` 中声明)— PDF 文本提取 - 无需额外安装其他依赖 --- ## 6. 错误处理策略 ### 6.1 异常层级 ``` BidProposalError (base) ├── BidReadError — PDF 读取/解析失败 ├── AnalysisError — AI 分析失败(API 错误、格式异常) ├── GenerationError — 内容生成失败 └── WriteError — DOCX 输出失败 ``` ### 6.2 重试策略 - **OpenAI API 调用**:指数退避重试(最多 3 次),超时 120 秒 - **PDF 读取**:一次性失败(文件不存在或损坏直接报错) - **长文档分析**:按章节拆分,单个章节分析失败不影响其他章节 ### 6.3 降级策略 | 场景 | 处理方式 | |------|---------| | 表格提取失败 | 仅使用文本内容继续分析,日志记录警告 | | API 部分失败 | 标记失败项为"【待人工补充】",继续后续步骤 | | 输出目录不可写 | 回退到系统临时目录 | --- ## 7. 环境与配置 ### 7.1 环境变量 | 变量 | 用途 | 默认值 | |------|------|--------| | `OPENAI_API_KEY` | OpenAI API 密钥 | —(必需) | ### 7.2 `config.py` 设计 ```python import os class Config: """配置管理。""" @staticmethod def get_openai_api_key() -> str: """获取 OpenAI API Key。""" api_key = os.environ.get("OPENAI_API_KEY") if not api_key: raise ValueError( "未设置 OPENAI_API_KEY 环境变量" ) return api_key @staticmethod def get_default_model() -> str: """获取默认模型名。""" return os.environ.get("OPENAI_MODEL", "gpt-4o") ``` --- ## 8. 工作拆分 ### 8.1 模块依赖关系 ``` A1 工作包(先决) A2 工作包(依赖 A1 的接口定义) ┌──────────────────────┐ ┌──────────────────────────┐ │ models.py (共享) │ │ proposal_generator.py │ │ config.py (共享) │◀─────────│ proposal_writer.py │ │ bid_reader.py │ │ pipeline.py │ │ requirement_ │ │ cli.py │ │ analyzer.py │ │ __main__.py │ └──────────────────────┘ └──────────────────────────┘ ``` ### 8.2 A1 工作包(`bid_reader.py` + `requirement_analyzer.py`) **实现文件**: - `bid_proposal/__init__.py` - `bid_proposal/models.py` — 所有数据类定义 - `bid_proposal/config.py` — 配置管理 - `bid_proposal/bid_reader.py` — 招标文件读取 - `bid_proposal/requirement_analyzer.py` — 需求分析 **接口契约**(A1 对 A2 提供的接口): ```python # models.py @dataclass class BidDocument: ... @dataclass class Requirement: ... @dataclass class AnalysisResult: ... @dataclass class ProposalSection: ... @dataclass class ProposalDocument: ... # bid_reader.py def read_bid_pdf(pdf_path: str) -> BidDocument: ... # requirement_analyzer.py class RequirementAnalyzer: def analyze(self, doc: BidDocument) -> AnalysisResult: ... ``` **验收标准**: 1. 能从测试 PDF 中正确提取文本和表格 2. 能够分析招标文件,输出结构化的需求列表 3. 支持评审标准、服务要求、资质要求三类分类 4. 单元测试覆盖正常路径和异常路径 **详细任务**见 `task_a1.md`。 ### 8.3 A2 工作包(`proposal_generator.py` + `proposal_writer.py` + `pipeline.py`) **实现文件**: - `bid_proposal/proposal_generator.py` — 投标内容生成 - `bid_proposal/proposal_writer.py` — 投标文件输出 - `bid_proposal/pipeline.py` — 流程编排 - `bid_proposal/cli.py` — 命令行入口 - `bid_proposal/__main__.py` — `python -m` 支持 **接口契约**(A2 对 A1 的依赖): - 引用 `models.py` 中定义的数据类 - 调用 `bid_reader.read_bid_pdf()` 和 `RequirementAnalyzer.analyze()` **验收标准**: 1. 能基于分析结果生成完整的投标响应内容 2. 能输出格式化的 DOCX 文件(含封面、目录、章节、正文) 3. 端到端流程可运行:PDF → DOCX 4. 单元测试 + 集成测试覆盖 **详细任务**见 `task_a2.md`。 --- ## 9. 后续扩展方向 | 方向 | 说明 | 优先级 | |------|------|--------| | 多轮对话优化 | 用户可对生成内容提出修改意见,AI 根据反馈调整 | 中 | | 模板匹配 | 支持用户提供历史投标文件作为风格模板 | 低 | | 评分预测 | 根据生成内容预测评审得分,指导内容优化 | 低 | | 参考文档库 | 支持参考多个招标文件和投标文件进行综合生成 | 低 | | 并行生成 | 章节级并行调用 OpenAI API,加速生成 | 低 |