基于 Qwen3.8-Max 构建 AI 合同精审系统:文档解析、多轮条款分析 Pipeline 工程实战

摘要

签合同前找律师审,一份合同报价 2000-5000 元,周期 2-4 小时起步。违约金不对等、管辖权单方约定这种坑,非法律专业的人很难一眼看出来。

我基于 Qwen3.8-Max 大模型 + Python Flask,搭建了一套 AI 合同条款精审助手。核心思路很直接:上传合同文件 → AI 自动提取全部条款 → 逐条六维度风险分析 → 生成评分报告与对比数据。整套系统 7 个核心源文件约 2000 行代码,实测 50 页(20,266 字符)技术开发与服务合同,295.5 秒完成全流程审查,安全评分 83/100,审查 62 条条款,发现 31 个问题。同等合同律师人工审查需 2-4 小时、花费 2000-5000 元,仅能覆盖 25 条条款、发现 9 个问题。

核心能力

  • 多格式文档解析:支持 PDF / Word / TXT / Markdown,自动提取合同全文
  • AI 逐条风险审查:六维度分析(霸王条款 / 模糊表述 / 缺失条款 / 条款矛盾 / 违法风险 / 违约金异常),四级风险标注
  • 安全评分体系:加权算法计算整体安全评分,输出优秀 / 良好 / 一般 / 较差四档评级
  • 可视化仪表盘:Chart.js 饼图 + 柱状图,风险等级分布与各条款评分一目了然
  • AI vs 人工对比:8 维度量化对比表,数据化展示 AI 审查优势
  • PDF 报告导出:一键生成包含封面、摘要、逐条分析、核心建议的完整审查报告

一、环境准备

1.1 Python 环境安装

步骤 1:确认 Python 版本 3.10+

bash 复制代码
python --version
# Python 3.12.x
1.2 Qwen3.8-Max API Key 获取

步骤 1 :访问 阿里云百炼平台 并登录

步骤 2:进入「API-KEY 管理」,创建新的 API Key

步骤 3:复制 API Key 并妥善保管,后续配置到 .env 文件中

1.3 项目依赖安装

步骤 1:进入项目目录,安装全部依赖

bash 复制代码
cd contract-reviewer
pip install -r requirements.txt

步骤 2:依赖清单

bash 复制代码
flask>=3.0.0          # Web 框架
flask-cors>=4.0.0     # 跨域支持
python-dotenv>=1.0.0  # 环境变量管理
openai>=1.12.0        # Qwen API 调用(OpenAI 兼容格式)
pdfplumber>=0.10.3    # PDF 文档解析
python-docx>=1.1.0    # Word 文档解析
fpdf2>=2.7.7          # PDF 报告生成

步骤 3:复制 .env.example 为 .env,填入 API Key

bash 复制代码
cp .env.example .env
bash 复制代码
# .env 配置文件
DASHSCOPE_API_KEY=sk-your-api-key-here
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
MODEL_NAME=qwen3.8-max
PORT=5000

步骤 4:启动服务,访问 http://localhost:5000

bash 复制代码
python app.py

启动时会自动检测 API Key 是否已配置

二、项目搭建

2.1 项目架构总览

项目定位:AI 合同条款精审助手,Flask 后端对接 Qwen3.8-Max API 进行合同风险审查,原生前端提供上传、预览、结果展示、仪表盘、对比报告一站式交互

bash 复制代码
contract-reviewer/
├── app.py              # Flask 主应用(API 路由 + 演示数据 + 对比引擎)
├── analyzer.py         # AI 分析引擎(文档解析 + 条款提取 + 批量风险审查)
├── config.py           # 配置管理(环境变量 + 路径常量)
├── reports.py          # PDF 审查报告生成(封面 + 摘要 + 逐条分析 + 建议)
├── requirements.txt    # Python 依赖清单
│
├── templates/
│   └── index.html      # 前端主页面(上传 / 预览 / 加载 / 结果四区域)
│
├── static/
│   ├── css/style.css   # 完整样式表(CSS 变量主题 + 响应式布局)
│   └── js/app.js       # 前端交互逻辑(上传 / 分析 / 渲染 / 图表)
│
└── samples/
    └── sample_contract.txt  # 示例合同(技术咨询服务合同,含已知风险条款)
2.2 技术选型
层级 技术 说明
AI 模型 Qwen3.8-Max 通过 DashScope OpenAI 兼容 API 调用
后端框架 Flask 轻量 Web 框架,提供 RESTful API 路由
前端 原生 HTML/CSS/JS 零框架依赖,Chart.js 4.4.0 做可视化图表
文档解析 pdfplumber + python-docx 支持 PDF / Word / TXT / Markdown 四种格式
报告生成 fpdf2 生成包含封面、摘要、逐条分析的 PDF 报告

三、核心功能模块

3.1 文档上传与解析

多格式文档解析:支持 PDF / Word / TXT / Markdown,自动提取全文,最大 20MB。

js 复制代码
def parse_document(self, file_path: str) -> dict:
    ext = os.path.splitext(file_path)[1].lower()
    parsers = {
        ".pdf": self._parse_pdf,     # pdfplumber 逐页提取
        ".docx": self._parse_docx,   # python-docx 段落合并
        ".doc":  self._parse_docx,
        ".txt":  self._parse_txt,    # 多编码尝试 utf-8/gbk/gb2312
        ".md":   self._parse_txt,
    }
    parser = parsers.get(ext)
    if not parser:
        raise ValueError(f"不支持的文件格式: {ext}")
    text = parser(file_path)
    if not text.strip():
        raise ValueError("文档内容为空,请检查文件")
    return {"text": text, "char_count": len(text), "filename": os.path.basename(file_path)}

前端拖拽上传:支持拖拽 + 点击选择,上传后展示文件预览(全文展示),提供「返回首页」和「重新上传」两个导航入口。

js 复制代码
uploadArea.addEventListener('drop', (e) => {
    e.preventDefault();
    uploadArea.classList.remove('dragover');
    if (e.dataTransfer.files.length) handleFileUpload(e.dataTransfer.files[0]);
});
3.2 AI 分析引擎(三步 Pipeline)

三步 Pipeline 编排:提取条款 → 批量风险分析 → 整体评估,非单次调用,而是链式处理 + 自动回退。

bash 复制代码
上传文档 → 解析文本 → AI 提取条款 → AI 批量风险分析 → AI 整体评估 → 加权评分 → 渲染结果
                                    ↓ (批量失败时)
                              回退到逐条分析(每条单独调 API)

第一步:AI 提取合同条款

bash 复制代码
prompt = """你是一位专业的中国合同审查律师。请仔细阅读以下合同文本,提取所有独立条款。

要求:
1. 保留原始条款编号
2. 完整保留条款原文内容
3. 每个条款独立编号
4. 包括合同的基本信息条款(如当事方、签署日期等)
5. 包括所有附件和补充条款

请以JSON数组格式返回:
[{"id": "条款编号", "text": "条款完整原文", "title": "条款标题/主题"}]
"""

第二步:批量风险分析(核心 Prompt)

将所有条款一次性丢给 AI,六维度逐条分析风险:

bash 复制代码
prompt = f"""c。请对以下合同的所有条款逐条进行风险分析。

分析维度:
1. 霸王条款:单方面加重对方义务或限制对方权利
2. 模糊表述:可能导致争议的模糊用语
3. 缺失条款:缺少必要的限定条件或例外情形
4. 条款矛盾:与其他条款存在逻辑矛盾
5. 违法风险:可能违反《民法典》《消费者权益保护法》《劳动法》等
6. 违约金异常:违约金比例过高或过低

风险等级标准:
- critical:严重风险,可能导致重大经济损失或法律纠纷
- high:高风险,需要立即修改
- medium:中风险,建议优化
- low:低风险,基本可接受

请以JSON数组格式返回每个条款的分析结果:
[{{
    "clause_index": 0,
    "risk_level": "critical/high/medium/low",
    "risk_types": ["具体风险类型"],
    "analysis": "详细风险分析说明(100-200字)",
    "suggestion": "具体修改建议,给出修改后的条款文本",
    "risk_score": 1到10的整数,
    "legal_basis": "相关法律条文引用"
}}]"""

第三步:生成整体评估

js 复制代码
prompt = f"""你是一位资深的合同审查律师。请基于以下信息对合同进行整体评估。

合同原文摘要(前1000字):
{{text[:1000]}}

高风险条款摘要:
{{risk_summary}}

共审查 {{len(clauses)}} 条条款。

请以JSON格式返回整体评估:
{{
    "summary": "合同整体评估概述(200字以内)",
    "overall_risk": "critical/high/medium/low",
    "strengths": ["合同优点1", "合同优点2"],
    "critical_issues": ["关键问题1", "关键问题2"],
    "key_suggestions": ["核心建议1", "核心建议2", "核心建议3"],
    "contract_type": "合同类型(如劳动合同/买卖合同/租赁合同等)"
}}"""

API 调用封装:显式关闭 thinking 模式加速 JSON 输出,180 秒超时兜底。

js 复制代码
def _call_ai(self, prompt: str, max_tokens: int = 4096) -> str:
    """调用 Qwen3.8-Max API(显式关闭 thinking 以加速 JSON 输出)"""
    try:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": "你是一位拥有15年经验的中国合同审查律师,"
                                              "擅长发现合同中的风险条款、霸王条款和模糊表述。"
                                              "请始终以有效的JSON格式返回分析结果,不要包含任何非JSON内容。"},
                {"role": "user", "content": prompt},
            ],
            temperature=0.1,
            max_tokens=max_tokens,
            extra_body={"enable_thinking": False},  # 关键:关闭 thinking 加速 JSON 输出
        )
        content = response.choices[0].message.content
        if content is None:
            return "{}"
        return content.strip()
    except Exception as e:
        print(f"[AI调用错误] {e}")
        return "{}"
3.3 审查结果展示

安全评分计算:加权算法,critical=10 / high=7 / medium=4 / low=1,结合 AI 给出的具体评分归一化到 0-100。

js 复制代码
def _calculate_score(self, clauses: list) -> int:
    if not clauses:
        return 50
    RISK_WEIGHTS = {"critical": 10, "high": 7, "medium": 4, "low": 1}
    total_penalty = 0
    for clause in clauses:
        level = clause.get("risk_level", "low")
        weight = self.RISK_WEIGHTS.get(level, 1)
        risk_score = clause.get("risk_score", 5)
        total_penalty += weight * (risk_score / 10.0)
    max_penalty = len(clauses) * 10
    return max(0, min(100, round(100 - (total_penalty / max_penalty * 100))))

条款卡片渲染:每条条款展示风险等级标签、风险类型标签、原文引用、风险分析、修改建议、法律依据、风险评分进度条,支持按等级筛选。

js 复制代码
function renderClauses(clauses) {
    const riskLabels = { critical: '严重', high: '高危', medium: '中等', low: '低危' };
    clauses.forEach(clause => {
        const level = clause.risk_level || 'low';
        const types = (clause.risk_types || []).map(t => `<span class="type-tag">${escapeHtml(t)}</span>`).join('');
        const scorePercent = (clause.risk_score || 0) * 10;
        card.innerHTML = `
            <div class="clause-header">
                <span class="clause-title">条款 ${escapeHtml(clause.original_id || '?')}:${escapeHtml(clause.title || '')}</span>
                <span class="risk-badge ${level}">${riskLabels[level] || level}</span>
            </div>
            ${types ? `<div class="clause-types">${types}</div>` : ''}
            <div class="clause-text">${escapeHtml(clause.original_text || '')}</div>
            <div class="clause-section-label">🔍 风险分析</div>
            <div class="clause-analysis">${escapeHtml(clause.analysis || '无分析')}</div>
            <div class="clause-section-label">✏️ 修改建议</div>
            <div class="clause-suggestion">${escapeHtml(clause.suggestion || '无建议')}</div>
            ${clause.legal_basis ? `<div class="clause-legal">📖 ${escapeHtml(clause.legal_basis)}</div>` : ''}
            <div class="risk-score-bar">
                <div class="risk-score-fill" style="width:${scorePercent}%"></div>
            </div>
        `;
    });
}
3.4 风险仪表盘

Chart.js 可视化:环形饼图展示风险等级分布,柱状图展示各条款风险评分,统计卡片汇总关键指标。

代码路径:static/js/app.js → renderDashboard() 第 317-360 行

js 复制代码
// 环形饼图 - 风险等级分布
const pieCtx = document.getElementById('riskPieChart');
pieCtx._chart = new Chart(pieCtx, {
    type: 'doughnut',
    data: {
        labels: ['严重', '高危', '中等', '低危'],
        datasets: [{ data: values, backgroundColor: ['#dc2626', '#ea580c', '#ca8a04', '#16a34a'] }],
    },
});

// 柱状图 - 各条款风险评分
const barCtx = document.getElementById('riskBarChart');
barCtx._chart = new Chart(barCtx, {
    type: 'bar',
    data: {
        labels: clauseLabels,
        datasets: [{ label: '风险评分', data: clauseScores, backgroundColor: clauseColors }],
    },
    options: { scales: { y: { beginAtZero: true, max: 10 } } },
});
3.5 AI vs 人工对比报告

8 维度对比表:安全评分、审查时间、审查成本、条款数、严重风险发现、高风险发现、结果一致性、全量覆盖。

js 复制代码
def _build_comparison_table(ai_analysis: dict, human: dict) -> list:
    ai_risk = ai_analysis.get("risk_counts", {})
    human_risk = human.get("risk_counts", {})
    def get_risk(d, key, cn_key):
        return d.get(key, d.get(cn_key, 0))  # 兼容中英文 key
    return [
        {"dimension": "安全评分", "ai": f"{ai_analysis.get('overall_score', 0)}/100",
         "human": f"{human['score']}/100", "winner": "ai" if ... else "human"},
        {"dimension": "审查时间", "ai": ai_analysis.get("analysis_duration", "?"),
         "human": human["duration"], "winner": "ai"},
        {"dimension": "审查成本", "ai": "≈ ¥0.5",
         "human": human["cost"], "winner": "ai"},
        {"dimension": "严重风险发现", "ai": str(ai_critical),
         "human": str(human_critical), "winner": "ai" if ai_critical >= human_critical else "human"},
        ...
    ]

人工审查基准数据:基于行业平均,评分 78/100、审查 2-4 小时、成本 ¥2,000-5,000、发现 9 个问题。

代码路径:app.py → _get_human_benchmark() 第 193-208 行

js 复制代码
def _get_human_benchmark() -> dict:
    return {
        "score": 78, "total_clauses": 25,
        "risk_counts": {"critical": 2, "high": 3, "medium": 4, "low": 16},
        "duration": "约 2-4 小时", "cost": "¥2,000 - ¥5,000", "issues_found": 9,
        "notes": [
            "人工审查受疲劳影响,可能遗漏低风险条款",
            "不同律师审查标准不一致",
            "审查周期长,影响业务进度",
            "按时/按份计费,成本较高",
        ],
    }
3.6 演示模式

无需 API Key 即可体验:内置 12 条示例条款分析数据,覆盖严重 / 高 / 中 / 低四级风险,包含完整的审查结果、仪表盘和对比报告。

js 复制代码
@app.route("/api/analyze-demo/<task_id>", methods=["POST"])
def analyze_demo(task_id):
    if task_id not in analysis_store:
        analysis_store[task_id] = {
            "filename": "示例合同 - 技术咨询服务合同.txt",
            "text": "",
            "created_at": datetime.now().isoformat(),
        }
    demo_result = _get_demo_analysis()  # 内置 12 条示例分析数据
    analysis_store[task_id]["analysis"] = demo_result
    return jsonify(demo_result)

五、技术选型与踩坑记录

5.1 开发工具链

整个项目用 Qoder 作为 IDE,接入 Qwen3.8-Max 模型做 AI 辅助编程,从项目搭建、代码编写到调试修复,全程在 Qoder 里完成。

工具 用途
Qoder AI 编程 IDE,代码编写 + 调试 + 文件管理
Qwen3.8-Max 双重角色:既做合同分析的 AI 引擎,也做 Qoder 的编程助手
Flask 后端 Web 框架,提供 RESTful API
python-docx / pdfplumber 文档解析(Word / PDF)
Chart.js 4.4.0 前端可视化仪表盘
fpdf2 PDF 审查报告生成

用 Qoder + Qwen3.8-Max 开发的最大感受是:代码生成和上下文理解能力很强,7 个核心文件约 2000 行代码,大部分是 Qoder 里对话生成的。但运行时的问题,AI 不一定能提前想到,得自己跑起来才知道。下面 6 个坑,调了 6 轮才从"能跑"变成"稳定可用"。

5.2 坑 1:Thinking 模式导致 API 卡死 10 分钟

这是最大的坑。API 调用一直超时,简单测试 2.4 秒能返回,但合同分析的大 Prompt 直接卡死。

排查过程:

  1. 先用简单 Prompt 测试 → 2.4 秒返回,正常
  2. 加上 enable_thinking=False → 0.5 秒返回
  3. 才发现:自定义端点默认开启了 thinking 模式,模型会先做一大段"内心独白"再输出结果
  4. 简单 Prompt 的 thinking 很短,但批量分析 36 条条款的 Prompt,thinking 内容巨长,直接超时
js 复制代码
# 解决:显式关闭 thinking
extra_body={"enable_thinking": False}
# 效果:从超时 10 分钟 → 101 秒完成全流程
5.3 坑 2:50 页大合同分析完跳回预览页

用户上传 50 页测试合同,分析一段时间后莫名其妙跳回预览页。

排查过程:

  1. 写测试脚本直接调 analyzer → AI 连通性正常(回复 OK)
  2. 但条款提取返回 0 条,抓取 AI 原始返回发现 JSON 在 7638 字符处被截断
  3. 根因:max_tokens=4096 太小,大合同的条款提取需要输出大量 JSON,4096 token 根本不够
js 复制代码
# 解决:三管齐下
# 1. 默认 max_tokens 4096 → 8192
# 2. 条款提取专用 max_tokens=16384
# 3. 新增 _repair_truncated_json() 截断修复:JSON 数组被截断时,
#    自动找最后一个完整对象并闭合数组,避免全部丢失
5.4 坑 3:openai SDK 版本冲突
plain 复制代码
TypeError: Client.__init__() got an unexpected keyword argument 'proxies'

环境里 openai==1.12.0 和 httpx 版本不兼容,pip install --upgrade openai 升级到 2.53.0 后恢复。

5.5 坑 4:演示模式 404 + 中英文 Key 不一致

两个问题一起爆发:

  • 前端 JS 设置 currentTaskId = 'demo',但 analysis_store 字典里没有 'demo' 这个 key → 404
  • 演示数据 risk_counts 用英文 key(critical/high/medium),summary.risk_counts 用中文 key(严重风险/高风险/中风险),对比表取不到值,统计全是 0
js 复制代码
# 解决 1:analyze_demo 端点自动创建 task 条目
# 解决 2:兼容函数,两种 key 都尝试
def _count_issues(risk_counts: dict) -> int:
    en_total = sum(risk_counts.get(k, 0) for k in ["critical", "high", "medium"])
    cn_total = sum(risk_counts.get(k, 0) for k in ["严重风险", "高风险", "中风险"])
    return max(en_total, cn_total)
5.6 坑 5:XSS 注入 + 事件冒泡

两个前端问题:

  • innerHTML 直接插入 AI 返回内容,有 XSS 风险 → 新增 escapeHtml() 全局转义
  • "选择文件"按钮在拖拽区域内,点击时同时触发 uploadArea 的 click 事件,文件选择对话框弹出两次 → stopPropagation() 修复
js 复制代码
function escapeHtml(str) {
    if (!str) return '';
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
}

selectFileBtn.addEventListener('click', (e) => {
    e.stopPropagation();
    fileInput.click();
});

6 轮调试,从"能跑"到"稳定可用"。Qoder 的代码生成和上下文理解能力确实强,2000 行代码大部分是对话生成的。但运行时的问题(超时、截断、版本冲突、事件冒泡)只有实际跑起来才会暴露,AI 不一定能提前想到。建议开发流程:Qoder 生成代码 → 立即跑起来测试 → 发现问题再让 Qoder 修,比一次性生成大段代码再统一调试效率高得多。

六、效果对比

以下数据来自实测:50 页(20,266 字符)技术开发与服务合同,AI 全流程审查 295.5 秒完成,与同等规模律师人工审查结果对比。

AI 审查结果
指标 数据
安全评分 83/100
审查时间 295.5 秒
审查成本 ≈ ¥0.5
审查条款 62 条
发现问题 31 个
律师人工审查
指标 数据
安全评分 78/100
审查时间 约 2-4 小时
审查成本 ¥2,000 - ¥5,000
审查条款 25 条
发现问题 9 个
8 维度对比
对比维度 AI 审查 律师人工 优势方
安全评分 83/100 78/100 AI 胜出
审查时间 295.5 秒 约 2-4 小时 AI 胜出
审查成本 ≈ ¥0.5 ¥2,000 - ¥5,000 AI 胜出
审查条款数 62 25 AI 胜出
严重风险发现 2 2 AI 胜出
高风险发现 5 3 AI 胜出
结果一致性 100% 因人而异 AI 胜出
全量覆盖 部分抽样 AI 胜出

七、总结

实测 50 页(20,266 字符)技术开发与服务合同,AI 审查 295.5 秒完成,成本约 0.5 元,审查 62 条条款发现 31 个问题;律师人工审查需 2-4 小时、花费 2000-5000 元,仅覆盖 25 条条款发现 9 个问题。AI 在安全评分(83 vs 78)、审查条款数(62 vs 25)、高风险发现(5 vs 3)三个关键维度全面领先,且结果 100% 可复现,不受疲劳和主观判断影响。

整套系统 7 个核心文件约 2000 行代码,实现了从文档解析、条款提取、批量风险审查、安全评分、可视化仪表盘、AI vs 人工对比报告到 PDF 导出的完整链路。对于中小企业日常合同初审场景,AI 不是替代律师,而是让你在找律师之前,就已经知道合同里有哪些坑。

相关推荐
FIT2CLOUD飞致云24 天前
操作教程丨在千问办公中使用Cordys CRM Skills技能,落地AI CRM
ai·开源·crm·销售管理·cordys crm·千问办公
小鹿软件办公1 个月前
阿里发布 Qwen3.8-Max 2.4 万亿参数大模型,16 天独立完成软件项目
qwen3.8-max·千问3.8-max