AI智能合同比对系统:从零搭建实战教程

一、项目背景与需求分析

在企业法务工作中,合同审核是一项高频且耗时的工作。传统的人工比对方式存在效率低、易遗漏等问题。本文将带领大家使用 Python + Streamlit + 智谱AI,搭建一个智能合同比对系统,实现合同模板与业务合同的自动化比对。

核心功能

  • ✅ 支持 DOCX、TXT、PDF 格式合同文件

  • ✅ 调用 AI 大模型智能识别条款差异

  • ✅ 可视化展示比对结果(统计概览、高风险提醒、详细差异)

  • ✅ 无需翻墙,使用国产免费 AI 模型

技术栈

二、环境准备

2.1 安装 Python

  1. 访问 https://www.python.org/downloads/release/python-3119/

  2. 下载 python-3.11.9-amd64.exe

  3. 安装时务必勾选 "Add Python to PATH"

  4. 验证安装:cmd

    python --version

显示 Python 3.11.9 即为成功

2.2 创建项目目录

在桌面创建项目文件夹

复制代码
mkdir C:\Users\你的用户名\Desktop\contract_compare
cd C:\Users\你的用户名\Desktop\contract_compare

2.3 安装依赖包

复制代码
pip install streamlit openai python-docx pdfplumber -i https://pypi.tuna.tsinghua.edu.cn/simple

验证安装:

复制代码
python -c "import streamlit; print('OK')"

三、获取智谱AI API Key(免费)

智谱 AI 提供永久免费的 GLM-4-Flash 模型,非常适合个人项目使用。

3.1 注册账号

  1. 访问 https://open.bigmodel.cn

  2. 手机号注册或微信扫码登录

  3. 完成实名认证(身份证+人脸识别,约2分钟)

3.2 创建 API Key

  1. 进入控制台 → API Keys

  2. 点击「新建 API Key」

  3. 命名如 contract-compare

  4. 立即复制生成的app-key 字符串并保存

⚠️ 注意:API Key 只在创建时显示一次,关闭后就看不到了

四、编写核心代码

4.1 项目文件结构

复制代码
contract_compare/
├── run_zhipu.py          # 主程序(唯一需要运行的文件)
├── 合同模板.txt           # 测试用模板合同
└── 业务合同.txt           # 测试用业务合同

4.2 完整代码实现

创建 run_zhipu.py,复制以下代码

python 复制代码
import streamlit as st
import tempfile
import os
import json
import re
from openai import OpenAI

# ===== 文件解析库 =====
try:
    import docx
    HAS_DOCX = True
except:
    HAS_DOCX = False

try:
    import pdfplumber
    HAS_PDF = True
except:
    HAS_PDF = False

# ===== 页面配置 =====
st.set_page_config(page_title="AI合同比对系统", page_icon="📄", layout="wide")

# ===== 自定义CSS样式 =====
st.markdown("""
<style>
.main-title { 
    font-size: 36px; font-weight: bold; color: #1a237e; text-align: center; padding: 20px; 
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; 
    margin-bottom: 30px; 
}
.metric-card { 
    background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); 
    text-align: center; transition: transform 0.3s;
}
.metric-card:hover { transform: translateY(-5px); }
.risk-high { 
    background: linear-gradient(135deg, #ff6b6b, #ee5a24); color: white; padding: 15px; 
    border-radius: 10px; margin: 10px 0; 
}
.tag-consistent { background: #c8e6c9; color: #2e7d32; padding: 4px 12px; border-radius: 20px; 
                  font-size: 12px; font-weight: bold; }
.tag-modified { background: #fff3e0; color: #ef6c00; padding: 4px 12px; border-radius: 20px; 
                font-size: 12px; font-weight: bold; }
.tag-missing { background: #ffcdd2; color: #c62828; padding: 4px 12px; border-radius: 20px; 
               font-size: 12px; font-weight: bold; }
.tag-added { background: #bbdefb; color: #1565c0; padding: 4px 12px; border-radius: 20px; 
             font-size: 12px; font-weight: bold; }
.diff-box { background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0; 
            border-left: 4px solid #1976d2; }
.diff-template { background: #e3f2fd; padding: 10px; border-radius: 5px; margin: 5px 0; }
.diff-business { background: #fff3e0; padding: 10px; border-radius: 5px; margin: 5px 0; }
</style>
""", unsafe_allow_html=True)

# ===== 页面标题 =====
st.markdown('<div class="main-title">📄 AI智能合同比对系统</div>', unsafe_allow_html=True)

# ===== 侧边栏 =====
with st.sidebar:
    st.markdown("### ⚙️ 配置")
    
    api_key = st.text_input("智谱 API Key", type="password", placeholder="sk-...",
                           help="在 https://open.bigmodel.cn 获取")
    
    model_options = ["glm-4.7-flash", "glm-4-flash"]
    model_name = st.selectbox("选择模型", model_options, index=0,
                             help="glm-4.7-flash 最新免费版")
    
    st.markdown("---")
    st.markdown("### 📤 上传文件")
    
    template_file = st.file_uploader("📋 合同模板", type=['docx', 'txt', 'pdf'])
    business_file = st.file_uploader("📑 业务合同", type=['docx', 'txt', 'pdf'])
    
    compare_btn = st.button("🚀 开始AI比对", type="primary", use_container_width=True,
                           disabled=not (template_file and business_file))
    
    st.markdown("---")
    st.markdown("### 💡 使用说明")
    st.info("""
    1. 获取智谱 API Key(免费)
    2. 上传合同模板和业务合同
    3. 点击"开始AI比对"
    支持格式:.docx、.txt、.pdf
    不填 API Key 将显示示例数据
    """)

# ===== 辅助函数 =====
def read_file(file_path):
    """读取文件内容"""
    ext = os.path.splitext(file_path)[1].lower()
    
    if ext == '.docx':
        if not HAS_DOCX:
            st.error("请安装 python-docx:pip install python-docx")
            return ""
        doc = docx.Document(file_path)
        return '\n'.join([p.text for p in doc.paragraphs if p.text.strip()])
    
    elif ext == '.txt':
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()
    
    elif ext == '.pdf':
        if not HAS_PDF:
            st.error("请安装 pdfplumber:pip install pdfplumber")
            return ""
        with pdfplumber.open(file_path) as pdf:
            texts = [page.extract_text() for page in pdf.pages if page.extract_text()]
            return '\n'.join(texts)
    
    return ""

def get_tag_html(diff_type):
    """获取差异类型的HTML标签"""
    tags = {
        "完全一致": '<span class="tag-consistent">✓ 完全一致</span>',
        "内容修改": '<span class="tag-modified">✎ 内容修改</span>',
        "模板有但业务缺失": '<span class="tag-missing">✗ 业务缺失</span>',
        "业务新增": '<span class="tag-added">+ 业务新增</span>'
    }
    return tags.get(diff_type, diff_type)

def get_mock_result():
    """返回模拟比对结果"""
    return {
        "total_clauses": 6,
        "consistent_count": 2,
        "modified_count": 2,
        "missing_count": 1,
        "added_count": 1,
        "high_risk_items": [
            "⚠️ 合同金额从100万增加到120万,增幅20%",
            "⚠️ 付款期限从30天延长到60天",
            "⚠️ 违约责任条款在业务合同中缺失"
        ],
        "details": [
            {
                "clause_name": "合同主体",
                "template_content": "乙方:上海贸易有限公司",
                "business_content": "乙方:深圳创新有限公司",
                "difference_type": "内容修改",
                "description": "乙方公司名称变更"
            },
            {
                "clause_name": "合同金额",
                "template_content": "¥1,000,000",
                "business_content": "¥1,200,000",
                "difference_type": "内容修改",
                "description": "金额从100万增加至120万"
            },
            {
                "clause_name": "付款方式",
                "template_content": "30日内支付",
                "business_content": "60日内支付",
                "difference_type": "内容修改",
                "description": "付款期限从30天延长至60天"
            },
            {
                "clause_name": "履行期限",
                "template_content": "2024年1月至12月",
                "business_content": "2024年1月至12月",
                "difference_type": "完全一致",
                "description": "履行期限一致"
            },
            {
                "clause_name": "违约责任",
                "template_content": "违约金20%",
                "business_content": "",
                "difference_type": "模板有但业务缺失",
                "description": "业务合同缺失违约责任条款"
            },
            {
                "clause_name": "知识产权",
                "template_content": "",
                "business_content": "知识产权归乙方",
                "difference_type": "业务新增",
                "description": "业务合同新增知识产权条款"
            }
        ]
    }

def call_zhipu(template_text, business_text, api_key, model="glm-4.7-flash"):
    """调用智谱 API 进行合同比对"""
    client = OpenAI(api_key=api_key, base_url="https://open.bigmodel.cn/api/paas/v4")
    
    prompt = f"""你是一个专业的合同审查专家。请仔细比对以下两份合同,找出所有差异。

合同模板:
{template_text[:6000]}

业务合同:
{business_text[:6000]}

请严格按照以下JSON格式输出比对结果:
{{
    "total_clauses": 总数,
    "consistent_count": 一致数,
    "modified_count": 修改数,
    "missing_count": 缺失数,
    "added_count": 新增数,
    "high_risk_items": ["高风险项"],
    "details": [
        {{
            "clause_name": "条款名",
            "template_content": "模板原文",
            "business_content": "业务原文",
            "difference_type": "完全一致/内容修改/模板有但业务缺失/业务新增",
            "description": "差异描述"
        }}
    ]
}}"""
    
    try:
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.1,
            max_tokens=4096
        )
        
        content = response.choices[0].message.content
        json_match = re.search(r'\{.*\}', content, re.DOTALL)
        if json_match:
            return json.loads(json_match.group())
        return json.loads(content)
    except Exception as e:
        st.error(f"API调用失败:{str(e)}")
        return None

# ===== 主逻辑 =====
if compare_btn:
    with st.spinner("🤖 AI正在智能比对合同,请稍候..."):
        # 保存上传的文件
        with tempfile.NamedTemporaryFile(delete=False, 
                suffix=f".{template_file.name.split('.')[-1]}") as t:
            t.write(template_file.getvalue())
            template_path = t.name
        
        with tempfile.NamedTemporaryFile(delete=False, 
                suffix=f".{business_file.name.split('.')[-1]}") as b:
            b.write(business_file.getvalue())
            business_path = b.name
        
        try:
            template_text = read_file(template_path)
            business_text = read_file(business_path)
            
            if api_key:
                result = call_zhipu(template_text, business_text, api_key, model_name)
                if result is None:
                    result = get_mock_result()
            else:
                result = get_mock_result()
                st.info("💡 未填写API Key,显示示例数据")
            
            # 显示统计概览
            st.markdown("### 📊 比对概览")
            cols = st.columns(5)
            metrics = [
                ("总条款数", result['total_clauses'], "#1976d2"),
                ("完全一致", result['consistent_count'], "#2e7d32"),
                ("内容修改", result['modified_count'], "#ef6c00"),
                ("业务缺失", result['missing_count'], "#c62828"),
                ("业务新增", result['added_count'], "#1565c0")
            ]
            for col, (label, value, color) in zip(cols, metrics):
                with col:
                    st.markdown(f"""
                    <div class="metric-card">
                        <div style="font-size:36px;font-weight:bold;color:{color};">{value}</div>
                        <div style="color:#666;">{label}</div>
                    </div>
                    """, unsafe_allow_html=True)
            
            # 显示高风险项
            if result.get('high_risk_items'):
                st.markdown("### 🚨 高风险项提醒")
                for item in result['high_risk_items']:
                    st.markdown(f'<div class="risk-high">{item}</div>', unsafe_allow_html=True)
            
            # 显示详细比对结果
            st.markdown("### 📝 详细比对结果")
            for i, detail in enumerate(result['details']):
                expanded = detail['difference_type'] != "完全一致"
                with st.expander(f"{detail['clause_name']} {get_tag_html(detail['difference_type'])}", 
                               expanded=expanded):
                    col1, col2 = st.columns(2)
                    with col1:
                        st.markdown("**📋 模板原文:**")
                        if detail['template_content']:
                            st.markdown(f'<div class="diff-template">{detail["template_content"]}</div>',
                                      unsafe_allow_html=True)
                        else:
                            st.markdown("*(无)*")
                    with col2:
                        st.markdown("**📑 业务合同:**")
                        if detail['business_content']:
                            st.markdown(f'<div class="diff-business">{detail["business_content"]}</div>',
                                      unsafe_allow_html=True)
                        else:
                            st.markdown("*(无)*")
                    st.markdown(f'<div class="diff-box">💬 {detail["description"]}</div>',
                              unsafe_allow_html=True)
        
        except Exception as e:
            st.error(f"❌ 错误:{str(e)}")
        finally:
            try:
                os.unlink(template_path)
                os.unlink(business_path)
            except:
                pass

else:
    # 欢迎界面
    st.markdown("""
    <div style="text-align:center;padding:80px 20px;">
        <div style="font-size:64px;margin-bottom:20px;">🤖</div>
        <h2>欢迎使用AI合同比对系统</h2>
        <p style="color:#666;font-size:18px;">请先在左侧上传合同文件,然后点击"开始AI比对"</p>
    </div>
    """, unsafe_allow_html=True)

五、准备测试数据

创建两个测试文件:

合同模板.txt

复制代码
采购合同

甲方:北京科技有限公司
乙方:上海贸易有限公司

第一条 合同金额
合同总金额为人民币壹佰万元整(¥1,000,000)

第二条 付款方式
合同签订后30日内支付全款

第三条 履行期限
2024年1月1日至2024年12月31日

第四条 违约责任
违约方支付合同总金额20%的违约金

第五条 争议解决
提交北京仲裁委员会仲裁

业务合同.txt

复制代码
采购合同

甲方:北京科技有限公司
乙方:深圳创新有限公司

第一条 合同金额
合同总金额为人民币壹佰贰拾万元整(¥1,200,000)

第二条 付款方式
合同签订后60日内支付全款

第三条 履行期限
2024年1月1日至2024年12月31日

第五条 争议解决
提交深圳仲裁委员会仲裁

第六条 知识产权
本项目知识产权归乙方所有

六、运行系统

6.1 启动程序

在项目目录下运行:

复制代码
streamlit run run_zhipu.py --server.port 8888

6.2 访问界面

浏览器自动打开 http://localhost:8888,你将看到:

https://via.placeholder.com/800x400?text=AI合同比对系统首页

6.3 使用步骤

  1. 左侧边栏输入智谱 API Key

  2. 选择模型 glm-4.7-flash

  3. 上传「合同模板.txt」和「业务合同.txt」

  4. 点击「开始AI比对」

6.4 查看结果

系统会展示:

  • 统计概览:总条款数、一致数、修改数、缺失数、新增数

  • 高风险提醒:红色醒目提示重大变更

  • 详细比对 :每个条款的原文对照和AI分析


七、系统架构解析

7.1 工作流程

复制代码
用户上传文件 → 文件解析 → AI模型比对 → 结果解析 → 可视化展示
     ↓            ↓            ↓            ↓            ↓
  Streamlit   python-docx   智谱API      JSON解析    HTML/CSS
              pdfplumber    glm-4-flash

7.2 核心设计理念

  1. 纯AI驱动:不写复杂的文本比对算法,全部交给大模型理解语义

  2. 结构化输出:要求AI输出固定JSON格式,便于程序解析

  3. 容错机制:API调用失败时自动切换到模拟数据

  4. 免费优先:使用国产免费模型,降低使用门槛

7.3 关键技术点

技术点 说明
Prompt工程 精心设计的提示词,确保AI输出结构化JSON
流式UI Streamlit自动处理前后端交互
文件处理 支持三种主流文档格式
临时文件 使用tempfile自动清理上传文件

八、常见问题解答

Q1:运行报错"No module named 'streamlit'"

复制代码
pip install streamlit -i https://pypi.tuna.tsinghua.edu.cn/simple

Q2:API调用返回401错误

  • 检查API Key是否正确

  • 确认已完成实名认证

  • 检查账户余额

Q3:上传文件后没有反应

  • 检查文件格式(仅支持docx/txt/pdf)

  • 文件不要超过6000字

  • 刷新页面重试

Q4:如何更换端口

复制代码
streamlit run run_zhipu.py --server.port 8080

Q5:如何停止程序

在命令提示符中按 Ctrl+C


九、进阶优化方向

9.1 功能增强

  • 添加合同版本对比历史记录

  • 支持批量比对多份合同

  • 导出比对报告为PDF/Excel

  • 添加关键词搜索功能

9.2 性能优化

  • 使用缓存减少重复API调用

  • 支持长文档分段处理

  • 添加并发处理能力

9.3 用户体验

  • 添加拖拽上传功能

  • 支持暗色主题

  • 添加进度条显示

  • 优化移动端适配


十、总结

本文从零开始,带领大家搭建了一个完整的AI合同比对系统。整个过程涉及:

  1. 环境搭建:Python安装、依赖管理

  2. API接入:智谱AI免费模型的使用

  3. 核心开发:Streamlit界面、文件解析、AI调用

  4. 测试验证:准备测试数据、运行调试

这个系统虽然简单,但涵盖了AI应用开发的完整流程,可以作为学习AI编程的入门项目。最重要的是,它使用了国产免费模型,没有任何使用门槛,每个人都可以轻松上手。

项目源码

完整代码已整理完毕,直接复制文中代码即可运行。

下一步行动

  • 尝试修改Prompt,优化比对效果

  • 添加更多文件格式支持

  • 部署到云端供团队使用

日期:2026年7月

技术栈:Python 3.11 + Streamlit + 智谱 GLM-4-Flash


如果你在搭建过程中遇到任何问题,欢迎留言交流!

组件 用途
Python 3.11 编程语言
Streamlit Web 界面框架
OpenAI SDK AI 模型调用(兼容智谱API)
python-docx Word 文件解析
pdfplumber PDF 文件解析
智谱 GLM-4-Flash 免费 AI 模型
相关推荐
Microvision维视智造1 小时前
10ppm是什么概念?锂电生产管控国标给AI视觉下了硬指标
人工智能·计算机视觉·视觉检测·机器视觉
初晴融雪-快雪时晴1 小时前
AI应用:二级市场AI应用全景深度分析(多表格结构化全文)
人工智能
XMAIPC_Robot1 小时前
软硬协同实时控制|RK3588业务调度+FPGA硬件时序,ethercat实现半导体设备微秒级响应(125us)
linux·arm开发·人工智能·fpga开发
用户938515635071 小时前
告别“屎山”与“幻觉”:从“Vibe Coding”到“胶水编程”的AI协作实践指南
人工智能
北五环小裁缝1 小时前
Coding工具搜索不够全?我做个 deuseek:一条命令补齐全网搜索
人工智能·搜索引擎
扑克中的黑桃A2 小时前
Dify + DeepSeek 搭了一个“职场规矩智能体”,专治各种尴尬
人工智能
临床数据科学和人工智能兴趣组2 小时前
RStudio的Console(控制台)是一个非常重要的组件
人工智能·机器学习·数据分析·r语言·r语言-4.2.1
AOwhisky2 小时前
Python 学习笔记(第十五期)——运维自动化(下·后篇):堡垒机实战——paramiko高阶篇
运维·python·学习·云原生·自动化·运维开发
观察员2 小时前
带你用一个 Python 图书系统,通关封装继承多态
python