langchain-ai/deepagents · error · TypeError

system_prompt must be str or None, got {type(system_prompt).

Error message

system_prompt must be str or None, got {type(system_prompt).__name__}

What it means

SkillsMiddleware accepts a custom `system_prompt` as a str or None; anything else (e.g. a list of prompt parts, a ChatPromptTemplate, bytes) is rejected with TypeError. String prompts are additionally checked for required format slots, so non-string composite prompt objects must be converted to a str first.

Source

Thrown at libs/deepagents/deepagents/middleware/skills.py:841

                are rendered as `**{label} Skills**` in the system prompt
                (do not include the trailing `Skills` in your label).
            system_prompt: System-prompt fragment template. Must contain
                `{skills_locations}`, `{skills_load_warnings}`, and
                `{skills_list}` slots for runtime substitution. Pass `None`
                to skip appending entirely (skills are still loaded into
                `state["skills_metadata"]`).

        Raises:
            TypeError: If a tuple entry in `sources` is not exactly a
                `(str, str)` pair, or if `system_prompt` is not `str` or
                `None`.
            ValueError: If `system_prompt` is a string missing any of the
                required format slots.
        """
        if system_prompt is not None:
            if not isinstance(system_prompt, str):
                msg = f"system_prompt must be str or None, got {type(system_prompt).__name__}"
                raise TypeError(msg)
            required = ("{skills_locations}", "{skills_load_warnings}", "{skills_list}")
            missing = [slot for slot in required if slot not in system_prompt]
            if missing:
                msg = f"system_prompt missing required format slot(s): {', '.join(missing)}"
                raise ValueError(msg)
        self._backend = backend
        # `self.sources` remains paths-only (`list[str]`) to preserve
        # backwards-compat for callers that inspect it directly; label
        # information is mirrored on `self.source_labels` at the same index.
        self.sources: list[str] = [_source_path(s) for s in sources]
        self.source_labels: list[str] = [_derive_source_label(s) for s in sources]
        self.system_prompt_template = system_prompt

    def _format_skills_locations(self) -> str:
        """Format skills locations for display in system prompt."""
        locations = []
        last = len(self.sources) - 1

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Convert to str: system_prompt="\n".join(parts) or str(template.invoke({})).
  2. Pass None to use the default system prompt.
  3. If composing dynamically, render the final template into a string before constructing.
  4. Use only the three supported slots {skills_locations}, {skills_load_warnings}, {skills_list} when customizing.

Example fix

// before
system_prompt=[intro, instructions]
// after
system_prompt="\n\n".join([intro, instructions])
Defensive patterns

Strategy: type-guard

Validate before calling

sp = system_prompt
if sp is not None and not isinstance(sp, str):
    sp = "\n".join(sp) if isinstance(sp, list) else str(sp)

Type guard

def is_valid_system_prompt(v: object) -> TypeGuard[str | None]:
    return v is None or isinstance(v, str)

Try / catch

try:
    mw = SkillsMiddleware(backend=b, system_prompt=system_prompt)
except TypeError as e:
    if "system_prompt must be str or None" in str(e):
        mw = SkillsMiddleware(backend=b, system_prompt=str(system_prompt))
    else:
        raise

Prevention

When it happens

Trigger: SkillsMiddleware(backend=..., system_prompt=["part1", "part2"]) or passing a LangChain prompt template / ChatPromptTemplate object instead of a rendered string.

Common situations: Building the prompt programmatically as a list and forgetting to join; passing an f-string pipeline object; refactoring from another middleware that accepted templates.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/199f06665fc3e271. Report an issue: GitHub.