microsoft/autogen · error · ValueError

Expected str or bytes, got {type(new_content)}

Error message

Expected str or bytes, got {type(new_content)}

What it means

add_or_update_file accepts only str or bytes (bytes is UTF-8 decoded first). Any other type (int, dict, list, None) is rejected with this ValueError to keep every revision a clean string.

Source

Thrown at python/packages/autogen-ext/src/autogen_ext/memory/canvas/_text_canvas.py:113

    # ----------------------------------------------------------------------------------
    # BaseCanvas interface implementation
    # ----------------------------------------------------------------------------------

    def list_files(self) -> Dict[str, int]:
        """Return a mapping of *filename → latest revision number*."""
        return {fname: revs[-1].revision for fname, revs in self._files.items() if revs}

    def get_latest_content(self, filename: str) -> str:  # noqa: D401 – keep API identical
        """Return the most recent content or an empty string if the file is new."""
        revs = self._files.get(filename, [])
        return revs[-1].content if revs else ""

    def add_or_update_file(self, filename: str, new_content: Union[str, bytes, Any]) -> None:
        """Create *filename* or append a new revision containing *new_content*."""
        if isinstance(new_content, bytes):
            new_content = new_content.decode("utf-8")
        if not isinstance(new_content, str):
            raise ValueError(f"Expected str or bytes, got {type(new_content)}")
        if filename not in self._files:
            self._files[filename] = [FileRevision(new_content, 1)]
        else:
            last_rev_num = self._files[filename][-1].revision
            self._files[filename].append(FileRevision(new_content, last_rev_num + 1))

    def get_diff(self, filename: str, from_revision: int, to_revision: int) -> str:
        """Return a unified diff between *from_revision* and *to_revision*."""
        revisions = self._files.get(filename, [])
        if not revisions:
            return ""
        # Fetch the contents for the requested revisions.
        from_content = self.get_revision_content(filename, from_revision)
        to_content = self.get_revision_content(filename, to_revision)
        if from_content == "" and to_content == "":  # one (or both) revision ids not found
            return ""
        diff = difflib.unified_diff(
            from_content.splitlines(keepends=True),

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Serialize non-string content to a string before calling: json.dumps for dicts/lists, str() for scalars.
  2. If you passed bytes with a non-UTF-8 encoding, decode manually first.

Example fix

# before
canvas.add_or_update_file("data.json", {"a": 1})
# after
canvas.add_or_update_file("data.json", json.dumps({"a": 1}))
Defensive patterns

Strategy: type-guard

Validate before calling

import json
if not isinstance(content, (str, bytes)):
    content = json.dumps(content)  # or str(content) for scalars
canvas.add_or_update_file(filename, content)

Type guard

def is_canvas_writable_content(value) -> bool:
    return isinstance(value, (str, bytes))

Prevention

When it happens

Trigger: Calling add_or_update_file(filename, 123), add_or_update_file(filename, {'a': 1}), or passing None/Path objects as content.

Common situations: LLM tool calls returning non-string payloads; upstream code passing parsed JSON or numbers directly; forgetting to serialize before storage.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/44d3a7d8725cdd98. Report an issue: GitHub.