为了账号安全,请及时绑定邮箱和手机立即绑定

构建本地文档智能解决方案:ExtractThinker、Ollama和Docling助力银行和金融机构高效处理文档

这是由Imagen 3生成的

在这个新的大型语言模型(LLM)时代,银行和金融机构处于一定的劣势,因为这些前沿模型由于硬件需求几乎无法在本地部署。然而,银行数据的敏感性引发了严重的隐私担忧,尤其是在它们只作为云服务存在时。为了解决这些挑战,组织可以转向本地部署或小型语言模型(SLM)方案,以将数据保留在内部,避免敏感信息的潜在外泄。这样可以利用先进的LLM(本地或仅通过少量外部请求),同时确保严格遵守如GDPR、HIPAA或各种金融法规等规定。

本文将展示如何通过结合以下几点,构建一个完全本地部署的文档智能解决方案:

  • ExtractThinker — 一个开源框架,用于协调OCR、分类和数据提取管道,供LLMs使用
  • Ollama — 一个本地部署语言模型(如Phi-4或Llama 3.x)的解决方案
  • DoclingMarkItDown — 灵活的库,用于处理文档加载、OCR和布局解析

无论你是在严格的保密规定下操作,处理扫描PDF或需要从PDF中提取信息,还是想要高级的视觉提取,这个端到端的栈提供了一个安全且高性能的管道,完全在你自己的基础设施中。

1 挑选正确的模型(文本 vs. 视觉)

这张图片是作者自己做的

当你构建文档智能栈时,首先需要决定是否需要一个文本模型或一个 视觉模型。文本模型通常更适用于本地解决方案,因为它们更容易获得且使用限制较少。然而,视觉模型对于复杂的分割任务非常关键,特别是当文档依赖于视觉线索,例如布局、颜色方案或特定格式时。

在某些情况下,您可以为不同阶段搭配不同的模型。例如,一个更小的moondream模型(0.5B 参数)可以处理拆分,而 Phi-4 14B 模型则负责分类和提取任务。许多大型机构更倾向于部署一个单一且更强大的模型(例如,70B 参数范围内的 Llama 3.3 或 Qwen 2.5),以涵盖所有使用场景。如果您只需要英文为中心的 IDP 系统,您可以主要使用 Phi4 来完成大部分任务,并让 moondream 模型作为特殊情况下的备用拆分工具。这一切都取决于您的具体需求和可用的基础设施情况。

2. 处理文档的过程:MarkItDown (保留原名) vs. Docling (保留原名)

在文档解析方面,有两个特别突出的库。

MarkItDown(一个特定的术语或名称,此处保持原样)

注:为了帮助理解,“MarkItDown”在此处保持原文,具体含义请参考上下文。

  • 更简单,更直接,由微软广泛支持
  • 非常适合直接处理文本任务,不需要使用多个OCR引擎
  • 安装和集成都非常简单
道克林
  • 更高级,支持多种OCR(比如Tesseract、AWS Textract、Google Document AI等)
  • 非常适合用于扫描工作流程或从图像PDF中稳健提取信息
  • 提供详细的文档,非常适合处理复杂布局

ExtractThinker 让你根据需要选择 DocumentLoaderMarkItDownDocumentLoaderDocling —— 简单的数字 PDF 文件或多引擎 OCR 技术。

  1. 运行本地模型程序

尽管 Ollama 是一个流行的本地托管 LLM 工具,现在也有一些可以与 ExtractThinker 无缝集成的本地部署方案。

  • LocalAI — 一个开源平台,可以在本地模仿OpenAI的API。它可以在消费级设备(甚至仅CPU)上运行语言模型(如Llama 2或Mistral),并提供一个简单的端点以进行对接。
  • OpenLLM — 由BentoML开发的项目,通过一个与OpenAI兼容的API来暴露语言模型。它优化了吞吐量和低延迟,以提供高效的响应速度,适合在本地和云环境中使用,并支持广泛的开源语言模型。
  • Llama.cpp — 运行Llama模型的底层实现,支持高级的自定义配置。非常适合需要细致控制或HPC环境,但管理起来相对复杂。

Ollama 因为易于设置和简单的CLI而成为首选。然而,在企业或HPC场景下部署Llama.cpp服务器、使用OpenLLM解决方案,或使用LocalAI这样的解决方案可能更为合适。所有这些都可以通过将本地LLM端点指向代码中的环境变量或基础URL,轻松地与ExtractThinker集成。

4: 应对小范围上下文

策略流:原创作者

当处理具有有限的上下文窗口(例如不超过大约8K令牌)的本地的模型时,管理以下几点变得非常重要:

文档拆分

为了避免模型输入容量超限,惰性分段 更为理想。而是选择分批次处理文档。

  • 逐页比较页面(例如,第1至第2页,然后是第2至第3页),判断它们是否属于同一个子文档。
  • 如果属于同一个子文档,你将它们进入下一步。如果不属于同一个子文档,你将开始一个新的部分。
  • 这种方法节省内存,并且通过每次只加载和分析少数几页即可扩展到非常大的PDF文档。

当您有更多的标记限制时,使用 Concatenate 更适合;当窗口大小受限时,更推荐使用 Paginate。注意哦,这是根据您的具体情况进行的建议。

应对部分回复

对于较小的本地模型来说,如果提示较大,每个响应也可能被截断。PaginationHandler 优雅地解决了这个问题。

  • 将文档的每一页分别作为单独的请求(每页一个请求)。
  • 最后合并结果,如有必要,解决页面之间在某些字段上的分歧。

注意: 当您有更多可用的 token 时,建议使用 Concatenate;如果 token 数量有限,建议使用 Paginate。

快速示例
  1. 懒惰拆分 PDF,确保每一页都保持在你模型的限制之下。
  2. 跨页处理:每个部分的结果分别返回。
  3. 合并每个部分的页面结果以形成最终的结构化结果。

这种方法确保你不会超出模型的上下文窗口——无论是处理 PDF 还是多页响应的方式。

5. ExtractThinker:构建我们的栈

以下是一个展示如何将这些组件集成在一起的代码片段。首先,安装 ExtractThinker

    pip install extract-thinker
文档加载器(Document Loader)

正如上面提到的,我们可以用MarkitDown或Docling。

    from extract_thinker import DocumentLoaderMarkItDown, DocumentLoaderDocling  

    # 文档加载器可以选择 DocumentLoaderDocling 或 DocumentLoaderMarkItDown 中的一种  
    document_loader = DocumentLoaderDocling()
关于定义合同

我们使用基于Pydantic的合约模型来指定数据结构。例如,发票和驾驶证:

    from extract_thinker.models.contract import Contract  
    from pydantic import Field  

    class InvoiceContract(Contract):  
        invoice_number: str = Field(description="唯一的发票编号")  
        invoice_date: str = Field(description="发票日期")  
        total_amount: float = Field(description="总金额")  

    class DriverLicense(Contract):  
        name: str = Field(description="持照人全名")  
        age: int = Field(description="持照人年龄")  
        license_number: str = Field(description="驾照号码")
分类 (fēn lèi)

如果你有多种文件类型,定义分类项。你可以指定具体的分类标准。

  • 每个分类的名称,例如“发票”。
  • 描述内容。
  • 对应的合同。
from extract_thinker import Classification

TEST_CLASSIFICATIONS = [
    Classification(
        name="发票",
        description="这是一张发票单据",
        contract=InvoiceContract
    ),
    Classification(
        name="驾驶证",
        description="这是一张驾驶证单据",
        contract=驾驶证
    )
]
整合一切:本地提取流程

以下,我们创建一个Extractor,它使用我们选择的document_loader工具和一个本地模型(如Ollama、LocalAI等)。接着我们构建一个Process,用于加载、分类、分割和提取内容,形成一个单一的流程。

    import os  
    from dotenv import load_dotenv  

    from extract_thinker import (  
        Extractor,  
        Process,  
        Classification,  
        SplittingStrategy,  
        ImageSplitter,  
        TextSplitter  
    )  

    # 加载环境变量(如果你在 .env 文件中存储了 LLM 端点/API_BASE 等)
    load_dotenv()  

    # 示例多页文档路径
    MULTI_PAGE_DOC_PATH = "path/to/your/multi_page_doc.pdf"  

    def setup_local_process():  
        """  
        辅助函数,用于使用本地 LLM 端点(例如 Ollama、LocalAI、OnPrem.LLM 等)设置 ExtractThinker 过程  
        """  

        # 1) 创建一个 Extractor
        extractor = Extractor()  

        # 2) 连接我们选择的 DocumentLoader(Docling 或 MarkItDown)
        extractor.load_document_loader(document_loader)  

        # 3) 配置你的本地 LLM
        #    对于 Ollama,你可以这样做:
        os.environ["API_BASE"] = "http://localhost:11434"  # 请替换为你的本地端点
        extractor.load_llm("ollama/phi4")  # 或 "ollama/llama3.3" 或你本地的模型  

        # 4) 将 Extractor 连接到每个分类
        TEST_CLASSIFICATIONS[0].extractor = extractor  
        TEST_CLASSIFICATIONS[1].extractor = extractor  

        # 5) 构建 Process
        process = Process()  
        process.load_document_loader(document_loader)  
        return process  

    def run_local_idp_workflow():  
        """  
        使用本地 LLM 加载、分类、拆分和提取多页文档的示例  
        """  
        # 初始化过程
        process = setup_local_process()  

        # (可选) 你可以使用 ImageSplitter(model="ollama/moondream:v2") 进行拆分
        process.load_splitter(TextSplitter(model="ollama/phi4"))  

        # 1) 加载文件
        # 2) 使用 EAGER 策略拆分成子文档
        # 3) 使用我们的 TEST_CLASSIFICATIONS 对每个子文档进行分类
        # 4) 根据匹配的合同(发票或驾驶执照)提取字段
        result = (  
            process  
            .load_file(MULTI_PAGE_DOC_PATH)  
            .split(TEST_CLASSIFICATIONS, strategy=SplittingStrategy.LAZY)  
            .extract(vision=False, completion_strategy=CompletionStrategy.PAGINATE)  
        )  

        # 'result' 是一个包含提取对象(InvoiceContract 或 DriverLicense)的列表
        for item in result:  
            # 打印或存储每个提取的数据模型
            if isinstance(item, InvoiceContract):  
                print("[提取的发票]")  
                print(f"编号: {item.invoice_number}")  
                print(f"日期: {item.invoice_date}")  
                print(f"总计: {item.total_amount}")  
            elif isinstance(item, DriverLicense):  
                print("[提取的驾驶执照]")  
                print(f"姓名: {item.name}, 年龄: {item.age}")  
                print(f"执照编号: {item.license_number}")  

    # 快速测试时,只需调用 run_local_idp_workflow() 函数即可
    if __name__ == "__main__":  
        run_local_idp_workflow()
6. 隐私和PII:云中的大模型(LLM)

注:PII通常指的是个人可识别信息(Personally Identifiable Information,简称PII),在中文语境中,我们通常直接使用“个人可识别信息(PII)”来指代这一概念。

并不是每个组织都愿意或能够运行本地硬件。有些人更喜欢使用基于云的高级LLM。请记住:如果这样的话。

  • 数据隐私风险:将敏感数据发送到云端可能会引发合规风险。
  • GDPR/HIPAA:法规可能完全禁止数据离开您的公司。
  • VPC + 防火墙:您可以在专用网络中隔离云端资源,但这会增加一些复杂性。

注意 : 许多大型语言模型的 API(例如 OpenAI)确实符合 GDPR 要求。但是,如果您受到严格监管或希望轻松更换提供商,您可以考虑使用本地或本地化云的方法。

PII 掩码
一个可靠的方法是构建一个 PII 掩码管道。工具如 Presidio 可以自动检测并遮蔽个人标识符,然后再发送给语言模型。这样,你可以保持模型的灵活性,同时确保符合法规。

图片由作者提供。

7. 最后。 结论。

通过将ExtractThinker本地LLM(如Ollama、LocalAI或OnPrem.LLM)和灵活的文档加载器(Docling或MarkItDown)结合使用,您可以从头开始构建一个安全、本地化的文档智能流程。如果监管要求需要完全隐私或尽量减少对外部的依赖,这样可以确保您的数据保留在内部,同时不损失现代LLM的强大功能。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消