利用 Docling、Ollama、Phi-4 | ExtractThinker 构建企业内部文档智能堆栈
- Rifx.Online
- Finance , Technology , Data Science
- 19 Jan, 2025
在这个大型语言模型(LLM)的新时代,银行和金融机构在某种程度上处于劣势,因为前沿模型由于其硬件要求几乎无法在本地使用。然而,银行数据的敏感性带来了显著的隐私问题,尤其是当这些模型仅作为云服务提供时。为了解决这些挑战,组织可以转向**本地或小型语言模型(SLM)**设置,以将数据保留在内部,避免敏感信息的潜在泄露。这种方法使您能够利用先进的LLM(本地或通过最少的外部调用),同时确保严格遵守GDPR、HIPAA或各种金融指令等法规。
本文展示了如何通过结合以下内容构建一个完全本地的文档智能解决方案:
- ExtractThinker — 一个开源框架,用于协调OCR、分类和数据提取管道,以支持LLM
- Ollama — 一个本地部署解决方案,适用于Phi-4或Llama 3.x等语言模型
- Docling 或 MarkItDown — 灵活的库,用于处理文档加载、OCR和布局解析
无论您是在严格的保密规则下操作,处理扫描的PDF,还是仅仅想要先进的基于视觉的提取,这个端到端的堆栈都提供了一个完全在您自己基础设施内的安全、高性能管道。
1. 选择合适的模型(文本与视觉)
在构建文档智能堆栈时,首先要决定您需要仅文本模型还是具备视觉能力的模型。仅文本模型通常更适合本地解决方案,因为它们广泛可用且限制较少。然而,具备视觉能力的模型在高级分割任务中可能至关重要,特别是当文档依赖于视觉线索时——例如布局、配色方案或独特格式。
在某些情况下,您可以为不同阶段配对不同的模型。例如,一个较小的 moondream 模型(0.5B 参数)可能负责分割,而 Phi-4 14B 模型则管理分类和提取。许多大型机构更倾向于部署单一、更强大的模型(例如,70B 范围内的 Llama 3.3 或 Qwen 2.5)以覆盖所有用例。如果您只需要以英语为中心的 IDP,您可以简单地使用Phi4来处理大多数任务,并保持一个轻量级的 moondream 模型以应对边缘案例分割。这一切都取决于您的具体需求和可用基础设施。
2. 处理文档:MarkItDown 与 Docling
对于文档解析,有两个流行的库脱颖而出:
MarkItDown
- 更简单、直接,广泛支持微软
- 非常适合直接的基于文本的任务,无需多个OCR引擎
- 易于安装和集成
Docling
- 更高级,支持多种OCR(Tesseract、AWS Textract、Google Document AI等)
- 非常适合扫描工作流程或从图像PDF中进行强大的提取
- 详细的文档,灵活适应复杂布局
ExtractThinker 让您可以根据需求选择 DocumentLoaderMarkItDown
或 DocumentLoaderDocling
,无论是简单的数字PDF还是多引擎OCR。
3. 运行本地模型
虽然 Ollama 是一个流行的本地托管 LLM 的工具,但现在有 几种解决方案 可用于与 ExtractThinker 无缝集成的本地部署:
- LocalAI — 一个开源平台,可以在本地模拟 OpenAI 的 API。它可以在消费级硬件(甚至仅 CPU)上运行 LLM,比如 Llama 2 或 Mistral,并提供一个简单的端点进行连接。
- OpenLLM — BentoML 的一个项目,通过与 OpenAI 兼容的 API 暴露 LLM。它针对吞吐量和低延迟进行了优化,适用于本地和云端,并支持多种开源 LLM。
- Llama.cpp — 一种更低级别的方法,用于运行 Llama 模型,并具有高级自定义配置。适合细粒度控制或高性能计算设置,尽管管理上更复杂。
Ollama 通常是首选,因为它易于设置且 CLI 简单。然而,对于企业或高性能计算场景,Llama.cpp 服务器部署、OpenLLM 或像 LocalAI 这样的解决方案可能更为合适。所有这些都可以通过简单地将本地 LLM 端点指向代码中的环境变量或基础 URL 来与 ExtractThinker 集成。
4. 处理小上下文窗口
在使用具有有限上下文窗口的本地模型(例如,~8K tokens 或更少)时,管理这两者变得至关重要:
拆分文档
为了避免超过模型的输入容量,懒惰拆分是理想的选择。与其一次性处理整个文档:
- 你逐步比较页面(例如,页面 1–2,然后 2–3),决定它们是否属于同一子文档。
- 如果是,你将它们保留在一起以便下一步。如果不是,你开始一个新段落。
- 这种方法对内存友好,并通过一次只加载和分析几页来适应非常大的 PDF。
注意: 当你有更高的令牌限制时,连接是理想的选择;在窗口有限的情况下,分页更为优先。
处理部分响应
对于较小的本地模型,如果提示较大,每个响应也有被截断的风险。PaginationHandler 优雅地解决了这个问题:
- 将文档的页面拆分为单独的请求(每个请求一页)。
- 在最后合并页面级结果,如果页面在某些字段上存在分歧,则可选择进行冲突解决。
注意: 当您有更高的令牌限制时,连接是理想的;对于有限的窗口,分页是首选。
快速示例流程
- 懒惰分割 PDF,使每个块/页面保持在模型限制之下。
- 分页:每个块的结果单独返回。
- 合并 部分页面结果为最终结构化数据。
这种最小化的方法确保您永远不会超过本地模型的上下文窗口——无论是在喂入 PDF 的方式上,还是在处理多页响应时。
5. ExtractThinker: 构建堆栈
下面是一个最小的代码片段,展示如何集成这些组件。首先,安装 ExtractThinker:
pip install extract-thinker
文档加载器
如上所述,我们可以使用 MarkitDown 或 Docling。
from extract_thinker import DocumentLoaderMarkItDown, DocumentLoaderDocling
## DocumentLoaderDocling or DocumentLoaderMarkItDown
document_loader = DocumentLoaderDocling()
定义合同
我们使用 Pydantic-based Contracts 来指定我们想要提取的数据结构。例如,发票和驾驶执照:
from extract_thinker.models.contract import Contract
from pydantic import Field
class InvoiceContract(Contract):
invoice_number: str = Field(description="Unique invoice identifier")
invoice_date: str = Field(description="Date of the invoice")
total_amount: float = Field(description="Overall total amount")
class DriverLicense(Contract):
name: str = Field(description="Full name on the license")
age: int = Field(description="Age of the license holder")
license_number: str = Field(description="License number")
分类
如果您有多个文档类型,请定义 Classification 对象。您可以指定:
- 每个分类的名称(例如,“发票”)。
- 描述。
- 它映射到的合同。
from extract_thinker import Classification
TEST_CLASSIFICATIONS = [
Classification(
name="Invoice",
description="This is an invoice document",
contract=InvoiceContract
),
Classification(
name="Driver License",
description="This is a driver license document",
contract=DriverLicense
)
]
整合所有内容:本地提取过程
下面,我们创建一个 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) 将提取器附加到每个分类
TEST_CLASSIFICATIONS[0].extractor = extractor
TEST_CLASSIFICATIONS[1].extractor = extractor
# 5) 构建过程
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. 隐私和个人身份信息:云中的大型语言模型
并非每个组织都能——或想要——运行本地硬件。有些组织更喜欢先进的基于云的大型语言模型。如果是这样,请记住:
- 数据隐私风险:将敏感数据发送到云端可能会引发合规性问题。
- GDPR/HIPAA:法规可能会限制数据完全离开您的场所。
- VPC + 防火墙:您可以在私有网络中隔离云资源,但这会增加复杂性。
注意*:许多大型语言模型API(例如,OpenAI)确实提供GDPR合规性。但如果您受到严格监管或希望能够轻松切换提供商,请考虑本地或掩码云的方法。*
个人身份信息掩码一个强有力的方法是建立一个个人身份信息掩码管道。像Presidio这样的工具可以在发送到大型语言模型之前自动检测和删除个人标识符。这样,您可以保持与模型无关,同时保持合规性。
7. 结论
通过将 ExtractThinker 与 本地 LLM(如 Ollama、LocalAI 或 OnPrem.LLM)以及灵活的 DocumentLoader(Docling 或 MarkItDown)结合起来,您可以从零开始构建一个 安全的本地文档智能工作流程。如果监管要求需要完全隐私或最小的外部调用,这个堆栈可以在不牺牲现代 LLM 功能的情况下,将您的数据保留在内部。