harry0703/MoneyPrinterTurbo · error · ValueError
[{llm_provider}] returned empty text content
Error message
[{llm_provider}] returned empty text content What it means
First empty-content branch in _normalize_text_response: the LLM SDK returned content=None. Comment notes various SDKs return None on interception/exception paths; this guard converts it into an explicit ValueError before downstream .replace() calls would crash with AttributeError on NoneType.
Source
Thrown at app/services/llm.py:52
## Constrains:
1. the script is to be returned as a string with the specified number of paragraphs.
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:View on GitHub (pinned to 1f9f19c202)
Solutions
- Retry once — safety-filter nulls are often prompt-specific and transient; vary the prompt slightly if it persists
- Inspect the full raw response (finish_reason/finish_reason field) to confirm content filtering vs. a provider bug
- Loosen the prompt: remove topics that trigger provider filters
- Switch provider or model (e.g. from a filtered hosted model to a self-hosted OpenAI-compatible endpoint)
Example fix
# before
text = response.choices[0].message.content.replace("\n", "") # NoneType crash
# after
content = response.choices[0].message.content
if content is None:
raise ValueError(f"[{provider}] returned empty text content (finish_reason="
f"{response.choices[0].finish_reason})")
text = content.replace("\n", "") Defensive patterns
Strategy: type-guard
Validate before calling
content = getattr(message, "content", None)
if content is None:
finish = getattr(choice, "finish_reason", None)
raise ValueError(f"content null (finish_reason={finish})") Type guard
def has_text_content(message) -> bool:
return isinstance(getattr(message, "content", None), str) and bool(message.content.strip()) Try / catch
try:
text = generate(prompt)
except ValueError as e:
if "empty text content" in str(e):
text = generate(tweak_prompt(prompt)) # one retry, then surface to user Prevention
- Always null-check message.content before string ops
- Check finish_reason to distinguish content filtering from provider bugs
When it happens
Trigger: An OpenAI-compatible provider returns a completion where message.content is null — typical for content-filtered responses, tool-call-only responses, or providers whose safety filter silently nulls content.
Common situations: Gemini/OpenAI safety filters blocking the script prompt, reasoning models that put everything in a reasoning field and leave content null, or proxies that strip content on policy grounds.
Related errors
- [{llm_provider}] returned non-text content: {type(content)._
- [{llm_provider}] returned empty choices
- [{llm_provider}] returned empty message
- [qwen] returned empty choices
- {llm_provider}: unsupported llm provider
AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14).
Data as JSON: /api/errors/751f364c3843da13.
Report an issue: GitHub.