bytedance/deer-flow · error · HTTPException

Invalid message at input.messages[{index}]: {exc}

Error message

Invalid message at input.messages[{index}]: {exc}

What it means

HTTP 400 raised while converting the input.messages array of a run request: a dict-form message failed conversion in convert_to_messages (ValueError/TypeError/NotImplementedError). The detail includes the failing index and the converter's reason, so the offending message can be located precisely. Conversion happens before any run starts, so the request is rejected cleanly.

Source

Thrown at backend/app/gateway/services.py:270

    ``original_user_content``, dynamic-context reminder markers, and the
    transient view-image context marker are server-owned. External callers
    cannot supply them; trusted internal channel calls may preserve metadata
    they added before invoking this boundary.
    """
    if raw_input is None:
        return {}
    messages = raw_input.get("messages")
    if messages and isinstance(messages, list):
        converted: list[Any] = []
        for index, msg in enumerate(messages):
            if isinstance(msg, BaseMessage):
                converted.append(msg)
            elif isinstance(msg, dict):
                try:
                    converted.extend(convert_to_messages([msg]))
                except (ValueError, TypeError, NotImplementedError) as exc:
                    raise HTTPException(
                        status_code=400,
                        detail=f"Invalid message at input.messages[{index}]: {exc}",
                    ) from exc
            else:
                converted.append(msg)
        if not trusted_internal:
            converted = [_strip_external_message_metadata(message) for message in converted]
        return {**raw_input, "messages": converted}
    return raw_input


_DEFAULT_ASSISTANT_ID = "lead_agent"


# Whitelist of run-context keys that the langgraph-compat layer forwards from
# ``body.context`` into the run config. ``config["context"]`` exists in
# LangGraph >=0.6, but these values must be written to both ``configurable``
# (for legacy ``_get_runtime_config`` consumers) and ``context`` because

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Use the index from the error (input.messages[N]) to find the exact bad message.
  2. Fix that dict to a supported shape: include a valid 'type'/'role', well-formed content, and required ids for tool messages.
  3. Prefer sending messages already shaped like LangChain BaseMessage serializations, or validate with the same convert_to_messages helper client-side if embedded.

Example fix

# before
{"input": {"messages": [{"role": "user", "content": "hi"}, {"type": "tool_result", "content": "42"}]}}

# after
{"input": {"messages": [{"role": "user", "content": "hi"}, {"type": "tool", "tool_call_id": "call_1", "content": "42"}]}}
Defensive patterns

Strategy: type-guard

Validate before calling

const VALID_TYPES = new Set(['human', 'ai', 'system', 'tool', 'user', 'assistant']);
function messagesAreValid(msgs) {
  return msgs.every((m, i) => {
    if (typeof m !== 'object' || m === null) return true; // passthrough
    if (!VALID_TYPES.has(m.type ?? m.role)) { console.error(`messages[${i}] has bad type`); return false; }
    if ((m.type ?? m.role) === 'tool' && !m.tool_call_id) { console.error(`messages[${i}] tool missing tool_call_id`); return false; }
    return true;
  });
}

Type guard

function isMessageDict(m: unknown): m is Record<string, unknown> {
  if (typeof m !== 'object' || m === null) return false;
  const t = (m as any).type ?? (m as any).role;
  return typeof t === 'string' && VALID_TYPES.has(t);
}

Try / catch

catch 400 'Invalid message at input.messages[N]'; use N and the exc text to fix that one message, then resend.

Prevention

When it happens

Trigger: POSTing a run whose input.messages[i] dict has an unknown/missing 'type', malformed role fields, or a message shape convert_to_messages does not accept (e.g. a tool message without a matching tool_call_id, or unsupported content blocks).

Common situations: Replaying captured LangGraph payloads with older/newer message schemas; hand-building message dicts; sending provider-specific content shapes (Anthropic/OpenAI blocks) where DeerFlow expects its own schema; BaseMessage objects pass through, only dicts are converted.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/ff493a32ce93f68b. Report an issue: GitHub.