离线合同结构化提取与检索:LangExtract + 本地DeepSeek + Elasticsearch 9.x

一、方案总览

核心解决的问题

针对法律、金融等行业合同、发票等非结构化文本数据,解决人工提取效率低、云端大模型数据泄露风险、纯大模型提取结果不可控/不可追溯三大核心痛点,实现:

  1. 100%本地闭环:合同文本、模型推理、数据存储全流程不出本地环境,运行阶段无外网依赖,满足强合规要求

  2. 精准结构化提取:基于LangExtract实现可追溯、格式可控的字段提取,从根源上杜绝大模型幻觉

  3. 毫秒级检索筛选:基于Elasticsearch 9.x实现结构化数据多条件筛选+全文检索,适配复杂业务查询场景

  4. 零API成本:无需申请任何云端大模型API,无调用次数、流量限制,可无限量处理文档

核心技术栈

组件 选型 核心作用
大语言模型 DeepSeek-R1/DeepSeek-V3(本地部署) 负责文本语义理解与关键信息提取,通过Ollama部署,提供OpenAI兼容的本地API接口
信息提取框架 LangExtract 封装标准化结构化提取逻辑,提供少样本学习、结果溯源、格式校验、可视化能力,解决纯大模型提取的不稳定性
存储与检索引擎 Elasticsearch 9.x 本地部署 结构化数据持久化存储、全文检索、多维度筛选,支持海量合同数据的高并发查询
模型部署工具 Ollama(新手推荐)/vLLM(高性能场景) 一键部署本地DeepSeek模型,屏蔽复杂的模型配置,自动提供标准化API服务

适用场景

  • 企业合同管理系统:批量合同信息提取、智能归档、多维度检索

  • 金融信贷审核:发票、财报、信贷合同的关键信息自动提取与核验

  • 法务合规场景:合同主体、期限、金额、适用法律的批量核查与风险预警

  • 内网/物理隔离环境:无法访问外网的政府、国企、金融机构内部系统

  • 其他非结构化文本处理:简历解析、病历提取、工单信息结构化等场景

前置环境要求

环境项 最低要求 推荐配置
操作系统 Windows 10+/macOS 12+/Linux CentOS7+/Ubuntu20.04+ 同左,64位稳定版系统
Java环境 JDK 17+(Elasticsearch 9.x 强制要求) JDK 17 LTS 稳定版
Python环境 Python 3.10 ~ 3.12 Python 3.11(兼容性最优)
硬件(7B模型) 16GB可用内存,CPU运行 16GB内存 + 8GB以上NVIDIA显存GPU
硬件(14B模型) 32GB可用内存 32GB内存 + 16GB以上NVIDIA显存GPU
网络 部署阶段需外网拉取模型和安装包,运行阶段完全离线 同左

二、全流程环境部署(一步不缺,新手可直接照搬)

2.1 Elasticsearch 9.x 本地部署与初始化

核心目的:搭建本地存储与检索引擎,完成安全配置与凭证生成,为后续数据写入和查询做准备

2.1.1 下载与安装
  1. 打开Elasticsearch官方下载页,选择对应系统的9.x最新稳定版安装包

  2. 解压到无中文、无空格、无特殊字符的本地路径:

    • Windows示例:D:\elasticsearch-9.3.0

    • macOS/Linux示例:/opt/elasticsearch-9.3.0

2.1.2 启动前置配置
操作系统 必做配置
Windows 1. 右键以管理员身份运行终端 2. 临时关闭杀毒软件实时防护(避免误杀ES进程)
macOS 1. 打开「系统设置→隐私与安全性」,允许ES的开发者权限 2. 终端执行sudo xattr -r -d com.apple.quarantine ES解压目录,解决文件无法打开的问题
Linux 1. 执行sudo sysctl -w vm.max_map_count=262144,解决内存映射不足的启动报错 2. 禁止用root用户直接启动ES,需新建普通用户并给ES目录授权
2.1.3 启动ES并获取核心凭证
  1. 打开终端,进入ES解压后的bin目录

  2. 执行启动命令:

    • Windows:elasticsearch.bat

    • macOS/Linux:./elasticsearch

  3. 启动成功后,终端会输出3组核心信息,必须复制保存

    • elastic超级用户的初始密码(示例:elastic: abc123def456

    • HTTPS访问地址(默认:https://localhost:9200

    • 根CA证书路径(默认:ES解压目录/config/certs/http_ca.crt

  4. 验证启动成功:浏览器访问https://localhost:9200,输入用户名elastic和上述密码,能返回ES版本信息的JSON数据,即为启动正常

2.1.4 生成专属API密钥(安全必备)

ES 9.x 推荐使用API密钥做身份认证,避免代码中硬编码账号密码,生成方式:

  1. 新建终端窗口,进入ES的bin目录

  2. 执行以下命令,生成有效期30天的API密钥:

    Bash 复制代码
    # Windows
    elasticsearch-api-key.bat create "langextract-es-key" --expiration 30d
    
    # macOS/Linux
    ./elasticsearch-api-key create "langextract-es-key" --expiration 30d
  3. 执行后会输出一串API密钥字符串,必须复制保存,后续代码会用到


2.2 本地DeepSeek大模型部署

核心目的:一键部署本地DeepSeek模型,提供OpenAI兼容的API接口,实现完全离线的文本提取能力,彻底摆脱云端依赖

2.2.1 安装Ollama模型部署工具

Ollama是开源的本地大模型一键部署工具,屏蔽了复杂的模型配置、环境依赖问题,对新手极度友好:

  1. 打开Ollama官方下载页,选择对应系统的安装包

  2. 按提示完成安装,安装后Ollama会自动在后台运行,并开机自启

2.2.2 拉取并运行DeepSeek模型
  1. 打开新的终端窗口,执行以下命令拉取适配硬件的DeepSeek模型(推荐先试7B版本,平衡速度与准确率):

    Bash 复制代码
    # 首选:DeepSeek-R1:7B(推理能力强,适合信息提取任务,8GB显存/16GB内存可跑)
    ollama pull deepseek-r1:7b
    
    # 高配置可选:DeepSeek-R1:14B(准确率更高,16GB显存/32GB内存可跑)
    # ollama pull deepseek-r1:14b
    
    # 低配置可选:DeepSeek-V3:7B(速度更快,适合简单提取场景)
    # ollama pull deepseek-v3:7b
  2. 验证模型是否正常运行:

    Bash 复制代码
    # 进入模型交互界面
    ollama run deepseek-r1:7b
    # 输入测试问题:你好,请介绍一下自己
    # 能正常回复说明模型运行成功,输入 /exit 退出交互界面
  3. 核心特性 :Ollama会自动在后台启动OpenAI兼容的API服务 ,默认地址为http://localhost:11434/v1,后续LangExtract会通过这个地址调用本地模型,无需额外配置

2.2.3 验证本地API接口(提前避坑)

执行以下curl命令,验证本地API服务是否正常(确保Ollama在后台运行):

Bash 复制代码
curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-r1:7b",
    "messages": [{"role": "user", "content": "你好"}],
    "stream": false
  }'

能正常返回JSON格式的响应内容,说明API服务正常,后续代码可正常调用。


2.3 Python依赖安装

  1. 本地新建项目文件夹(示例:contract-extraction),作为项目根目录

  2. 打开终端,进入项目根目录,执行以下命令安装所有依赖:

    Bash 复制代码
    # 升级LangExtract到最新版(必须,旧版本不支持自定义OpenAI兼容API)
    pip install --upgrade langextract -q
    # 安装其他核心依赖
    pip install elasticsearch python-dotenv -q
  3. 依赖包作用说明:

    • langextract:核心结构化提取框架,适配本地大模型

    • elasticsearch:ES官方Python客户端,用于和本地ES服务通信

    • python-dotenv:环境变量管理工具,避免密钥硬编码在代码中,提升安全性


三、核心工作原理详解(先懂原理,再做实操,少走90%的弯路)

3.1 整体工作闭环流程

Plain 复制代码
本地合同文本文件 → LangExtract按预设规则调用本地DeepSeek提取结构化数据 → 结果校验与格式转换 → 批量写入Elasticsearch 9.x → 按需执行多条件筛选/全文检索/统计分析

全程数据不出本地环境,部署完成后可完全离线运行。

3.2 核心组件原理解释

3.2.1 LangExtract:解决纯大模型提取的核心痛点

LangExtract是Google开源的信息提取框架,底层基于大模型,但针对结构化提取场景做了深度优化,和纯大模型直接提取的核心差异如下:

纯大模型直接提取 LangExtract + 本地DeepSeek
输出格式不稳定,经常不按要求返回结构化数据 强制输出标准化的字段+值结构,格式100%可控
结果无法追溯,不知道数据来自原文哪个位置,极易出现幻觉 每个提取结果都绑定原文的字符位置,支持可视化溯源,从根源上杜绝编造数据
准确率完全依赖prompt,稳定性极差 支持少样本示例学习,给1-3个示例就能让模型对齐提取规则,准确率大幅提升
长文本处理能力弱,容易遗漏关键信息 原生支持长上下文、多轮提取补全、多进程并行处理
它的核心工作逻辑:
  1. 你通过「prompt描述+少样本示例」,明确告诉它要提取的字段、格式规则、归一化要求

  2. 它调用本地DeepSeek模型,严格按规则从文本中提取对应内容,同时记录每个内容在原文中的字符位置

  3. 自动完成格式校验、数据归一化(比如日期统一为MM/dd/yyyy、月份转天数、金额去符号)

  4. 输出可追溯的结构化结果,支持HTML可视化校验

3.2.2 Elasticsearch 9.x 索引设计原理

ES的索引相当于MySQL的表,映射(mapping)相当于表结构,字段类型的选型直接决定了数据写入的成功率和查询效率,本方案的选型逻辑如下:

字段类型 核心特性 本方案选型规则
date 支持时间范围筛选、排序,必须指定日期格式 合同签订日期、到期日期,统一指定格式为MM/dd/yyyy,和LangExtract的提取格式完全对齐
keyword 不分词的精准匹配类型,支持精准筛选、聚合、排序 仅用于精准筛选的字段(服务类型、适用法律),不需要分词搜索
text + keyword 双字段类型,既支持全文分词搜索,又支持精准筛选 合同双方主体名称(既支持模糊搜索,又支持按完整名称筛选)
float 浮点数值类型,支持数值范围筛选 付款金额,适配带小数的金额场景
integer 整数数值类型,支持数值范围筛选,占用空间更小、查询更快 交付天数,均为整数场景
text 全文分词类型,支持模糊搜索、语义匹配 原始合同文本,支持合同全文检索
3.2.3 本地DeepSeek调用逻辑

LangExtract原生支持OpenAI兼容的API接口,因此只需通过3个核心参数,即可无缝对接本地部署的DeepSeek模型,无需修改核心提取逻辑:

  • model_id:指定本地部署的模型名称(和Ollama拉取的模型名称完全一致)

  • api_base:指定本地Ollama提供的API服务地址(默认http://localhost:11434/v1

  • api_key:本地模型无需真实密钥,填写任意占位符即可(示例:ollama-local-key


四、完整代码实现(可直接复制运行,全流程闭环)

4.1 项目目录结构

先按以下结构创建文件夹和文件,确保代码能正常运行:

Plain 复制代码
contract-extraction/
├── contracts/                # 存放所有合同txt文件
├── .env                      # 环境变量配置文件(密钥、路径)
└── main.py                   # 核心运行代码

4.2 环境变量配置文件(.env)

在项目根目录新建.env文件,粘贴以下内容,替换为你自己的配置:

Plain 复制代码
# --------------------------
# Elasticsearch 9.x 配置
# --------------------------
# ES本地访问地址,默认https://localhost:9200
ELASTICSEARCH_URL=https://localhost:9200
# 你之前生成的ES API密钥
ELASTICSEARCH_API_KEY=你的ES API密钥
# ES根CA证书的绝对路径,在ES解压目录的config/certs/http_ca.crt
# Windows示例:ES_CA_CERTS_PATH=D:\\elasticsearch-9.3.0\\config\\certs\\http_ca.crt
# macOS/Linux示例:ES_CA_CERTS_PATH=/opt/elasticsearch-9.3.0/config/certs/http_ca.crt
ES_CA_CERTS_PATH=你的ES CA证书绝对路径

# --------------------------
# 本地DeepSeek模型配置
# --------------------------
# Ollama提供的OpenAI兼容API地址,默认无需修改
LANGEXTRACT_API_BASE=http://localhost:11434/v1
# 本地模型无需真实密钥,任意占位符即可
LANGEXTRACT_API_KEY=ollama-local-key
# 你拉取的DeepSeek模型名称,必须和ollama pull的名称完全一致
LANGEXTRACT_MODEL_ID=deepseek-r1:7b

4.3 核心运行代码(<main.py>)

在项目根目录新建main.py文件,粘贴以下完整代码,每一行都带详细注释,可直接运行:

Python 复制代码
# --------------------------
# 1. 导入所需依赖库
# --------------------------
from elasticsearch import Elasticsearch, helpers
import os
from dotenv import load_dotenv
import langextract as lx
import glob
import json

# --------------------------
# 2. 加载环境变量 & 全局配置
# --------------------------
# 加载.env文件中的配置,避免密钥硬编码
load_dotenv()

# 全局配置,统一管理,后续修改仅需调整此处
INDEX_NAME = "contracts"  # ES索引名称,相当于MySQL的表名
CONTRACTS_DIR = "./contracts"  # 合同文件存放目录
NDJSON_FILE = "extraction_results.ndjson"  # 提取结果保存文件

# 本地DeepSeek配置(从.env读取)
LANGEXTRACT_API_BASE = os.getenv("LANGEXTRACT_API_BASE")
LANGEXTRACT_API_KEY = os.getenv("LANGEXTRACT_API_KEY")
LANGEXTRACT_MODEL_ID = os.getenv("LANGEXTRACT_MODEL_ID")

# --------------------------
# 3. Elasticsearch客户端初始化 & 索引创建
# --------------------------
# 初始化ES客户端,和本地ES服务建立连接
es_client = Elasticsearch(
    hosts=os.getenv("ELASTICSEARCH_URL"),
    api_key=os.getenv("ELASTICSEARCH_API_KEY"),
    ca_certs=os.getenv("ES_CA_CERTS_PATH"),
    # 本地测试可临时关闭证书验证,生产环境必须开启
    # verify_certs=False
)

# 验证ES连接是否正常
if not es_client.ping():
    raise Exception("❌ ES连接失败,请检查地址、API密钥、证书路径是否正确")
print("✅ ES连接成功")

# 定义ES索引映射(表结构),严格和提取字段对齐
index_mapping = {
    "mappings": {
        "properties": {
            "contract_date": {"type": "date", "format": "MM/dd/yyyy"},
            "end_contract_date": {"type": "date", "format": "MM/dd/yyyy"},
            "service_provider": {
                "type": "text",
                "fields": {"keyword": {"type": "keyword", "ignore_above": 256}}
            },
            "client": {
                "type": "text",
                "fields": {"keyword": {"type": "keyword", "ignore_above": 256}}
            },
            "service_type": {"type": "keyword"},
            "payment_amount": {"type": "float"},
            "delivery_time_days": {"type": "integer"},
            "governing_law": {"type": "keyword"},
            "raw_contract": {"type": "text"}
        }
    }
}

# 创建索引(仅当索引不存在时创建,避免重复创建报错)
if not es_client.indices.exists(index=INDEX_NAME):
    es_client.indices.create(index=INDEX_NAME, body=index_mapping)
    print(f"✅ 索引 {INDEX_NAME} 创建成功")
else:
    print(f"ℹ️ 索引 {INDEX_NAME} 已存在,无需重复创建")

# --------------------------
# 4. LangExtract提取规则配置(核心,决定提取准确率)
# --------------------------
# 提取prompt:清晰、无歧义,明确提取规则、格式要求、禁止项
contract_prompt_description = """
从合同文本中提取以下结构化信息,严格遵守所有规则:
1. 必须提取的字段:contract_date、end_contract_date、service_provider、client、service_type、payment_amount、delivery_time_days、governing_law
2. 日期格式统一为 MM/dd/yyyy,例如2025年2月2日必须转为02/02/2025
3. 付款金额仅保留数字,去掉$符号、千分位逗号,例如$3,200必须转为3200
4. 交付时间统一转为天数,例如3个月转为90天,1年转为365天
5. 所有提取内容必须100%来自原文,禁止编造、补充任何原文没有的信息,找不到的字段留空
6. 按文本中信息出现的顺序提取
"""

# 少样本示例:给模型做示范,是提升提取准确率的核心,可根据你的合同格式补充更多示例
contract_examples = [
    lx.data.ExampleData(
        # 示例合同原文
        text="Service Agreement dated March 10, 2024, between ABC Corp (Service Provider) and John Doe (Client) for consulting services. Payment: $5,000. Delivery: 30 days. Contract ends June 10, 2024. Governed by California law.",
        # 预期提取结果,严格和prompt中的字段对齐
        extractions=[
            lx.data.Extraction(extraction_class="contract_date", extraction_text="03/10/2024"),
            lx.data.Extraction(extraction_class="end_contract_date", extraction_text="06/10/2024"),
            lx.data.Extraction(extraction_class="service_provider", extraction_text="ABC Corp"),
            lx.data.Extraction(extraction_class="client", extraction_text="John Doe"),
            lx.data.Extraction(extraction_class="service_type", extraction_text="consulting services"),
            lx.data.Extraction(extraction_class="payment_amount", extraction_text="5000"),
            lx.data.Extraction(extraction_class="delivery_time_days", extraction_text="30"),
            lx.data.Extraction(extraction_class="governing_law", extraction_text="California"),
        ],
    )
]

# --------------------------
# 5. 批量读取合同文件 & 调用本地DeepSeek提取数据
# --------------------------
# 匹配合同目录下所有txt格式的合同文件
contract_files = glob.glob(os.path.join(CONTRACTS_DIR, "*.txt"))

# 检查是否找到合同文件
if len(contract_files) == 0:
    raise Exception(f"❌ 在 {CONTRACTS_DIR} 目录下未找到任何txt合同文件,请检查路径")
print(f"📄 找到 {len(contract_files)} 个合同文件")

# 批量提取合同数据
extraction_results = []
for file_path in contract_files:
    filename = os.path.basename(file_path)
    print(f"\n🔄 正在处理文件:{filename}")

    # 读取合同文件内容,指定utf-8编码避免乱码
    with open(file_path, "r", encoding="utf-8") as f:
        contract_content = f.read()

    # 调用本地DeepSeek模型执行提取
    contract_result = lx.extract(
        text_or_documents=contract_content,
        prompt_description=contract_prompt_description,
        examples=contract_examples,
        # 核心参数:对接本地DeepSeek模型
        model_id=LANGEXTRACT_MODEL_ID,
        api_base=LANGEXTRACT_API_BASE,
        api_key=LANGEXTRACT_API_KEY,
        # 提取参数优化:降低温度,让输出更稳定,信息提取场景推荐0.05-0.1
        temperature=0.1,
    )

    extraction_results.append(contract_result)
    print(f"✅ {filename} 处理完成,提取到 {len(contract_result.extractions)} 个字段")

# 保存提取结果到NDJSON文件,方便后续写入ES和校验
lx.io.save_annotated_documents(extraction_results, output_name=NDJSON_FILE, output_dir=".")
print(f"\n💾 提取结果已保存到 {NDJSON_FILE}")

# 生成提取结果可视化HTML文件,可打开查看每个提取结果对应的原文位置,校验是否准确
html_content = lx.visualize(NDJSON_FILE)
with open("extraction_visualization.html", "w", encoding="utf-8") as f:
    f.write(html_content.data)
print("📊 提取结果可视化文件已生成:extraction_visualization.html,打开即可校验提取准确率")

# --------------------------
# 6. 批量写入提取结果到Elasticsearch
# --------------------------
def build_es_documents(ndjson_file, index_name):
    """
    构建ES批量写入的文档生成器
    核心作用:做数据类型转换,解决字符串写入数值/日期字段的报错问题;用生成器减少内存占用
    """
    with open(ndjson_file, "r", encoding="utf-8") as f:
        for line in f:
            doc = json.loads(line)
            es_doc = {}

            # 遍历提取结果,转换为ES文档的字段和值
            for extraction in doc["extractions"]:
                field_name = extraction["extraction_class"]
                field_value = extraction["extraction_text"]

                # 数据类型转换,严格和ES映射中的类型对齐
                try:
                    if field_name == "payment_amount":
                        es_doc[field_name] = float(field_value)
                    elif field_name == "delivery_time_days":
                        es_doc[field_name] = int(field_value)
                    else:
                        es_doc[field_name] = field_value
                except Exception as e:
                    print(f"⚠️ 字段 {field_name} 类型转换失败,值:{field_value},错误:{e}")
                    es_doc[field_name] = None

            # 写入原始合同文本,支持全文检索
            es_doc["raw_contract"] = doc["text"]

            # 生成ES bulk API要求的格式
            yield {"_index": index_name, "_source": es_doc}

# 执行批量写入
try:
    success_count, error_list = helpers.bulk(
        client=es_client,
        actions=build_es_documents(NDJSON_FILE, INDEX_NAME),
        chunk_size=100  # 批次大小,合同量大可调整
    )
    print(f"\n✅ 批量写入完成,成功写入 {success_count} 个文档")
    if error_list:
        print(f"⚠️ 写入错误详情:{error_list}")
except Exception as e:
    print(f"❌ 批量写入失败,错误:{str(e)}")

# --------------------------
# 7. ES数据查询示例(覆盖常用业务场景)
# --------------------------
print("\n==================== 🔍 数据查询结果 ====================")

# 场景1:查询已过期、付款金额≥15000的合同
print("\n1. 已过期、付款金额≥15000的合同:")
try:
    response = es_client.search(
        index=INDEX_NAME,
        source_excludes=["raw_contract"],  # 排除原始合同文本,让结果更简洁
        body={
            "query": {
                "bool": {
                    "filter": [
                        {"range": {"payment_amount": {"gte": 15000}}},
                        {"range": {"end_contract_date": {"lte": "now"}}},
                    ]
                }
            }
        }
    )
    print(f"匹配到 {response['hits']['total']['value']} 个合同:")
    for hit in response["hits"]["hits"]:
        print(json.dumps(hit["_source"], indent=4, ensure_ascii=False))
except Exception as e:
    print(f"查询失败:{str(e)}")

# 场景2:按服务提供方精准查询
print("\n2. 服务提供方为GreenLeaf Landscaping Co.的合同:")
try:
    response = es_client.search(
        index=INDEX_NAME,
        source_excludes=["raw_contract"],
        body={
            "query": {
                "term": {"service_provider.keyword": "GreenLeaf Landscaping Co."}
            }
        }
    )
    print(f"匹配到 {response['hits']['total']['value']} 个合同:")
    for hit in response["hits"]["hits"]:
        print(json.dumps(hit["_source"], indent=4, ensure_ascii=False))
except Exception as e:
    print(f"查询失败:{str(e)}")

# 场景3:全文检索合同内容中的关键词
print("\n3. 合同内容包含'landscaping'的合同:")
try:
    response = es_client.search(
        index=INDEX_NAME,
        source_excludes=["raw_contract"],
        body={
            "query": {
                "match": {"raw_contract": "landscaping"}
            }
        }
    )
    print(f"匹配到 {response['hits']['total']['value']} 个合同:")
    for hit in response["hits"]["hits"]:
        print(json.dumps(hit["_source"], indent=4, ensure_ascii=False))
except Exception as e:
    print(f"查询失败:{str(e)}")

4.4 测试合同文件准备

contracts文件夹中,新建contract1.txt文件,粘贴以下测试合同内容,也可以添加自己的合同文件:

Plain 复制代码
This Contract Agreement ("Agreement") is made and entered into on February 2, 2025, by and between:
* Contractor: GreenLeaf Landscaping Co.
* Contractee: Robert Jenkins

Purpose: Garden maintenance and landscaping for private residence.
Terms and Conditions:
1. The Client agrees to pay the Service Provider the sum of $3,200 for the services.
2. The Service Provider agrees to provide landscaping and maintenance services for a period of 3 months.
3. This Agreement shall terminate on May 2, 2025.
4. This Agreement shall be governed by the laws of California.
5. Both parties accept the conditions stated herein.

Signed:
GreenLeaf Landscaping Co.
Robert Jenkins

五、运行与结果验证

5.1 前置服务检查

运行代码前,必须确保:

  1. Elasticsearch服务正在后台运行,能正常访问https://localhost:9200

  2. Ollama服务正在后台运行,执行ollama list能看到你拉取的DeepSeek模型

  3. 项目目录结构正确,.env文件配置无误,contracts文件夹中有txt合同文件

5.2 运行代码

打开终端,进入项目根目录,执行以下命令运行完整代码:

Bash 复制代码
python main.py

5.3 运行结果验证

运行完成后,你将得到以下结果,可逐一验证:

  1. 终端输出:依次输出ES连接状态、找到的合同文件、每个文件的提取进度、写入结果、3个场景的查询结果

  2. 提取结果文件 :项目根目录生成extraction_results.ndjson,包含所有合同的结构化提取结果

  3. 可视化校验文件 :项目根目录生成extraction_visualization.html,用浏览器打开即可看到每个提取结果对应的原文高亮位置,验证提取是否准确、有无幻觉

  4. ES数据验证 :可通过Kibana或ES API查询contracts索引,确认数据已成功写入,字段类型正确


六、全场景常见问题排查与避坑指南

6.1 Elasticsearch相关问题

问题现象 根因与解决方案
ES连接失败 1. 检查ES服务是否正常启动 2. 检查API密钥是否正确、是否过期 3. 检查CA证书路径是否正确,Windows路径需用双反斜杠 4. 本地测试可临时开启verify_certs=False关闭证书验证
索引创建失败 1. 检查索引是否已存在,已存在需先删除再创建 2. 检查mapping语法是否正确,字段类型是否为ES支持的类型
数据写入报错 90%是类型不匹配:检查提取的字段值是否能转换为ES映射中的类型,比如日期格式是否和format一致,数值是否有非数字字符
查询不到数据 1. 确认数据已成功写入,终端输出成功写入的文档数>0 2. 检查字段名是否拼写正确,精准查询是否加了.keyword后缀 3. ES写入有1秒刷新间隔,可手动执行es_client.indices.refresh(index=INDEX_NAME)刷新

6.2 本地DeepSeek相关问题

问题现象 根因与解决方案
LangExtract连接本地API失败 1. 检查Ollama是否在后台运行,浏览器访问http://localhost:11434能看到欢迎页 2. 检查api_base是否正确,必须带/v1后缀 3. 检查模型名称是否和ollama list中的名称完全一致
模型推理速度极慢 1. CPU运行:仅用7B模型,关闭其他占用内存的程序 2. GPU加速:安装CUDA和cuDNN,Ollama会自动识别NVIDIA GPU,速度提升5-10倍 3. 减少并发,单文件依次处理
提取准确率低、结果乱 1. 降低temperature到0.05-0.1,让输出更稳定 2. 增加2-3个贴合你合同格式的少样本示例 3. 优化prompt,把提取规则写得更明确,增加禁止编造的强调 4. 硬件允许的话,更换14B/32B更大的模型
模型加载失败、闪退 1. 检查可用内存是否足够,7B模型至少需要8GB可用内存 2. 清理Ollama缓存,重新拉取模型 3. 关闭其他占用内存的程序,释放系统资源

6.3 代码运行相关问题

问题现象 根因与解决方案
找不到合同文件 检查CONTRACTS_DIR路径是否正确,合同文件是否为.txt格式
依赖安装失败 1. 升级pip到最新版:pip install --upgrade pip 2. 更换国内pip镜像源:pip install 包名 -i https://pypi.tuna.tsinghua.edu.cn/simple 3. 检查Python版本是否在3.10~3.12之间
合同文本乱码 读取文件时指定encoding="utf-8",确保合同文件的编码为UTF-8

七、进阶优化方案

7.1 推理速度优化:vLLM部署DeepSeek

如果需要批量处理成百上千份合同,Ollama的速度可能无法满足需求,可使用vLLM部署DeepSeek模型,推理速度比Ollama快2-4倍,支持更高并发:

  1. 安装vLLM:pip install vllm

  2. 启动vLLM的OpenAI兼容API服务:

    Bash 复制代码
    vllm serve deepseek-ai/DeepSeek-R1-7B --port 8000
  3. 修改.env文件中的配置:

    Plain 复制代码
    LANGEXTRACT_API_BASE=http://localhost:8000/v1
    LANGEXTRACT_MODEL_ID=deepseek-ai/DeepSeek-R1-7B

7.2 提取准确率优化

  1. 少样本优化:准备5-10份贴合你业务场景的标注合同示例,覆盖不同格式、不同边界场景,准确率可提升30%以上

  2. 多轮提取优化 :在lx.extract中设置num_passes=2,让模型两轮提取补全遗漏的信息,提升召回率

  3. 模型微调:用10-50份标注数据,通过LLaMA Factory对DeepSeek做LoRA微调,适配特定行业的合同术语,提取准确率会大幅提升

  4. 规则校验:增加自定义后处理规则,比如日期合法性校验、金额大于0校验、主体名称非空校验,不符合规则的自动重新提取

7.3 功能扩展

  1. 多格式文件支持 :通过pdfplumberpython-docx库,实现PDF、Word格式合同的文本自动提取,无需手动转为txt

  2. 完全离线向量搜索:用本地开源Embedding模型(BGE-M3、M3E)生成合同文本的向量,结合ES 9.x的向量搜索能力,实现完全离线的语义级合同检索

  3. 多进程并行处理 :在批量提取时开启num_workers多进程,同时处理多份合同,大幅提升批量处理效率

  4. 合同风险预警:在提取规则中增加风险条款提取,实现合同风险自动识别与预警

相关推荐
TDengine (老段)2 小时前
TDengine IDMP 数据可视化——散点图
大数据·数据库·物联网·信息可视化·时序数据库·tdengine·涛思数据
王九思2 小时前
Thrift Server 介绍
大数据·系统架构·运维开发
梦想的旅途22 小时前
企业微信API:外部群自动化推送实战指南
大数据·机器人·自动化·企业微信·rpa
zxfBdd3 小时前
Error:scala: No ‘scala-library*.jar‘ in Scala compiler classpath in Scala SDK
大数据·scala·jar
AI猫站长4 小时前
快讯|灵心巧手融资15亿计划2026年交付5-10万台灵巧手,Linker Hand系列覆盖多种技术路线
大数据·人工智能·机器人·具身智能·灵心巧手
清 晨4 小时前
知识产权投诉增多跨境卖家如何构建图片文案证据链
大数据·人工智能·跨境电商·亚马逊·内容营销
汽车仪器仪表相关领域4 小时前
中小型储能/轻型电动车电池管理中枢:BMS-100型电池管理系统 全场景实战全解
大数据·网络·人工智能
xhaoDream4 小时前
Hive3.1.3 配置 Tez 引擎
大数据·hive·tez
Asher05094 小时前
Spark核心基础与架构全解析
大数据·架构·spark