Skip to content

BinaryInput.kind degrades to str after EventLogWriter persist/load, crashing the OpenAI Responses mapper (AttributeError: 'str' object has no attribute 'value') #3084

Description

@Lancetnik

Summary

A BinaryInput (e.g. ImageInput) has its .kind field silently degraded from the BinaryType enum to a plain str after a persist → load round-trip through EventLogWriter / SqliteKnowledgeStore (ag2.knowledge.log). When that reloaded history is later mapped for the OpenAI Responses API, events_to_responses_input (ag2/config/openai/mappers.py) uses enum-identity checks (is / in) that silently miss the string, falls through to an error branch, and raises:

AttributeError: 'str' object has no attribute 'value'

In practice this crashes any multi-turn chat that persists an image/PDF/binary tool result to disk and then continues the conversation (the reloaded attachment is re-sent to the model on the next turn).

Environment

  • ag2 1.0.0b0
  • openai 2.45.0
  • Python 3.13.9
  • Client: OpenAI Responses API (ag2/config/openai/openai_responses_client.py)

Reproduction (self-contained, no external files, no API key)

import asyncio, base64, json, tempfile
from ag2 import ImageInput, ToolResult
from ag2.config.openai.mappers import events_to_responses_input
from ag2.events.tool_events import ToolResultEvent, ToolResultsEvent
from ag2.knowledge.log import EventLogWriter
from ag2.knowledge.sqlite import SqliteKnowledgeStore

_PNG = base64.b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
)

class Ser:
    def encode(self, d): return json.dumps(d).encode()
    def decode(self, b): return json.loads(b)

async def main():
    store = SqliteKnowledgeStore(tempfile.mktemp(suffix=".db"))
    writer = EventLogWriter(store)
    result = ToolResult(ImageInput(data=_PNG, media_type="image/png"))
    event = ToolResultsEvent(results=[ToolResultEvent(parent_id="call_1", name="read_file", result=result)])
    print("fresh kind:   ", repr(event.results[0].result.parts[0].kind))
    await writer.persist("chat", [event])
    loaded = await writer.load("chat")
    print("reloaded kind:", repr(loaded[0].results[0].result.parts[0].kind))
    events_to_responses_input(loaded, Ser())  # <-- crashes

asyncio.run(main())

Output:

fresh kind:    <BinaryType.IMAGE: 'image'>
reloaded kind: 'image'
...
  File ".../ag2/config/openai/mappers.py", line 164, in events_to_responses_input
    raise UnsupportedInputError(f"BinaryInput({part.kind.value})", "openai-responses")
AttributeError: 'str' object has no attribute 'value'

Expected

After EventLogWriter.load, part.kind should be BinaryType.IMAGE (the enum), and the reloaded image should map to an input_image block exactly like the fresh one.

Actual

part.kind is the string "image". In events_to_responses_input:

elif isinstance(part, BinaryInput):
    ...
    if part.kind is BinaryType.IMAGE:            # False: str is not the enum
        ...
    elif part.kind in (BinaryType.DOCUMENT, BinaryType.BINARY):  # False
        ...
    else:
        raise UnsupportedInputError(f"BinaryInput({part.kind.value})", "openai-responses")  # str.value -> AttributeError

Root cause

EventLogWriter._serialize_events stores event.to_dict() (so BinaryType.IMAGE becomes "image"), but _load_file reconstructs via cls.from_dict(event_data) (ag2/knowledge/log.py) without re-coercing BinaryInput.kind back into BinaryType. Two things combine:

  1. Serialization round-trip does not restore the kind enum on load.
  2. The Responses mapper compares with is / in against the enum and its fallthrough assumes an enum (part.kind.value), so a stringified kind both mis-routes and raises an unhelpful AttributeError instead of mapping the image.

The same pattern (inp.kind.value / part.kind.value) appears at mappers.py lines 164, 172, 256, 266 (responses) and 322, 351 (completions), so completions history is likely affected too.

Suggested fix

Coerce BinaryInput.kind back to BinaryType on from_dict (the primary fix — the data shouldn't degrade). Optionally, harden the mapper to normalize kind (BinaryType(part.kind)) before the identity checks so a stringified value maps correctly rather than crashing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coreAgent runtime core: ag2/*.py, ag2/events, ag2/response, ag2/streamspriority:normalDefault priority: scheduled into regular workstatus:confirmedTriage passed: reproducible / accepted as validtype:bugExisting behavior is broken or doesn't match the docs

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions