HKUDS/DeepTutor · error · GenerationProviderError

Image response had no image in the assistant message.

Error message

Image response had no image in the assistant message.

What it means

_extract_sources walked the assistant message content parts but found none whose image_url.url / url started with data:image or http, so the sources list is empty. This fires even though the response contained a message — the message simply had no recognizable inline image.

Solutions

  1. Log the raw response JSON and identify where the image reference actually lives
  2. If the field differs, normalize upstream or use a provider/adapter matching the response shape (e.g. the /images/generations adapter)
  3. Ensure the model actually emitted an image and not a text refusal
Defensive patterns

Strategy: try-catch

Type guard

def has_recognized_image_source(part: dict) -> bool:
    src = (part.get("image_url") or {}).get("url") or part.get("url")
    return isinstance(src, str) and src.startswith(("data:image", "http"))

Try / catch

try:
    sources = adapter._extract_sources(resp)
except GenerationProviderError:
    logger.warning("unrecognized image shape: %s", resp.json())
    raise

Prevention

When it happens

Trigger: The model returns images as markdown links, a different field name, or a URL scheme not starting with http/data:image; content parts exist but are text-only; the provider wraps image data in a proprietary shape.

Common situations: Non-OpenAI-compatible gateways returning vendor-specific image fields; model returning a relative URL or non-image content type; response shape drift after a provider API version change.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/fcda2c358daea6de. Report an issue: GitHub.

Appendix: source

Thrown at deeptutor/services/imagegen/adapters/chat_completions.py:96

        for choice in choices or []:
            message = (choice or {}).get("message") or {}
            for image in message.get("images") or []:
                if not isinstance(image, dict):
                    continue
                src = (image.get("image_url") or {}).get("url") or image.get("url")
                if isinstance(src, str) and src:
                    sources.append(src)
            # Fallback: some variants nest images in the content parts array.
            content = message.get("content")
            if isinstance(content, list):
                for part in content:
                    if not isinstance(part, dict):
                        continue
                    src = (part.get("image_url") or {}).get("url") or part.get("url")
                    if isinstance(src, str) and src.startswith(("data:image", "http")):
                        sources.append(src)
        if not sources:
            raise GenerationProviderError("Image response had no image in the assistant message.")
        return sources

    async def _materialize(self, client: httpx.AsyncClient, src: str) -> tuple[bytes, str]:
        if src.startswith("data:"):
            header, _, encoded = src.partition(",")
            if not encoded:
                raise GenerationProviderError("Malformed image data URI.")
            content_type = header[5:].split(";", 1)[0].strip() or "image/png"
            return base64.b64decode(encoded), content_type
        resp = await client.get(src)
        raise_for_provider(resp, "Image download")
        content_type = resp.headers.get("content-type") or "image/png"
        if not content_type.startswith("image/"):
            content_type = "image/png"
        return resp.content, content_type


__all__ = ["ChatCompletionsImagegenAdapter"]

View on GitHub (pinned to 3e82f13042)