ARCHITECTURE.md 18 KB

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 数据类总览

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 分析的完整上下文)

接口定义

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 次)

接口定义

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 组织文档结构

接口定义

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 借鉴经验,但投标文件有独立样式需求

接口定义

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 一键执行

接口定义

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 — 命令行入口

接口定义

# 一键生成
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 设计

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 提供的接口):

# 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__.pypython -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,加速生成