Skip to content

bug(openai-compatible): historical image serialization failures across chat roles and completion mode #387

Open

Description

@WH-2099

Summary

Track the historical multimodal serialization failures investigated for COM-13644 and the remaining work needed to verify that community report.
Merged fixes: #386 for chat conversion and #411 for explicit completion-mode input handling.
SDK 0.11.0 publication is tracked in #413; #412 remains a draft response-handling follow-up.
This issue stays open for plugin delivery and verification of the original report in Dify 1.19.0.

The SDK's OpenAI-compatible chat converter historically converted TEXT/IMAGE/VIDEO content parts only for user messages.
For system, assistant, and configured tool messages it passed Python content objects into the HTTP payload, causing TypeError: Object of type ImagePromptMessageContent is not JSON serializable before the request reaches the server.
vLLM inherits this converter; the official OpenAI API Compatible plugin delegates non-user chat messages to it.

A separate existing path produces the same error: mode=completion places prompt_messages[0].content directly into prompt, bypassing the chat converter even when the image is in a user message.
The error text alone therefore does not identify which path caused the community report.

Historical evidence

  • The user-only conversion is present in the original Dify Model Runtime implementation from January 2024 and the SDK implementation from September 2024.
  • langgenius/dify-official-plugins#1118 contains a human analysis identifying system image variables as the trigger for the same exception.
  • Dify 1.10.0 file-variable construction preserves the configured message role; ordinary Vision/sys_files inputs take a separate user-message path.
  • Published SDK 0.7.4, 0.10.0, and 0.10.2 contain the affected code paths.
    Function-level comparisons covered 48 role/mode/stream combinations, using exact functions extracted from those wheels with current SDK entities/helpers; these were not full historical runtime environments.

Reproduction

The following reproduces both paths with released SDK 0.10.2 and requires no API key or running model server.
Save it as repro.py and run uv run --isolated --no-project --with dify-plugin==0.10.2 python repro.py.
The HTTP method is mocked in the completion check, so no request is sent.

import json
from unittest.mock import patch

from dify_plugin import OAICompatLargeLanguageModel
from dify_plugin.entities.model.message import (
    AssistantPromptMessage,
    ImagePromptMessageContent,
    SystemPromptMessage,
    ToolPromptMessage,
    UserPromptMessage,
)

llm = OAICompatLargeLanguageModel([])
image = ImagePromptMessageContent(
    url="https://example.com/image.png", format="png", mime_type="image/png"
)
credentials = {"function_calling_type": "tool_call"}
messages = [
    UserPromptMessage(content=[image]),
    SystemPromptMessage(content=[image]),
    AssistantPromptMessage(content=[image]),
    ToolPromptMessage(content=[image], tool_call_id="call-1"),
]
for message in messages:
    try:
        json.dumps(llm._convert_prompt_message_to_dict(message, credentials))
        print(message.role.value, "serialized")
    except TypeError as error:
        print(message.role.value, type(error).__name__, str(error))

with patch("dify_plugin.interfaces.model.openai_compatible.llm.requests.post") as post:
    try:
        llm._generate(
            "model",
            {"mode": "completion", "endpoint_url": "https://example.com/v1/"},
            [messages[0]],
            {},
            stream=False,
        )
    except TypeError as error:
        print("completion/user", type(error).__name__, str(error))
    post.assert_not_called()
Case Released SDK 0.10.2 SDK main after #411
Chat, user image JSON encoding succeeds Succeeds
Chat, system/assistant/tool image Image object JSON TypeError JSON encoding succeeds through #386
Completion, user image Image object JSON TypeError Public invocation raises an explicit modality error before HTTP through #411
Completion, text content blocks SDK objects reach request serialization Ordered text is joined into the first prompt through #411

Expected behavior: encode supported chat content into the provider's wire format while preserving roles and metadata.
If an input modality is unsupported by a configured API mode, handle that explicitly at the mode/capability boundary rather than leaking a Python JSON serialization exception.
This does not imply that the legacy completions API accepts Chat Completions image arrays, or that every chat endpoint/template supports images in every role.

Community report and unresolved attribution

Reported environment: Dify 1.10.0, vLLM server 0.26.0, vLLM plugin 0.2.4, OpenAI API Compatible 0.0.64, model reported as Qwen3.8-27B.
The reporter confirmed text calls work and image calls fail through both plugins.
We have not obtained the original attachment, full traceback, workflow DSL, configured mode, or actual message roles for either failing call.

The publicly verified vLLM plugin artifact is 0.2.3, which pins SDK 0.7.4; the reported 0.2.4 artifact and installation source remain unverified.
The official compatible 0.0.64 source specifies SDK >=0.10.0 and locks 0.10.2.
These inspected sources do not establish the exact dependencies inside the reporter's installed packages.

The pinned official Qwen3.8-27B chat template rejects system images/video.
The reporter's actual model revision, template, and launch configuration are unknown, and JSON serialization success alone cannot establish successful model inference.

Current fix and follow-up — 2026-11-02

#386 merged on September 11 and converts supported chat content after preserving message roles and metadata.
#411 merged on October 31 and handles completion input at the mode boundary: text blocks are joined in order, while media raises InvokeError through public invocation before HTTP.
It also fixes missing-usage estimates and forwards tools through response hooks.
Its Python 3.12/3.13 CI passed 1,750 tests on each version.

#412 remains a draft follow-up for complete tool-schema estimation, empty finish markers, and daemon-decodable tool-only response text.
Its current checks pass, including Python 3.12/3.13 CI; local validation reports 1,769 tests and a passing build.
These are implementation and controlled transport checks, with original deployment verification still outstanding.

Target product release: Dify 1.19.0.
Successful serialization does not establish that the selected model/template accepts an image in that role.
Keep API keys, authorization headers, private endpoints, and sensitive workflow/image data out of the public issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions