microsoft/autogen · error · ValueError

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

Error message

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

What it means

apply_patch accepts a unified diff as str or bytes (bytes is UTF-8 decoded). Any other type for patch_data raises before parsing, guarding the unidiff parser from garbage input.

Source

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

        if from_content == "" and to_content == "":  # one (or both) revision ids not found
            return ""
        diff = difflib.unified_diff(
            from_content.splitlines(keepends=True),
            to_content.splitlines(keepends=True),
            fromfile=f"{filename}@r{from_revision}",
            tofile=f"{filename}@r{to_revision}",
        )
        return "".join(diff)

    def apply_patch(self, filename: str, patch_data: Union[str, bytes, Any]) -> None:
        """Apply *patch_text* (unified diff) to the latest revision and save a new revision.

        Uses the *unidiff* library to accurately apply hunks and validate context lines.
        """
        if isinstance(patch_data, bytes):
            patch_data = patch_data.decode("utf-8")
        if not isinstance(patch_data, str):
            raise ValueError(f"Expected str or bytes, got {type(patch_data)}")
        self._ensure_file(filename)
        original_content = self.get_latest_content(filename)

        if PatchSet is None:
            raise ImportError(
                "The 'unidiff' package is required for patch application. Install with 'pip install unidiff'."
            )

        patch = PatchSet(patch_data)
        # Our canvas stores exactly one file per patch operation so we
        # use the first (and only) patched_file object.
        if not patch:
            raise ValueError("Empty patch text provided.")
        patched_file = patch[0]
        working_lines = original_content.splitlines(keepends=True)
        line_offset = 0
        for hunk in patched_file:
            # Calculate the slice boundaries in the *current* working copy.

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Pass the raw unified-diff string: join line lists with '\n', or extract the diff field from the tool response.
  2. Ensure the value is not None before calling; guard tool outputs at the boundary.

Example fix

# before
canvas.apply_patch("f.txt", diff_lines_list)
# after
canvas.apply_patch("f.txt", "\n".join(diff_lines_list))
Defensive patterns

Strategy: type-guard

Validate before calling

import json
if not isinstance(patch_data, (str, bytes)):
    patch_data = patch_data if isinstance(patch_data, str) else json.dumps(patch_data)
# better: extract the raw diff string from tool output up front
raw_diff = tool_call_output["diff"] if isinstance(tool_call_output, dict) else tool_call_output

Type guard

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

Prevention

When it happens

Trigger: Calling apply_patch(filename, patch_data) where patch_data is a list of diff lines, a dict from a parsed tool schema, None, or a PatchSet object.

Common situations: LLM tool output delivered as structured JSON instead of raw diff text; passing already-split lines; forgetting to extract the diff string from a tool-call response envelope.

Related errors


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