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:
- Serialization round-trip does not restore the
kind enum on load.
- 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.
Summary
A
BinaryInput(e.g.ImageInput) has its.kindfield silently degraded from theBinaryTypeenum to a plainstrafter a persist → load round-trip throughEventLogWriter/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: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
ag21.0.0b0openai2.45.0ag2/config/openai/openai_responses_client.py)Reproduction (self-contained, no external files, no API key)
Output:
Expected
After
EventLogWriter.load,part.kindshould beBinaryType.IMAGE(the enum), and the reloaded image should map to aninput_imageblock exactly like the fresh one.Actual
part.kindis the string"image". Inevents_to_responses_input:Root cause
EventLogWriter._serialize_eventsstoresevent.to_dict()(soBinaryType.IMAGEbecomes"image"), but_load_filereconstructs viacls.from_dict(event_data)(ag2/knowledge/log.py) without re-coercingBinaryInput.kindback intoBinaryType. Two things combine:kindenum on load.is/inagainst the enum and its fallthrough assumes an enum (part.kind.value), so a stringifiedkindboth mis-routes and raises an unhelpfulAttributeErrorinstead of mapping the image.The same pattern (
inp.kind.value/part.kind.value) appears atmappers.pylines 164, 172, 256, 266 (responses) and 322, 351 (completions), so completions history is likely affected too.Suggested fix
Coerce
BinaryInput.kindback toBinaryTypeonfrom_dict(the primary fix — the data shouldn't degrade). Optionally, harden the mapper to normalizekind(BinaryType(part.kind)) before the identity checks so a stringified value maps correctly rather than crashing.