harry0703/MoneyPrinterTurbo · error · TypeError

[{llm_provider}] returned non-text content: {type(content)._

Error message

[{llm_provider}] returned non-text content: {type(content).__name__}

What it means

Type guard in _normalize_text_response: the content attribute exists but is not a str (e.g. a list of content-part objects as returned by some OpenAI-compatible/multimodal providers). Raises TypeError with the offending type name instead of crashing later on .replace/.sub.

Source

Thrown at app/services/llm.py:55

2. do not under any circumstance reference this prompt in your response.
3. get straight to the point, don't start with unnecessary things like, "welcome to this video".
4. you must not include any type of markdown or formatting in the script, never use a title.
5. only return the raw content of the script.
6. do not include "voiceover", "narrator" or similar indicators of what should be spoken at the beginning of each paragraph or line.
7. you must not mention the prompt, or anything about the script itself. also, never talk about the amount of paragraphs or lines. just write the script.
8. respond in the same language as the video subject.
""".strip()


def _normalize_text_response(content, llm_provider: str) -> str:
    # 不同 LLM SDK 在异常或被拦截场景下,可能返回 None、空字符串,
    # 甚至返回非字符串对象。这里统一做兜底校验,避免后续直接调用
    # `.replace()` 时抛出 `NoneType` 之类的属性错误。
    if content is None:
        raise ValueError(f"[{llm_provider}] returned empty text content")

    if not isinstance(content, str):
        raise TypeError(
            f"[{llm_provider}] returned non-text content: {type(content).__name__}"
        )

    # MiniMax M3、DeepSeek R1 这类 reasoning 模型可能会把内部推理包在
    # `<think>...</think>` 中返回。视频脚本和关键词只需要最终可朗读文本,
    # 如果不在服务层统一清理,WebUI、字幕和配音都会把思考过程当正文处理。
    content = _THINK_BLOCK_RE.sub("", content)
    content = _UNCLOSED_THINK_BLOCK_RE.sub("", content).strip()
    if not content:
        raise ValueError(f"[{llm_provider}] returned empty text content")

    return content.replace("\n", "")


def _sanitize_error_message(error: object) -> str:
    """
    清理返回给 WebUI/API 的错误信息,避免自定义 base_url 中的凭据泄露。

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Pin the SDK version whose response shape your adapter expects
  2. If the provider returns content parts, join the text parts before calling _normalize_text_response: ''.join(p['text'] for p in content if p.get('type')=='text')
  3. Route around the offending provider: switch llm_provider in config.toml to a known-string provider

Example fix

# before
content = message.content  # list of parts from multimodal provider

# after
content = message.content
if isinstance(content, list):
    content = "".join(
        part.get("text", "") for part in content
        if isinstance(part, dict) and part.get("type") == "text"
    )
# then proceed to _normalize_text_response
Defensive patterns

Strategy: type-guard

Validate before calling

content = message.content
if isinstance(content, list):
    content = "".join(p.get("text", "") for p in content
                      if isinstance(p, dict) and p.get("type") == "text")

Type guard

from typing import Any

def as_text(content: Any) -> str | None:
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        joined = "".join(p.get("text", "") for p in content
                          if isinstance(p, dict) and p.get("type") == "text")
        return joined or None
    return None

Try / catch

try:
    text = _normalize_text_response(message.content, provider)
except TypeError as e:
    if "non-text content" in str(e):
        log_raw_response_shape()  # capture schema drift before it spreads

Prevention

When it happens

Trigger: Providers that return structured content blocks (list of {type:'text',...} dicts) or SDK versions where message.content is a typed object rather than a plain string.

Common situations: Upgrading an OpenAI-compatible proxy that starts returning multimodal content-part arrays, SDK changes where content becomes a ChatCompletionContentPart list, or misconfigured adapters routing to an endpoint with a different response schema.

Related errors


AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14). Data as JSON: /api/errors/4d06143113ce8f0b. Report an issue: GitHub.