支持视觉大模型的开源PDF解析+OCR工具!Docling本地配置从入门到精通保姆级教程!支持LM Studio+InternVL3-9B与Gemini2.5 Pro轻松识别解析模糊PDF扫描文件

👉👉👉本篇笔记所对应的视频:支持视觉大模型的开源PDF解析+OCR工具!Docling本地配置从入门到精通保姆级教程!支持LM Studio+InternVL3-9B与Gemini2.5_哔哩哔哩_bilibili

在AI浪潮席卷全球的今天,企业和个人都在追问:如何让手头海量的PDF、Word、Excel、网页和图片文档真正变成AI可以理解和利用的知识?答案正悄然诞生--Docling,这款由IBM Research团队主导、开源社区热捧的文档处理神器,正以惊人的速度重塑“文档到AI”的数据通道。

Docling到底是什么?

Docling是一款开源的文档解析与转换工具,它能将各种复杂的文档格式(如PDF、DOCX、XLSX、HTML、图片等)一键解析,自动转化为结构化的JSON、Markdown或HTML格式。这些格式对大语言模型(LLM)和生成式AI来说,简直是“美味佳肴”--它们能直接用来训练、微调AI,或作为知识库支撑智能问答、内容生成等前沿应用。

为什么Docling如此特别?

  • 极致的格式兼容力:无论是多栏排版的年度报告、带有嵌入图片和表格的技术手册,还是扫描版的发票和合同,Docling都能精准识别文本、图片、表格、代码块、数学公式等元素,甚至还能理解页面布局和阅读顺序。
  • 超强的PDF解析能力:PDF一直是AI界的“硬骨头”,因为其内容类型混杂且结构复杂。Docling不仅能把多页表格还原成一个整体,还能识别公式、代码和图片,最大程度保留原始语义和上下文。
  • 统一的文档表达格式:Docling创新性地提出了DoclingDocument格式,无论原始文档来自何种格式,最终都能转换成标准化的结构对象,极大简化了后续AI处理流程。
  • 灵活的导出与本地执行:用户可根据需求选择导出为Markdown、HTML或无损JSON格式。更重要的是,Docling支持本地离线运行,数据隐私和安全性无忧,特别适合处理敏感或内网环境下的企业数据。
  • 与主流AI框架无缝集成:Docling已深度集成LangChain、LlamaIndex、Crew AI、Haystack等热门生成式AI生态,开发者只需几行代码,即可将文档知识注入AI智能体,实现自动问答、内容生成、知识检索等创新场景。

Docling正在如何改变行业?

在知识管理、企业智能、法律合规、技术文档分析等领域,Docling已成为不可或缺的“知识管道”。例如,Red Hat和IBM将Docling集成到RHEL AI和InstructLab平台,帮助企业用自有文档微调大模型,让AI真正“懂业务、懂行业”。开源社区更是热情高涨,Docling在GitHub上星标数飙升,成为AI开发者的新宠。

未来展望:AI与文档的无限可能

Docling的研发团队正持续扩展其能力,未来将支持更复杂的数据类型,如图表、化学结构、业务表单等。想象一下,AI不仅能读懂技术手册,还能自动解析财报图表、识别专利分子结构,企业的知识资产将被彻底激活,成为AI驱动创新的燃料。

Docling不是下一个“文档OCR工具”,而是AI时代的“知识发动机”。它让沉睡在海量文档中的数据焕发新生,助力每一个组织和开发者,轻松跨越“文档到AI”的鸿沟。未来已来,Docling正带你领跑AI知识革命!

🚀安装

pip install litellm google-generativeai docling

🚀命令行

docling <https://arxiv.org/pdf/2206.01062>

🚀基础用法

from docling.document_converter import DocumentConverter

source = "./test/docling.pdf"  # document per local path or URL
output_path = "./output/docling.md"  # 修改为你希望保存的路径

converter = DocumentConverter()
result = converter.convert(source)

markdown_text = result.document.export_to_markdown()

# 保存到本地 Markdown 文件
with open(output_path, "w", encoding="utf-8") as f:
    f.write(markdown_text)

print(f"Markdown 已保存到:{output_path}")

🚀docling+LM Studio

import logging
import os
from pathlib import Path
import requests
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import (
    ApiVlmOptions,
    ResponseFormat,
    VlmPipelineOptions,
)
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.pipeline.vlm_pipeline import VlmPipeline

def check_lm_studio_connection(url="<http://127.0.0.1:1234>", timeout=5):
    """检查LM Studio是否正常运行"""
    try:
        response = requests.get(f"{url}/v1/models", timeout=timeout)
        if response.status_code == 200:
            models = response.json()
            logging.info("LM Studio连接成功")
            return True, models
        else:
            logging.error(f"LM Studio响应异常,状态码: {response.status_code}")
            return False, None
    except Exception as e:
        logging.error(f"无法连接到LM Studio: {e}")
        return False, None

def lm_studio_vlm_options(model: str, prompt: str, timeout: int = 300):
    """配置LM Studio的VLM选项"""
    options = ApiVlmOptions(
        url="<http://localhost:1234/v1/chat/completions>",
        params=dict(
            model=model,
            max_tokens=8192,
            temperature=0.1,
        ),
        prompt=prompt,
        timeout=timeout,
        scale=0.5,  # 可以调整图片缩放比例
        response_format=ResponseFormat.MARKDOWN,
    )
    return options

def process_single_pdf(pdf_path: Path, output_dir: Path, model_name: str = "internvl3-9b"):
    """处理单个PDF文件"""
    logging.info(f"正在处理: {pdf_path.name}")

    # 配置VLM流水线
    pipeline_options = VlmPipelineOptions(
        enable_remote_services=True
    )

    pipeline_options.vlm_options = lm_studio_vlm_options(
        model=model_name,
        prompt="OCR the full page to markdown.",

        #         prompt="""Please accurately extract all text content from this page, including:
# 1. Text content
# 2. Mathematical formulas (in LaTeX format if possible)
# 3. Figure captions and references
# 4. Table content
# 5. Any other relevant information
#
# Format the output in markdown.""",
        timeout=300
    )

    # 创建文档转换器
    doc_converter = DocumentConverter(
        format_options={
            InputFormat.PDF: PdfFormatOption(
                pipeline_options=pipeline_options,
                pipeline_cls=VlmPipeline,
            )
        }
    )

    try:
        # 执行转换
        result = doc_converter.convert(pdf_path)

        # 保存结果
        markdown_content = result.document.export_to_markdown()
        output_file = output_dir / f"{pdf_path.stem}_content.md"

        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(markdown_content)

        logging.info(f"转换完成,结果已保存到: {output_file}")
        return True, output_file

    except Exception as e:
        logging.error(f"处理 {pdf_path.name} 时出错: {e}")
        return False, None

def process_pdf_folder(input_folder: str, output_folder: str = "./output", model_name: str = "internvl3-2b"):
    """处理指定文件夹中的所有PDF文件"""

    # 设置日志
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

    # 检查输入文件夹
    input_path = Path(input_folder)
    if not input_path.exists():
        logging.error(f"输入文件夹不存在: {input_folder}")
        return

    # 创建输出文件夹
    output_path = Path(output_folder)
    output_path.mkdir(parents=True, exist_ok=True)

    # 检查LM Studio连接
    logging.info("=== 检查LM Studio连接 ===")
    success, models = check_lm_studio_connection()
    if not success:
        logging.error("无法连接到LM Studio,请确保:")
        logging.error("1. LM Studio已启动")
        logging.error("2. API服务器已启用")
        logging.error("3. internvl3-2b模型已加载")
        return

    # 查找所有PDF文件
    pdf_files = list(input_path.glob("*.pdf"))
    if not pdf_files:
        logging.warning(f"在 {input_folder} 中未找到PDF文件")
        return

    logging.info(f"找到 {len(pdf_files)} 个PDF文件")

    # 处理每个PDF文件
    success_count = 0
    failed_files = []

    for i, pdf_file in enumerate(pdf_files, 1):
        logging.info(f"\\n=== 处理第 {i}/{len(pdf_files)} 个文件 ===")
        success, output_file = process_single_pdf(pdf_file, output_path, model_name)

        if success:
            success_count += 1
            # 显示部分内容预览
            with open(output_file, 'r', encoding='utf-8') as f:
                content = f.read()
                print(f"\\n--- {pdf_file.name} 转换结果预览 ---")
                print(content[:200] + "..." if len(content) > 200 else content)
        else:
            failed_files.append(pdf_file.name)

    # 输出处理结果统计
    logging.info(f"\\n=== 处理完成 ===")
    logging.info(f"成功处理: {success_count}/{len(pdf_files)} 个文件")
    if failed_files:
        logging.warning(f"失败文件: {', '.join(failed_files)}")

def main():
    """主函数"""
    # 设置输入文件夹 - 修改这里指定你的PDF文件所在位置
    input_folder = "./pdf_files"  # 修改为你的PDF文件夹路径

    # 设置输出文件夹
    output_folder = "./output"  # 结果保存位置

    # 设置模型名称
    model_name = "internvl3-9b"  # 或者其他你在LM Studio中使用的模型名称

    # 处理指定文件夹中的所有PDF
    process_pdf_folder(input_folder, output_folder, model_name)

if __name__ == "__main__":
    main()

🚀docling+Gemini2.5 Pro

import logging
import os
from pathlib import Path
import litellm
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import (
    ApiVlmOptions,
    ResponseFormat,
    VlmPipelineOptions,
)
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.pipeline.vlm_pipeline import VlmPipeline
import threading
import queue
import time

# 创建一个自定义的 API 服务器模拟器
class GeminiAPIServer:
    def __init__(self):
        self.is_running = False
        self.server_thread = None
        self.model_cache = {}

    def start(self):
        """启动 API 服务器"""
        import http.server
        import socketserver
        import json
        from urllib.parse import parse_qs, urlparse

        class CustomHandler(http.server.SimpleHTTPRequestHandler):
            server_obj = self

            def do_POST(self):
                if self.path == '/v1/chat/completions':
                    content_length = int(self.headers['Content-Length'])
                    post_data = self.rfile.read(content_length)

                    try:
                        request_data = json.loads(post_data.decode('utf-8'))
                        response = self.handle_completion(request_data)

                        self.send_response(200)
                        self.send_header('Content-type', 'application/json')
                        self.end_headers()
                        self.wfile.write(json.dumps(response).encode())
                    except Exception as e:
                        self.send_response(500)
                        self.send_header('Content-type', 'application/json')
                        self.end_headers()
                        error_response = {"error": str(e)}
                        self.wfile.write(json.dumps(error_response).encode())
                else:
                    self.send_response(404)
                    self.end_headers()

            def handle_completion(self, request_data):
                model = request_data.get("model", "gemini-2.5-pro-preview-05-06")
                messages = request_data.get("messages", [])

                # 使用 liteLLM 进行实际调用
                try:
                    response = litellm.completion(
                        model=f"gemini/{model}",
                        messages=messages,
                        temperature=request_data.get("temperature", 0.1),
                        max_tokens=request_data.get("max_tokens", 65536)
                    )

                    # 确保响应格式符合 OpenAI 标准
                    return {
                        "id": response.id,
                        "object": "chat.completion",
                        "created": int(time.time()),
                        "model": model,
                        "choices": [
                            {
                                "index": 0,
                                "message": {
                                    "role": "assistant",
                                    "content": response.choices[0].message.content
                                },
                                "finish_reason": "stop"
                            }
                        ],
                        "usage": response.usage.dict() if response.usage else {}
                    }
                except Exception as e:
                    raise e

            def log_message(self, format, *args):
                pass  # 减少日志输出

        def run_server():
            with socketserver.TCPServer(("", 4000), CustomHandler) as httpd:
                httpd.timeout = 1  # 设置超时,便于优雅关闭
                self.httpd = httpd
                while self.is_running:
                    httpd.handle_request()

        self.is_running = True
        self.server_thread = threading.Thread(target=run_server)
        self.server_thread.start()
        time.sleep(2)  # 等待服务器启动

    def stop(self):
        """停止 API 服务器"""
        self.is_running = False
        if self.server_thread:
            self.server_thread.join(timeout=5)
        logging.info("API 服务器已停止")

# 全局 API 服务器实例
api_server = GeminiAPIServer()

def gemini_vlm_options(model: str, prompt: str, timeout: int = 300):
    """配置 Gemini 的 VLM 选项"""
    options = ApiVlmOptions(
        url="<http://localhost:4000/v1/chat/completions>",
        params=dict(
            model=model,
            max_tokens=65536,
            temperature=1,
        ),
        prompt=prompt,
        timeout=timeout,
        scale=1.0, # 图片缩放比例
        response_format=ResponseFormat.MARKDOWN,
    )
    return options

def process_single_pdf(pdf_path: Path, output_dir: Path, model_name: str = "gemini-2.5-pro-preview-05-06"):
    """处理单个PDF文件"""
    logging.info(f"正在处理: {pdf_path.name}")

    # 配置VLM流水线
    pipeline_options = VlmPipelineOptions(
        enable_remote_services=True
    )

    pipeline_options.vlm_options = gemini_vlm_options(
        model=model_name,
        prompt="OCR the full page to markdown.",
        timeout=300
    )

    # 创建文档转换器
    doc_converter = DocumentConverter(
        format_options={
            InputFormat.PDF: PdfFormatOption(
                pipeline_options=pipeline_options,
                pipeline_cls=VlmPipeline,
            )
        }
    )

    try:
        # 执行转换
        result = doc_converter.convert(pdf_path)

        # 保存结果
        markdown_content = result.document.export_to_markdown()
        output_file = output_dir / f"{pdf_path.stem}_content.md"

        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(markdown_content)

        logging.info(f"转换完成,结果已保存到: {output_file}")
        return True, output_file

    except Exception as e:
        logging.error(f"处理 {pdf_path.name} 时出错: {e}")
        return False, None

def process_pdf_folder(input_folder: str, output_folder: str = "./output", model_name: str = "gemini-2.5-pro-preview-05-06"):
    """处理指定文件夹中的所有PDF文件"""

    # 设置日志
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

    # 检查环境变量
    if not os.getenv("GEMINI_API_KEY"):
        logging.error("未设置 GEMINI_API_KEY 环境变量")
        logging.error("请设置你的 Gemini API 密钥: export GEMINI_API_KEY='your-api-key'")
        return

    # 初始化 liteLLM
    os.environ["LITELLM_LOG"] = "ERROR"  # 减少日志输出

    # 启动本地 API 服务器
    logging.info("=== 启动本地 API 服务器 ===")
    try:
        api_server.start()
        logging.info("API 服务器启动成功")
    except Exception as e:
        logging.error(f"无法启动 API 服务器: {e}")
        return

    # 检查输入文件夹
    input_path = Path(input_folder)
    if not input_path.exists():
        logging.error(f"输入文件夹不存在: {input_folder}")
        api_server.stop()
        return

    # 创建输出文件夹
    output_path = Path(output_folder)
    output_path.mkdir(parents=True, exist_ok=True)

    try:
        # 查找所有PDF文件
        pdf_files = list(input_path.glob("*.pdf"))
        if not pdf_files:
            logging.warning(f"在 {input_folder} 中未找到PDF文件")
            return

        logging.info(f"找到 {len(pdf_files)} 个PDF文件")

        # 处理每个PDF文件
        success_count = 0
        failed_files = []

        for i, pdf_file in enumerate(pdf_files, 1):
            logging.info(f"\\n=== 处理第 {i}/{len(pdf_files)} 个文件 ===")
            success, output_file = process_single_pdf(pdf_file, output_path, model_name)

            if success:
                success_count += 1
                # 显示部分内容预览
                with open(output_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                    print(f"\\n--- {pdf_file.name} 转换结果预览 ---")
                    print(content[:200] + "..." if len(content) > 200 else content)
            else:
                failed_files.append(pdf_file.name)

        # 输出处理结果统计
        logging.info(f"\\n=== 处理完成 ===")
        logging.info(f"成功处理: {success_count}/{len(pdf_files)} 个文件")
        if failed_files:
            logging.warning(f"失败文件: {', '.join(failed_files)}")

    finally:
        # 停止服务器
        api_server.stop()

def main():
    """主函数"""
    # 设置输入文件夹
    input_folder = "./pdf_files"  # 修改为你的PDF文件夹路径

    # 设置输出文件夹
    output_folder = "./output"  # 结果保存位置

    # 设置模型名称
    model_name = "gemini-2.5-pro-preview-05-06"  # 使用 Gemini 2.5 Pro Preview

    # 处理指定文件夹中的所有PDF
    process_pdf_folder(input_folder, output_folder, model_name)

if __name__ == "__main__":
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值