公司动态
2026年7月2日 Docker 容器化部署 PaddleOCR-VL 实战(OpenAI 协议兼容 HTTP 调用)
衔接上文https://elowen.blog.csdn.net/article/details/162460343?spm1011.2415.3001.5331本文主要讲解PaddleOCR-VL-1.5/1.6 模型部署并将模型封装成API支持OpenAI1、拉取镜像# 拉取镜像 docker pull ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddle:3.3.1拉取下来后镜像拉取后本地磁盘总占用 6.99GB实际使用1.8G2、启动容器并进入# 启动容器把当前目录挂载到容器的 /paddle docker run --name paddle_vl -it \ -p 8010:8000 \ -v $PWD:/paddle \ ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddle:3.3.1 \ /bin/bash--name paddle_vl容器命名为 paddle_vl-it交互式终端可进入容器操作-p 8010:8000宿主机 8010 端口映射容器 8000-v $PWD:/paddle当前目录挂载到容器/paddle镜像百度云 paddle 3.3.1 镜像/bin/bash启动后进入 bash 命令行3、进入容器内后安装paddleOCR进入容器后补装 PaddleOCR 开发套件[doc-parser]包含了运行 VLM 所需的全部依赖pip install -U paddleocr[doc-parser] #如果下载慢可以用国内镜像 pip install -U paddleocr[doc-parser] python-multipart fastapi uvicorn pydantic pillow numpy -i https://pypi.tuna.tsinghua.edu.cn/simple出现成功的提示即可4、下载模型模型下载在 /paddle/model 下#看paddleocr的框架如果是最新的话就只支持1.6的模型 #modelscope download --model PaddlePaddle/PaddleOCR-VL-1.5 modelscope download --model PaddlePaddle/PaddleOCR-VL-1.6⚠️注意这里有的同学可能会下载到 1.5 的模型目前的 paddle 最新的框架是不支持1.5了必须改用1.65、OpenAI 协议兼容 HTTP 推理服务在目录 /paddle 下我们新建一个文件 paddleocr_vl_server.py 把以下内容写入到文件中 PaddleOCR-VL-1.5 OpenAI-Compatible HTTP Server 兼容 OpenAI Chat Completions API 协议支持多模态图片识别 用法: python paddleocr_vl_server.py --model-path /path/to/PaddleOCR-VL-1.5 --port 8000 import argparse import base64 import json import time import uuid from io import BytesIO from typing import List, Optional, Dict, Any, Union import uvicorn from fastapi import FastAPI, HTTPException, UploadFile, File from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field # # OpenAI API 兼容的数据模型 # class ChatMessageContentPart(BaseModel): 多模态消息内容部分 type: str text: Optional[str] None image_url: Optional[Dict[str, str]] None class ChatMessage(BaseModel): 聊天消息 role: str content: Union[str, List[ChatMessageContentPart]] class ChatCompletionRequest(BaseModel): OpenAI Chat Completions 请求 model: str PaddleOCR-VL-1.5 messages: List[ChatMessage] max_tokens: Optional[int] 4096 temperature: Optional[float] 0.0 top_p: Optional[float] 1.0 stream: Optional[bool] False # PaddleOCR-VL 扩展参数 use_layout_detection: Optional[bool] True layout_model_dir: Optional[str] None class Choice(BaseModel): index: int 0 message: ChatMessage finish_reason: str stop class Usage(BaseModel): prompt_tokens: int 0 completion_tokens: int 0 total_tokens: int 0 class ChatCompletionResponse(BaseModel): OpenAI Chat Completions 响应 id: str object: str chat.completion created: int model: str choices: List[Choice] usage: Usage class ModelInfo(BaseModel): id: str object: str model created: int 0 owned_by: str PaddlePaddle class ModelsResponse(BaseModel): object: str list data: List[ModelInfo] # # 图片处理工具 # def decode_image_from_url(url: str) - bytes: 从 data URL 或网络 URL 解码图片 if url.startswith(data:image): # data:image/png;base64,xxxxxx header, b64 url.split(,, 1) return base64.b64decode(b64) else: # 普通 URL需要下载 import urllib.request with urllib.request.urlopen(url) as resp: return resp.read() def extract_image_from_message(content: Union[str, List[ChatMessageContentPart]]) - Optional[bytes]: 从消息中提取图片二进制数据 if isinstance(content, str): return None for part in content: if part.type image_url and part.image_url: url part.image_url.get(url, ) if url: return decode_image_from_url(url) return None def extract_text_from_message(content: Union[str, List[ChatMessageContentPart]]) - str: 从消息中提取文本 if isinstance(content, str): return content texts [] for part in content: if part.type text and part.text: texts.append(part.text) return \n.join(texts) # # PaddleOCR-VL Pipeline 封装 # class PaddleOCRVLPipeline: PaddleOCR-VL 推理管线封装 def __init__(self, model_path: str, layout_model_path: Optional[str] None): self.model_path model_path self.layout_model_path layout_model_path self.pipeline None self._init_pipeline() def _init_pipeline(self): from paddleocr import PaddleOCRVL kwargs { vl_rec_model_dir: self.model_path, vl_rec_backend: native, } if self.layout_model_path: kwargs[layout_detection_model_dir] self.layout_model_path else: kwargs[use_layout_detection] False self.pipeline PaddleOCRVL(**kwargs) print(f✅ PaddleOCR-VL 模型加载完成: {self.model_path}) def predict(self, image_bytes: bytes, prompt: str 描述这张图片, use_layout_detection: bool True) - str: 对图片进行 OCR 识别返回 Markdown 格式文本 import tempfile import os from PIL import Image import numpy as np # 将 bytes 转为临时文件PaddleOCR-VL 接受文件路径 with tempfile.NamedTemporaryFile(suffix.png, deleteFalse) as f: f.write(image_bytes) img_path f.name try: # 如果启用了版面检测需要在 pipeline 层面控制 if not use_layout_detection: self.pipeline.use_layout_detection False output self.pipeline.predict(img_path) # 组装结果 results [] for res in output: # 优先获取 Markdown 格式输出没有就转字符串 if hasattr(res,markdown): content res.markdown else: # fallback: 获取纯文本 content str(res) if isinstance(content, dict): content str(content) results.append(content) return \n\n.join(results) if results else 未识别到内容 finally: os.unlink(img_path) if not use_layout_detection: self.pipeline.use_layout_detection True # # FastAPI 应用 # app FastAPI( titlePaddleOCR-VL-1.5 API, descriptionOpenAI-compatible API for PaddleOCR-VL-1.5 multimodal OCR, version1.0.0, ) app.add_middleware( CORSMiddleware, allow_origins[*], allow_credentialsTrue, allow_methods[*], allow_headers[*], ) # 全局 pipeline 实例 pipeline: Optional[PaddleOCRVLPipeline] None app.on_event(startup) async def startup(): global pipeline parser argparse.ArgumentParser() parser.add_argument(--model-path, requiredTrue, helpPaddleOCR-VL 模型目录) parser.add_argument(--layout-model-path, defaultNone, help版面分析模型目录) parser.add_argument(--port, typeint, default8000) args, _ parser.parse_known_args() pipeline PaddleOCRVLPipeline(args.model_path, args.layout_model_path) # ---------- /v1/models ---------- app.get(/v1/models, response_modelModelsResponse) async def list_models(): return ModelsResponse( data[ ModelInfo(idPaddleOCR-VL-1.5, owned_byPaddlePaddle), ] ) app.get(/v1/models/{model_id}, response_modelModelInfo) async def get_model(model_id: str): return ModelInfo(idmodel_id, owned_byPaddlePaddle) # ---------- /v1/chat/completions ---------- app.post(/v1/chat/completions, response_modelChatCompletionResponse) async def chat_completions(request: ChatCompletionRequest): global pipeline if pipeline is None: raise HTTPException(status_code500, detail模型未初始化) # 从 messages 中提取最后一张图片和配套文本 image_bytes None user_text # 遍历消息找最后一条含图片的用户消息 for msg in reversed(request.messages): if msg.role user: img extract_image_from_message(msg.content) txt extract_text_from_message(msg.content) if img is not None: image_bytes img user_text txt break elif not user_text: user_text txt if image_bytes is None: raise HTTPException(status_code400, detail请求中未包含图片请使用 image_url 字段提供图片) # 执行推理 try: result_text pipeline.predict( image_bytes, promptuser_text if user_text else 请识别图片中的内容, use_layout_detectionrequest.use_layout_detection, ) except Exception as e: raise HTTPException(status_code500, detailf推理失败: {str(e)}) # 组装 OpenAI 格式响应 response_id fchatcmpl-{uuid.uuid4().hex[:12]} now int(time.time()) return ChatCompletionResponse( idresponse_id, creatednow, modelrequest.model, choices[ Choice( messageChatMessage( roleassistant, contentresult_text, ), finish_reasonstop, ) ], usageUsage( prompt_tokens0, completion_tokens0, total_tokens0, ), ) # ---------- /v1/ocr (PaddleOCR-VL 专属接口额外功能) ---------- class OCRRequest(BaseModel): 直接 OCR 请求接受 base64 图片 image: str # base64 编码的图片 prompt: Optional[str] 请识别图片中的内容 use_layout_detection: Optional[bool] True output_format: Optional[str] markdown # markdown / json / text app.post(/v1/ocr) async def ocr_direct(request: OCRRequest): 直接 OCR 接口接受 base64 图片数据 global pipeline if pipeline is None: raise HTTPException(status_code500, detail模型未初始化) try: image_bytes base64.b64decode(request.image) except Exception: raise HTTPException(status_code400, detail图片 base64 解码失败) try: result_text pipeline.predict( image_bytes, promptrequest.prompt, use_layout_detectionrequest.use_layout_detection, ) return { status: success, result: result_text, format: request.output_format, } except Exception as e: raise HTTPException(status_code500, detailfOCR 失败: {str(e)}) # ---------- /health ---------- app.get(/health) async def health(): return {status: ok, model_loaded: pipeline is not None} # # 入口 # if __name__ __main__: parser argparse.ArgumentParser(descriptionPaddleOCR-VL-1.5 OpenAI-Compatible Server) parser.add_argument(--model-path, requiredTrue, helpPaddleOCR-VL 模型目录路径) parser.add_argument(--layout-model-path, defaultNone, help版面分析模型目录路径) parser.add_argument(--host, default0.0.0.0, help监听地址) parser.add_argument(--port, typeint, default8000, help监听端口) parser.add_argument(--workers, typeint, default1, help工作进程数CPU 建议 1) args parser.parse_args() # 把参数注入到环境变量供 startup 事件读取 import os os.environ[PADDLEOCR_VL_MODEL_PATH] args.model_path if args.layout_model_path: os.environ[PADDLEOCR_VL_LAYOUT_PATH] args.layout_model_path # 重写 startup 让它从环境变量读 app.on_event(startup) async def startup_env(): global pipeline mp os.environ.get(PADDLEOCR_VL_MODEL_PATH) lp os.environ.get(PADDLEOCR_VL_LAYOUT_PATH) or None if mp: pipeline PaddleOCRVLPipeline(mp, lp) uvicorn.run( app, hostargs.host, portargs.port, workersargs.workers, )该脚本完整作用详解这份代码是PaddleOCR-VL-1.5 多模态 OCR 大模型的 OpenAI 协议兼容 HTTP 推理服务核心目标把 Paddle 多模态图文识别模型封装成和 OpenAI ChatGPT 接口格式完全一致的 API方便现有大模型客户端直接调用 OCR 能力。一、核心定位底层能力封装PaddleOCR-VL-1.5多模态 OCR 模型支持图片文字识别、版面分析、文档转 Markdown上层兼容完全对齐 OpenAI/v1/chat/completions标准接口任何支持 OpenAI 协议的工具LangChain、OpenAI SDK、各类 AI 客户端、RAG 系统不用改代码就能接入 OCR额外扩展提供原生/v1/ocr简化接口、健康检查、模型列表接口方便运维与直接传 Base64 图片调用。二、分模块拆解功能1. 数据模型层OpenAI 协议对齐用 Pydantic 严格复刻 OpenAI Chat Completions 请求 / 响应结构支持多模态入参文本 图片 URLbase64 dataURL、网络图片链接兼容 OpenAI 标准字段model、messages、stream、max_tokens、temperature自定义扩展参数use_layout_detection控制是否启用版面分析输出标准 OpenAI 返回体id、choices、assistant回答、usage字段。2. 图片解析工具解析消息内的image_url支持两种图片来源Base64 内嵌图片data:image/png;base64,xxx互联网远程图片 URL自动下载从对话 messages 中自动分离提示文本和图片二进制流自动检索最后一条带图片的用户消息作为识别输入。3. PaddleOCR-VL 推理封装类PaddleOCRVLPipeline启动时加载本地 VL 多模态 OCR 权重 可选版面分析模型接收图片二进制生成临时图片文件传给 PaddleOCR-VL 推理识别完成自动删除临时文件支持开关版面检测优先输出结构化 Markdown 文档文本表格、标题、段落自动排版无 markdown 字段则返回原始识别文本。4. FastAPI HTTP 服务接口标准 OpenAI 兼容接口给通用 AI 客户端用GET /v1/models列出当前可用模型模拟 OpenAI 模型列表接口GET /v1/models/{model_id}查询模型信息POST /v1/chat/completions核心接口接收 OpenAI 格式多模态对话文字 图片自动提取图片与提示词调用 OCR 识别把识别结果包装成 OpenAI 标准返回格式输出。专属简化 OCR 接口业务直接调用POST /v1/ocr轻量化接口直接传 Base64 图片字符串无需套 chat 对话结构适合内部业务系统快速调用支持指定输出格式。运维接口GET /health健康探测返回模型是否加载成功用于容器 / 集群存活检测。5. 服务启动入口命令行参数指定模型路径、版面模型路径、监听端口、主机、多进程通过环境变量传递模型路径给服务启动钩子避免 uvicorn 多进程下参数冲突使用 uvicorn 启动高性能异步 Web 服务默认单进程Paddle 推理不支持多 worker 并发。三、实际使用场景RAG 知识库文档解析LangChain/LlamaIndex 等框架直接调用 OpenAI 接口上传 PDF 截图、票据、试卷、合同自动提取结构化文本通用 AI 客户端对接Chatbox、OpenWebUI 等工具不用二次开发填入该服务地址即可拥有图片 OCR 能力业务系统 OCR 服务后端微服务调用/v1/ocr识别发票、证件、手写文档私有化本地多模态 OCR 部署离线环境部署不依赖第三方云端 OCR完全本地运行。四、核心优势总结协议兼容复用现有 OpenAI 生态工具链无需自定义请求逻辑多模态原生支持遵循 GPT-4V 图片输入规范图文混合提问结构化输出版面分析后自动生成 Markdown保留文档排版双接口设计兼顾标准化 AI 调用与轻量化业务调用容器友好提供健康检查便于 Docker/K8s 集群运维离线私有化全部推理在本地模型完成无数据外传。6、运行进入到容器中启动python /paddle/paddleocr_vl_server.py \ --model-path /paddle/model/PaddleOCR-VL-1.6 \ --host 0.0.0.0 \ --port 8000启动成功后到界面7、调用7.1 远程上传urlpost http://IP:8010/v1/chat/completions{ model: PaddleOCR-VL-1.6, messages: [ { role: user, content: [ { type: text, text: 提取图片全部文字保留排版 }, { type: image_url, image_url: { url: http://ip:10143/api/xinfang-intelligent-analysis/v1/material/preview/2071840161683677185?rj-token*** } } ] } ]返回的结果{ id: chatcmpl-9b3d3ee3ebd0, object: chat.completion, created: 1782870165, model: PaddleOCR-VL-1.6, choices: [ { index: 0, message: { role: assistant, content: {markdown_texts: 我是一个靓仔, markdown_images: {}, page_index: None, input_path: /tmp/tmp75d07ys5.png} }, finish_reason: stop } ], usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } }7.2 本地图片上传post http://192.168.100.210:8010/v1/ocr/uploadEND如果觉得这份基础知识点总结清晰别忘了动动小手点个赞再关注一下呀 后续还会分享更多有关开发问题的干货技巧同时一起解锁更多好用的功能少踩坑多提效 你的支持就是我更新的最大动力咱们下次分享再见呀