[GH-ISSUE #22601] feat: Preprocess PDFs as images for VL models #123071

Closed
opened 2026-05-21 02:15:53 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @ccdv-ai on GitHub (Mar 12, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/22601

Check Existing Issues

  • I have searched for all existing open AND closed issues and discussions for similar requests. I have found none that is comparable to my request.

Verify Feature Scope

  • I have read through and understood the scope definition for feature requests in the Issues section. I believe my feature request meets the definition and belongs in the Issues section instead of the Discussions.

Problem Description

OWUI preprocess PDF using various backend to convert it to text. We should be able to convert a pdf to images (or img + text) for new VL models (e.g qwen 3.5).

Desired Solution you'd like

Add an option to process pdf as images if the model can read images.
DPI (image quality) should be a parameter.

Alternatives Considered

No response

Additional Context

No response

Originally created by @ccdv-ai on GitHub (Mar 12, 2026). Original GitHub issue: https://github.com/open-webui/open-webui/issues/22601 ### Check Existing Issues - [x] I have searched for all existing **open AND closed** issues and discussions for similar requests. I have found none that is comparable to my request. ### Verify Feature Scope - [x] I have read through and understood the scope definition for feature requests in the Issues section. I believe my feature request meets the definition and belongs in the Issues section instead of the Discussions. ### Problem Description OWUI preprocess PDF using various backend to convert it to text. We should be able to convert a pdf to images (or img + text) for new VL models (e.g qwen 3.5). ### Desired Solution you'd like Add an option to process pdf as images if the model can read images. DPI (image quality) should be a parameter. ### Alternatives Considered _No response_ ### Additional Context _No response_
Author
Owner

@rgaricano commented on GitHub (Mar 12, 2026):

You can also use a function/tool ​​similar to this:

"""
name: PDF to Image Converter Tool
description: Convert PDF pages to images for vision model analysis
authos: rgaricano _00_ (I∀I)
version: 0.1.0
required_open_webui_version:
"""

import os
import tempfile
import base64
from typing import List, Dict, Any
from pathlib import Path
from pydantic import BaseModel, Field
import pdf2image
from PIL import Image
import io

class Valves(BaseModel):
    """Admin-configurable settings for the tool"""
    dpi: int = Field(default=200, description="DPI for image conversion")
    image_format: str = Field(default="PNG", description="Image format (PNG, JPEG)")
    max_pages: int = Field(default=10, description="Maximum pages to convert")

class UserValves(BaseModel):
    """User-configurable settings"""
    include_text: bool = Field(default=True, description="Include extracted text with images")

def convert_pdf_to_images(
    pdf_path: str, 
    dpi: int = 200, 
    image_format: str = "PNG",
    max_pages: int = 10
) -> List[Dict[str, Any]]:
    """
    Convert PDF pages to images with metadata.

    Args:
        pdf_path: Path to the PDF file
        dpi: Resolution for converted images
        image_format: Output image format
        max_pages: Maximum number of pages to convert

    Returns:
        List of dictionaries containing image data and metadata
    """
    try:
        # Convert PDF pages to images
        images = pdf2image.convert_from_path(
            pdf_path, 
            dpi=dpi, 
            fmt=image_format.lower(),
            last_page=max_pages
        )

        result = []
        for i, image in enumerate(images):
            # Convert image to base64 for transmission
            buffered = io.BytesIO()
            image.save(buffered, format=image_format)
            img_str = base64.b64encode(buffered.getvalue()).decode()
  
            result.append({
                "page_number": i + 1,
                "image_data": img_str,
                "image_format": image_format.lower(),
                "mime_type": f"image/{image_format.lower()}",
                "width": image.width,
                "height": image.height
            })

        return result

    except Exception as e:
        raise Exception(f"Error converting PDF to images: {str(e)}")

def pdf_to_image_converter(
    pdf_file: str = Field(..., description="Path to the PDF file to convert"),
    __user__: Dict[str, Any] = Field(default_factory=dict, description="User context"),
    __id__: str = Field(default="", description="Tool ID"),
    __event_emitter__: Any = Field(default=None, description="Event emitter for progress updates")
) -> Dict[str, Any]:
    """
    Convert PDF pages to images for vision model analysis.

    This tool takes a PDF file and converts each page into a high-quality image
    that can be processed by vision-capable AI models. Each image includes metadata
    about the page dimensions and format.

    Args:
        pdf_file: Path to the PDF file to convert

    Returns:
        Dictionary containing converted images and metadata
    """

    # Get user valves
    user_valves = __user__.get("valves", {})
    include_text = user_valves.get("include_text", True)

    # Get admin valves (tool configuration)
    # Note: In a real implementation, you'd access these through the tool module
    dpi = 200  # Default, would come from Valves
    image_format = "PNG"  # Default, would come from Valves
    max_pages = 10  # Default, would come from Valves

    # Emit progress update
    if __event_emitter__:
        __event_emitter__({
            "type": "status",
            "data": {"description": "Starting PDF conversion...", "done": False}
        })

    try:
        # Convert PDF to images
        images = convert_pdf_to_images(pdf_file, dpi, image_format, max_pages)

        # Emit completion status
        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": f"Converted {len(images)} pages to images", "done": True}
            })

        return {
            "success": True,
            "total_pages": len(images),
            "images": images,
            "message": f"Successfully converted {len(images)} PDF pages to images"
        }

    except Exception as e:
        if __event_emitter__:
            __event_emitter__({
                "type": "error",
                "data": {"description": f"Error: {str(e)}"}
            })

        return {
            "success": False,
            "error": str(e),
            "message": f"Failed to convert PDF: {str(e)}"
        }
<!-- gh-comment-id:4045070187 --> @rgaricano commented on GitHub (Mar 12, 2026): You can also use a function/tool ​​similar to this: ``` """ name: PDF to Image Converter Tool description: Convert PDF pages to images for vision model analysis authos: rgaricano _00_ (I∀I) version: 0.1.0 required_open_webui_version: """ import os import tempfile import base64 from typing import List, Dict, Any from pathlib import Path from pydantic import BaseModel, Field import pdf2image from PIL import Image import io class Valves(BaseModel): """Admin-configurable settings for the tool""" dpi: int = Field(default=200, description="DPI for image conversion") image_format: str = Field(default="PNG", description="Image format (PNG, JPEG)") max_pages: int = Field(default=10, description="Maximum pages to convert") class UserValves(BaseModel): """User-configurable settings""" include_text: bool = Field(default=True, description="Include extracted text with images") def convert_pdf_to_images( pdf_path: str, dpi: int = 200, image_format: str = "PNG", max_pages: int = 10 ) -> List[Dict[str, Any]]: """ Convert PDF pages to images with metadata. Args: pdf_path: Path to the PDF file dpi: Resolution for converted images image_format: Output image format max_pages: Maximum number of pages to convert Returns: List of dictionaries containing image data and metadata """ try: # Convert PDF pages to images images = pdf2image.convert_from_path( pdf_path, dpi=dpi, fmt=image_format.lower(), last_page=max_pages ) result = [] for i, image in enumerate(images): # Convert image to base64 for transmission buffered = io.BytesIO() image.save(buffered, format=image_format) img_str = base64.b64encode(buffered.getvalue()).decode() result.append({ "page_number": i + 1, "image_data": img_str, "image_format": image_format.lower(), "mime_type": f"image/{image_format.lower()}", "width": image.width, "height": image.height }) return result except Exception as e: raise Exception(f"Error converting PDF to images: {str(e)}") def pdf_to_image_converter( pdf_file: str = Field(..., description="Path to the PDF file to convert"), __user__: Dict[str, Any] = Field(default_factory=dict, description="User context"), __id__: str = Field(default="", description="Tool ID"), __event_emitter__: Any = Field(default=None, description="Event emitter for progress updates") ) -> Dict[str, Any]: """ Convert PDF pages to images for vision model analysis. This tool takes a PDF file and converts each page into a high-quality image that can be processed by vision-capable AI models. Each image includes metadata about the page dimensions and format. Args: pdf_file: Path to the PDF file to convert Returns: Dictionary containing converted images and metadata """ # Get user valves user_valves = __user__.get("valves", {}) include_text = user_valves.get("include_text", True) # Get admin valves (tool configuration) # Note: In a real implementation, you'd access these through the tool module dpi = 200 # Default, would come from Valves image_format = "PNG" # Default, would come from Valves max_pages = 10 # Default, would come from Valves # Emit progress update if __event_emitter__: __event_emitter__({ "type": "status", "data": {"description": "Starting PDF conversion...", "done": False} }) try: # Convert PDF to images images = convert_pdf_to_images(pdf_file, dpi, image_format, max_pages) # Emit completion status if __event_emitter__: __event_emitter__({ "type": "status", "data": {"description": f"Converted {len(images)} pages to images", "done": True} }) return { "success": True, "total_pages": len(images), "images": images, "message": f"Successfully converted {len(images)} PDF pages to images" } except Exception as e: if __event_emitter__: __event_emitter__({ "type": "error", "data": {"description": f"Error: {str(e)}"} }) return { "success": False, "error": str(e), "message": f"Failed to convert PDF: {str(e)}" } ```
Author
Owner

@ccdv-ai commented on GitHub (Mar 12, 2026):

Thank you.
I'm wondering if it is possible to intercept the file and process it with a filter before the conversion. But Im pretty sure the conversion to text cannot be skiped and will be added to prompt anyway.

<!-- gh-comment-id:4045312817 --> @ccdv-ai commented on GitHub (Mar 12, 2026): Thank you. I'm wondering if it is possible to intercept the file and process it with a filter before the conversion. But Im pretty sure the conversion to text cannot be skiped and will be added to prompt anyway.
Author
Owner

@rgaricano commented on GitHub (Mar 12, 2026):

@ccdv-ai
For skip the openwebui file processing core, set the file_handler = True flag (it enables custom file processing)
With a Filter Function:

"""
title: PDF to Image Filter Function
author: rgaricano _00_ (I∀I)
version: 0.1.0
"""

import os
import tempfile
import base64
from typing import Optional, Dict, Any
from pydantic import BaseModel, Field
import pdf2image
from PIL import Image
import io

class Filter:
    class Valves(BaseModel):
        """Admin-configurable settings"""
        enabled: bool = Field(default=True, description="Enable PDF to image conversion")
        dpi: int = Field(default=200, description="DPI for image conversion")
        image_format: str = Field(default="PNG", description="Image format (PNG, JPEG)")
        max_pages: int = Field(default=10, description="Maximum pages to convert")

    class UserValves(BaseModel):
        """User-configurable settings"""
        auto_convert: bool = Field(default=True, description="Automatically convert PDFs to images")

    def __init__(self):
        self.valves = self.Valves()
        # Enable custom file handling to intercept files
        self.file_handler = True

    def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
        """Intercept and process PDF files before content extraction"""
        if not self.valves.enabled:
            return body

        user_valves = __user__.get("valves", {}) if __user__ else {}
        if not user_valves.get("auto_convert", True):
            return body

        # Check if there are files in the request
        files = body.get("files", [])
        if not files:
            return body

        processed_files = []
        added_images = []

        for file_info in files:
            # Process PDF files
            if file_info.get("type") == "application/pdf":
                try:
                    # Convert PDF pages to images
                    images = self._convert_pdf_to_images(file_info)

                    # Add images to the files list
                    for img_data in images:
                        processed_files.append({
                            "name": f"{file_info['name']}_page_{img_data['page_number']}.{img_data['format']}",
                            "type": f"image/{img_data['format']}",
                            "data": img_data["base64_data"],
                            "size": len(img_data["base64_data"])
                        })
                        added_images.append(f"Page {img_data['page_number']}")

                    # Keep the original PDF for text extraction
                    processed_files.append(file_info)

                except Exception as e:
                    # If conversion fails, keep original file
                    processed_files.append(file_info)
            else:
                processed_files.append(file_info)

        # Update the body with processed files
        body["files"] = processed_files

        # Add a message about the conversion
        if added_images:
            messages = body.get("messages", [])
            if messages:
                last_message = messages[-1]
                if last_message.get("role") == "user":
                    content = last_message.get("content", "")
                    content += f"\n\n📄 PDF converted to images: {', '.join(added_images)}"
                    last_message["content"] = content

        return body

    def _convert_pdf_to_images(self, file_info: dict) -> list:
        """Convert PDF file to images"""
        # Decode base64 file data
        file_data = base64.b64decode(file_info["data"].split(",")[1])

        # Save to temporary file
        with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_file:
            tmp_file.write(file_data)
            tmp_path = tmp_file.name

        try:
            # Convert PDF to images
            images = pdf2image.convert_from_path(
                tmp_path,
                dpi=self.valves.dpi,
                fmt=self.valves.image_format.lower(),
                last_page=self.valves.max_pages
            )

            result = []
            for i, image in enumerate(images):
                # Convert image to base64
                buffered = io.BytesIO()
                image.save(buffered, format=self.valves.image_format)
                img_str = base64.b64encode(buffered.getvalue()).decode()

                result.append({
                    "page_number": i + 1,
                    "format": self.valves.image_format.lower(),
                    "base64_data": f"data:image/{self.valves.image_format.lower()};base64,{img_str}"
                })

            return result

        finally:
            # Clean up temporary file
            os.unlink(tmp_path)

    def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
        """Post-process response if needed"""
        return body
<!-- gh-comment-id:4047430178 --> @rgaricano commented on GitHub (Mar 12, 2026): @ccdv-ai For skip the openwebui file processing core, set the `file_handler = True` flag (it enables custom file processing) With a Filter Function: ``` """ title: PDF to Image Filter Function author: rgaricano _00_ (I∀I) version: 0.1.0 """ import os import tempfile import base64 from typing import Optional, Dict, Any from pydantic import BaseModel, Field import pdf2image from PIL import Image import io class Filter: class Valves(BaseModel): """Admin-configurable settings""" enabled: bool = Field(default=True, description="Enable PDF to image conversion") dpi: int = Field(default=200, description="DPI for image conversion") image_format: str = Field(default="PNG", description="Image format (PNG, JPEG)") max_pages: int = Field(default=10, description="Maximum pages to convert") class UserValves(BaseModel): """User-configurable settings""" auto_convert: bool = Field(default=True, description="Automatically convert PDFs to images") def __init__(self): self.valves = self.Valves() # Enable custom file handling to intercept files self.file_handler = True def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict: """Intercept and process PDF files before content extraction""" if not self.valves.enabled: return body user_valves = __user__.get("valves", {}) if __user__ else {} if not user_valves.get("auto_convert", True): return body # Check if there are files in the request files = body.get("files", []) if not files: return body processed_files = [] added_images = [] for file_info in files: # Process PDF files if file_info.get("type") == "application/pdf": try: # Convert PDF pages to images images = self._convert_pdf_to_images(file_info) # Add images to the files list for img_data in images: processed_files.append({ "name": f"{file_info['name']}_page_{img_data['page_number']}.{img_data['format']}", "type": f"image/{img_data['format']}", "data": img_data["base64_data"], "size": len(img_data["base64_data"]) }) added_images.append(f"Page {img_data['page_number']}") # Keep the original PDF for text extraction processed_files.append(file_info) except Exception as e: # If conversion fails, keep original file processed_files.append(file_info) else: processed_files.append(file_info) # Update the body with processed files body["files"] = processed_files # Add a message about the conversion if added_images: messages = body.get("messages", []) if messages: last_message = messages[-1] if last_message.get("role") == "user": content = last_message.get("content", "") content += f"\n\n📄 PDF converted to images: {', '.join(added_images)}" last_message["content"] = content return body def _convert_pdf_to_images(self, file_info: dict) -> list: """Convert PDF file to images""" # Decode base64 file data file_data = base64.b64decode(file_info["data"].split(",")[1]) # Save to temporary file with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_file: tmp_file.write(file_data) tmp_path = tmp_file.name try: # Convert PDF to images images = pdf2image.convert_from_path( tmp_path, dpi=self.valves.dpi, fmt=self.valves.image_format.lower(), last_page=self.valves.max_pages ) result = [] for i, image in enumerate(images): # Convert image to base64 buffered = io.BytesIO() image.save(buffered, format=self.valves.image_format) img_str = base64.b64encode(buffered.getvalue()).decode() result.append({ "page_number": i + 1, "format": self.valves.image_format.lower(), "base64_data": f"data:image/{self.valves.image_format.lower()};base64,{img_str}" }) return result finally: # Clean up temporary file os.unlink(tmp_path) def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict: """Post-process response if needed""" return body ```
Author
Owner

@JiwaniZakir commented on GitHub (Mar 13, 2026):

I can pick this up. The PDF processing logic lives in the retrieval pipeline where documents get chunked and embedded — we'd need to add a rendering path using something like pdf2image (poppler-based) to convert each page into a base64-encoded image before passing it to the chat completion payload. The DPI parameter could slot into the document settings alongside the existing chunk size/overlap config. I'll put together a PR.

<!-- gh-comment-id:4051546902 --> @JiwaniZakir commented on GitHub (Mar 13, 2026): I can pick this up. The PDF processing logic lives in the retrieval pipeline where documents get chunked and embedded — we'd need to add a rendering path using something like pdf2image (poppler-based) to convert each page into a base64-encoded image before passing it to the chat completion payload. The DPI parameter could slot into the document settings alongside the existing chunk size/overlap config. I'll put together a PR.
Author
Owner

@rgaricano commented on GitHub (Mar 13, 2026):

@ccdv-ai,
I debugged & made some changes, tested and working with qwen3.5_9b

"""
title: PDF to Image Filter Function
author: rgaricano _00_ (I∀I)
version: 0.1.1
OpenWebUI version: 0.8.10
"""

import os
import tempfile
import base64
from typing import Optional, Dict, Any
from pydantic import BaseModel, Field
import pdf2image
from PIL import Image
import io


class Filter:
    class Valves(BaseModel):
        """Admin-configurable settings"""

        enabled: bool = Field(
            default=True, description="Enable PDF to image conversion"
        )
        dpi: int = Field(default=200, description="DPI for image conversion")
        image_format: str = Field(default="PNG", description="Image format (PNG, JPEG)")
        max_pages: int = Field(default=10, description="Maximum pages to convert")

    class UserValves(BaseModel):
        """User-configurable settings"""

        auto_convert: bool = Field(
            default=True, description="Automatically convert PDFs to images"
        )

    def __init__(self):
        self.valves = self.Valves()
        # Enable custom file handling to intercept files

    #        self.file_handler = True

    def inlet(
        self, body: dict, __user__: Optional[dict] = None, __event_emitter__: Any = None
    ) -> dict:
        """Intercept and process PDF files before content extraction"""
        print(f"[PDF2IMG] Filter inlet called")

        if not self.valves.enabled:
            return body

        user_valves = __user__.get("valves") if __user__ else None
        if user_valves and not user_valves.auto_convert:
            return body

        files = body.get("files", [])
        if not files:
            return body

        processed_files = []
        added_images = []

        for file_info in files:
            if file_info.get("content_type") == "application/pdf":
                try:
                    print(f"[PDF2IMG] Processing PDF: {file_info.get('name')}")

                    # Convert PDF to images
                    images = self._convert_pdf_to_images(file_info)

                    # Add images to the files list
                    for img_data in images:
                        processed_files.append(
                            {
                                "name": f"{file_info['name']}_page_{img_data['page_number']}.{img_data['format']}",
                                "type": f"image/{img_data['format']}",
                                "data": img_data["base64_data"],
                                "size": len(img_data["base64_data"]),
                            }
                        )
                        added_images.append(f"Page {img_data['page_number']}")

                    # Keep the original PDF for text extraction
                    processed_files.append(file_info)

                except Exception as e:
                    print(f"[PDF2IMG] Error converting PDF: {str(e)}")
                    processed_files.append(file_info)
            else:
                processed_files.append(file_info)

        # Update the body with processed files
        body["files"] = processed_files

        # CRITICAL: Add images to message content for vision model
        if added_images:
            messages = body.get("messages", [])
            if messages:
                last_message = messages[-1]
                if last_message.get("role") == "user":
                    content = last_message.get("content", "")

                    # Convert content to list format if needed
                    if isinstance(content, str):
                        content = [{"type": "text", "text": content}]
                    elif not isinstance(content, list):
                        content = [{"type": "text", "text": str(content)}]

                    # Add image URLs to content
                    for file_info in processed_files:
                        if file_info.get("type", "").startswith("image/"):
                            content.append(
                                {
                                    "type": "image_url",
                                    "image_url": {"url": file_info["data"]},
                                }
                            )

                    last_message["content"] = content

                    # Add text notification
                    if content and content[0].get("type") == "text":
                        content[0][
                            "text"
                        ] += (
                            f"\n\n📄 PDF converted to images: {', '.join(added_images)}"
                        )

        return body

    def _convert_pdf_to_images(
        self, file_info: dict, __event_emitter__: Any = None
    ) -> list:
        """Convert PDF file to images"""
        print(f"[PDF2IMG] Starting conversion for {file_info.get('name')}")

        # FIXED: Read file directly from disk instead of decoding base64
        file_path = file_info["file"]["path"]

        try:
            # Convert PDF to images
            images = pdf2image.convert_from_path(
                file_path,
                dpi=self.valves.dpi,
                fmt=self.valves.image_format.lower(),
                last_page=self.valves.max_pages,
            )

            print(f"[PDF2IMG] Converted {len(images)} pages to images")

            result = []
            for i, image in enumerate(images):
                # Emit progress for each page
                if __event_emitter__:
                    __event_emitter__(
                        {
                            "type": "status",
                            "data": {
                                "description": f"Processing page {i+1}/{len(images)}",
                                "done": False,
                            },
                        }
                    )

                # Convert image to base64
                buffered = io.BytesIO()
                image.save(buffered, format=self.valves.image_format)
                img_str = base64.b64encode(buffered.getvalue()).decode()

                result.append(
                    {
                        "page_number": i + 1,
                        "format": self.valves.image_format.lower(),
                        "base64_data": f"data:image/{self.valves.image_format.lower()};base64,{img_str}",
                    }
                )

            return result

        except Exception as e:
            print(f"[PDF2IMG] Error in pdf2image conversion: {str(e)}")
            raise

    def outlet(
        self, body: dict, __user__: Optional[dict] = None, __event_emitter__: Any = None
    ) -> dict:
        """Post-process response if needed"""
        print(f"[PDF2IMG] Filter outlet called")
        return body
<!-- gh-comment-id:4054974960 --> @rgaricano commented on GitHub (Mar 13, 2026): @ccdv-ai, I debugged & made some changes, tested and working with qwen3.5_9b ``` """ title: PDF to Image Filter Function author: rgaricano _00_ (I∀I) version: 0.1.1 OpenWebUI version: 0.8.10 """ import os import tempfile import base64 from typing import Optional, Dict, Any from pydantic import BaseModel, Field import pdf2image from PIL import Image import io class Filter: class Valves(BaseModel): """Admin-configurable settings""" enabled: bool = Field( default=True, description="Enable PDF to image conversion" ) dpi: int = Field(default=200, description="DPI for image conversion") image_format: str = Field(default="PNG", description="Image format (PNG, JPEG)") max_pages: int = Field(default=10, description="Maximum pages to convert") class UserValves(BaseModel): """User-configurable settings""" auto_convert: bool = Field( default=True, description="Automatically convert PDFs to images" ) def __init__(self): self.valves = self.Valves() # Enable custom file handling to intercept files # self.file_handler = True def inlet( self, body: dict, __user__: Optional[dict] = None, __event_emitter__: Any = None ) -> dict: """Intercept and process PDF files before content extraction""" print(f"[PDF2IMG] Filter inlet called") if not self.valves.enabled: return body user_valves = __user__.get("valves") if __user__ else None if user_valves and not user_valves.auto_convert: return body files = body.get("files", []) if not files: return body processed_files = [] added_images = [] for file_info in files: if file_info.get("content_type") == "application/pdf": try: print(f"[PDF2IMG] Processing PDF: {file_info.get('name')}") # Convert PDF to images images = self._convert_pdf_to_images(file_info) # Add images to the files list for img_data in images: processed_files.append( { "name": f"{file_info['name']}_page_{img_data['page_number']}.{img_data['format']}", "type": f"image/{img_data['format']}", "data": img_data["base64_data"], "size": len(img_data["base64_data"]), } ) added_images.append(f"Page {img_data['page_number']}") # Keep the original PDF for text extraction processed_files.append(file_info) except Exception as e: print(f"[PDF2IMG] Error converting PDF: {str(e)}") processed_files.append(file_info) else: processed_files.append(file_info) # Update the body with processed files body["files"] = processed_files # CRITICAL: Add images to message content for vision model if added_images: messages = body.get("messages", []) if messages: last_message = messages[-1] if last_message.get("role") == "user": content = last_message.get("content", "") # Convert content to list format if needed if isinstance(content, str): content = [{"type": "text", "text": content}] elif not isinstance(content, list): content = [{"type": "text", "text": str(content)}] # Add image URLs to content for file_info in processed_files: if file_info.get("type", "").startswith("image/"): content.append( { "type": "image_url", "image_url": {"url": file_info["data"]}, } ) last_message["content"] = content # Add text notification if content and content[0].get("type") == "text": content[0][ "text" ] += ( f"\n\n📄 PDF converted to images: {', '.join(added_images)}" ) return body def _convert_pdf_to_images( self, file_info: dict, __event_emitter__: Any = None ) -> list: """Convert PDF file to images""" print(f"[PDF2IMG] Starting conversion for {file_info.get('name')}") # FIXED: Read file directly from disk instead of decoding base64 file_path = file_info["file"]["path"] try: # Convert PDF to images images = pdf2image.convert_from_path( file_path, dpi=self.valves.dpi, fmt=self.valves.image_format.lower(), last_page=self.valves.max_pages, ) print(f"[PDF2IMG] Converted {len(images)} pages to images") result = [] for i, image in enumerate(images): # Emit progress for each page if __event_emitter__: __event_emitter__( { "type": "status", "data": { "description": f"Processing page {i+1}/{len(images)}", "done": False, }, } ) # Convert image to base64 buffered = io.BytesIO() image.save(buffered, format=self.valves.image_format) img_str = base64.b64encode(buffered.getvalue()).decode() result.append( { "page_number": i + 1, "format": self.valves.image_format.lower(), "base64_data": f"data:image/{self.valves.image_format.lower()};base64,{img_str}", } ) return result except Exception as e: print(f"[PDF2IMG] Error in pdf2image conversion: {str(e)}") raise def outlet( self, body: dict, __user__: Optional[dict] = None, __event_emitter__: Any = None ) -> dict: """Post-process response if needed""" print(f"[PDF2IMG] Filter outlet called") return body ```
Author
Owner

@frenzybiscuit commented on GitHub (Mar 13, 2026):

I'd also like to see this natively supported in OWUI.

<!-- gh-comment-id:4055716758 --> @frenzybiscuit commented on GitHub (Mar 13, 2026): I'd also like to see this natively supported in OWUI.
Author
Owner

@frenzybiscuit commented on GitHub (Mar 13, 2026):

Though, the tools above likely work fine. It would still be nice to see native support.

<!-- gh-comment-id:4055718677 --> @frenzybiscuit commented on GitHub (Mar 13, 2026): Though, the tools above likely work fine. It would still be nice to see native support.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#123071