mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-01 19:06:01 +00:00
fix(serialization): strip base64 image data from streamed values events (#3631)
The SSE stream's `values` snapshots serialize the full state, including the hide_from_ui human messages that ViewImageMiddleware fills with base64 image payloads. #3535 stripped those from the REST wait/history/state endpoints via serialize_channel_values_for_api, but the streaming path (worker publishes serialize(chunk, mode="values")) still went through serialize_channel_values, so the same base64 leaked to the frontend over SSE. Route values-mode serialization through serialize_channel_values_for_api as well. Non-hidden messages and https image URLs are left untouched.
This commit is contained in:
parent
b5a4d3414b
commit
3e5c76eb0a
@ -133,11 +133,14 @@ def serialize(obj: Any, *, mode: str = "") -> Any:
|
|||||||
"""Serialize LangChain objects with mode-specific handling.
|
"""Serialize LangChain objects with mode-specific handling.
|
||||||
|
|
||||||
* ``messages`` — obj is ``(message_chunk, metadata_dict)``
|
* ``messages`` — obj is ``(message_chunk, metadata_dict)``
|
||||||
* ``values`` — obj is the full state dict; ``__pregel_*`` keys stripped
|
* ``values`` — obj is the full state dict; ``__pregel_*`` keys stripped and
|
||||||
|
base64 ``data:`` image blocks dropped from hide_from_ui messages
|
||||||
* everything else — recursive ``model_dump()`` / ``dict()`` fallback
|
* everything else — recursive ``model_dump()`` / ``dict()`` fallback
|
||||||
"""
|
"""
|
||||||
if mode == "messages":
|
if mode == "messages":
|
||||||
return serialize_messages_tuple(obj)
|
return serialize_messages_tuple(obj)
|
||||||
if mode == "values":
|
if mode == "values":
|
||||||
return serialize_channel_values(obj) if isinstance(obj, dict) else serialize_lc_object(obj)
|
# ``values`` snapshots stream the full state to the frontend, so they
|
||||||
|
# must drop base64 image payloads the same way the REST endpoints do.
|
||||||
|
return serialize_channel_values_for_api(obj) if isinstance(obj, dict) else serialize_lc_object(obj)
|
||||||
return serialize_lc_object(obj)
|
return serialize_lc_object(obj)
|
||||||
|
|||||||
@ -328,3 +328,32 @@ def test_serialize_channel_values_for_api_no_messages():
|
|||||||
|
|
||||||
result = serialize_channel_values_for_api({"title": "empty"})
|
result = serialize_channel_values_for_api({"title": "empty"})
|
||||||
assert result == {"title": "empty"}
|
assert result == {"title": "empty"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_serialize_values_mode_strips_base64_from_hidden_messages():
|
||||||
|
"""The SSE stream emits ``values`` snapshots of the full state, so it must
|
||||||
|
strip base64 image data from hide_from_ui messages just like the REST
|
||||||
|
endpoints do — otherwise the same payload leaks over the stream."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from deerflow.runtime.serialization import serialize
|
||||||
|
|
||||||
|
state = {
|
||||||
|
"messages": [
|
||||||
|
_make_msg(
|
||||||
|
[
|
||||||
|
{"type": "text", "text": "context"},
|
||||||
|
{
|
||||||
|
"type": "image_url",
|
||||||
|
"image_url": {"url": "data:image/png;base64,iVBOR..."},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hide_from_ui=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
result = serialize(state, mode="values")
|
||||||
|
# the hidden message survives (count/order preserved) but the data: block is gone
|
||||||
|
assert len(result["messages"]) == 1
|
||||||
|
assert "data:image/png;base64" not in json.dumps(result)
|
||||||
|
assert result["messages"][0]["content"] == [{"type": "text", "text": "context"}]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user