Skip to content

Commit 2ae3acf

Browse files
giulio-leoneCopilot
andcommitted
fix: encode bytes in SessionAgent/Session.to_dict() for JSON serialization
SessionAgent.to_dict() and Session.to_dict() call asdict() directly without encoding bytes values, while SessionMessage.to_dict() correctly uses encode_bytes_values(). This inconsistency causes S3SessionManager and FileSessionManager to crash with: TypeError: Object of type bytes is not JSON serializable when agent state contains binary content (e.g., inline PDF bytes from multimodal prompts passed through LiteLLM proxy). Fix: Apply encode_bytes_values() in SessionAgent.to_dict() and Session.to_dict() (matching SessionMessage.to_dict()), and add decode_bytes_values() in SessionAgent.from_dict() for round-trip correctness. Added regression tests for bytes serialization in SessionAgent. Fixes #1864 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fca208b commit 2ae3acf

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

src/strands/types/session.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,11 @@ def from_bidi_agent(cls, agent: "BidiAgent") -> "SessionAgent":
165165
@classmethod
166166
def from_dict(cls, env: dict[str, Any]) -> "SessionAgent":
167167
"""Initialize a SessionAgent from a dictionary, ignoring keys that are not class parameters."""
168-
return cls(**{k: v for k, v in env.items() if k in inspect.signature(cls).parameters})
168+
return cls(**decode_bytes_values({k: v for k, v in env.items() if k in inspect.signature(cls).parameters}))
169169

170170
def to_dict(self) -> dict[str, Any]:
171171
"""Convert the SessionAgent to a dictionary representation."""
172-
return asdict(self)
172+
return encode_bytes_values(asdict(self))
173173

174174
def initialize_internal_state(self, agent: "Agent") -> None:
175175
"""Initialize internal state of agent."""
@@ -204,4 +204,4 @@ def from_dict(cls, env: dict[str, Any]) -> "Session":
204204

205205
def to_dict(self) -> dict[str, Any]:
206206
"""Convert the Session to a dictionary representation."""
207-
return asdict(self)
207+
return encode_bytes_values(asdict(self))

tests/strands/types/test_session.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,43 @@ def test_session_agent_initialize_internal_state():
129129
tru_interrupt_state = agent._interrupt_state
130130
exp_interrupt_state = _InterruptState(interrupts={}, context={"test": "init"}, activated=False)
131131
assert tru_interrupt_state == exp_interrupt_state
132+
133+
134+
def test_session_agent_with_bytes():
135+
"""SessionAgent.to_dict() must encode bytes so json.dumps() doesn't crash.
136+
137+
This is the root cause of issue #1864: S3SessionManager fails when agent state
138+
contains binary document content (e.g., inline PDF bytes from multimodal prompts).
139+
"""
140+
state_with_bytes = {
141+
"documents": [
142+
{
143+
"format": "pdf",
144+
"name": "document.pdf",
145+
"source": {"bytes": b"fake-pdf-binary-content"},
146+
}
147+
]
148+
}
149+
150+
session_agent = SessionAgent(
151+
agent_id="a1",
152+
conversation_manager_state={},
153+
state=state_with_bytes,
154+
)
155+
156+
# Must be JSON-serializable (this is where #1864 crashes without the fix)
157+
agent_dict = session_agent.to_dict()
158+
json_str = json.dumps(agent_dict)
159+
160+
# Round-trip: from_dict must decode bytes back
161+
loaded_agent = SessionAgent.from_dict(json.loads(json_str))
162+
assert loaded_agent.state["documents"][0]["source"]["bytes"] == b"fake-pdf-binary-content"
163+
assert loaded_agent.agent_id == "a1"
164+
165+
166+
def test_session_with_bytes_in_session_type():
167+
"""Session.to_dict() must encode any bytes values to remain JSON-safe."""
168+
session = Session(session_id="test-id", session_type=SessionType.AGENT)
169+
session_dict = session.to_dict()
170+
# Should not raise
171+
json.dumps(session_dict)

0 commit comments

Comments
 (0)