[FLINK-AGENTS][api] Fix Kryo deserialization failure for Event classes with immutable lists#677
Conversation
wenjin272
left a comment
There was a problem hiding this comment.
Hi, @avichaym, Ty for this fix.
The current fix looks good to me, but I think two additional points need to be added:
- If user use
Map.ofto consturctToolResponseEvent, there will likely be issues as well. - Supplement with relevant tests. Since in the CI pipeline, the test cases in
flink-agents-end-to-end-tests-integrationmodule will be executed in jdk 17 with flink 1.20, 2.0, 2.1 and 2.2. I think we can add aEventKryoSerializationTestin this module like:
@Test
void chatRequestEventWithImmutableListSurvivesKryoRoundTrip() throws Exception {
ChatRequestEvent event =
new ChatRequestEvent(
"myModel", List.of(new ChatMessage(MessageRole.USER, "hello")));
ChatRequestEvent roundTripped = roundTrip(event, ChatRequestEvent.class);
assertEquals("myModel", roundTripped.getModel());
assertNotNull(roundTripped.getMessages());
assertEquals(1, roundTripped.getMessages().size());
assertEquals("hello", roundTripped.getMessages().get(0).getContent());
}
private static <T extends Event> T roundTrip(T event, Class<T> cls) throws Exception {
KryoSerializer<T> serializer = new KryoSerializer<>(cls, new SerializerConfigImpl());
DataOutputSerializer out = new DataOutputSerializer(64);
serializer.serialize(event, out);
DataInputDeserializer in = new DataInputDeserializer(out.getCopyOfBuffer());
return serializer.deserialize(in);
}
08bb545 to
751782b
Compare
|
Thanks for the careful review @wenjin272 - both points addressed in the new commit :
|
| Map.of( | ||
| "id", "call_1", | ||
| "name", "myTool", | ||
| "arguments", Map.of("x", 1)))); |
There was a problem hiding this comment.
Currently, we only copy the outer ImmutableList/ImmutableMap without handling nested ImmutableList/ImmutableMap cases. Therefore, this test will fail under Flink 1.20.
I think there are probably two solutions to this problem:
- Supports copying arbitrarily nested ImmutableList/ImmutableMap structures into nested MutableList/MutableMap, but this might be too heavy for constructing Events.
- Since both ToolRequestEvent and ToolResponseEvent are constructed by built-in actions, users typically do not create these events themselves. Therefore, we forgo defensive checks for Tool Events.
I personally prefer option 2, WDYT?
There was a problem hiding this comment.
Agreed, option 2 makes more sense. Updated to revert the Tool event changes and keeping defensive copies only on ChatRequestEvent and ContextRetrievalResponseEvent. Test trimmed to those two cases.
751782b to
0535311
Compare
…ted events + add contract test Wrap the List arg in new ArrayList<>() in the constructors of the events that user code instantiates directly: ChatRequestEvent (messages) and ContextRetrievalResponseEvent (documents). This avoids the JDK 17+ Kryo InaccessibleObjectException when callers pass List.of(...) or any other ImmutableCollections instance. ToolRequestEvent / ToolResponseEvent are intentionally not changed - they are produced only by built-in framework actions, never by user code, so adding defensive copies there only adds overhead. (And shallow copies wouldn't help anyway: tool-call payloads can carry nested Map.of(...).) EventKryoSerializationTest in the e2e integration module asserts the defensive-copy contract directly: the stored collection must be mutable. Two tests, one per affected event.
0535311 to
60a46a0
Compare
fixes #673
Wraps list arguments in new ArrayList<>() before passing to setAttr() in ChatRequestEvent, ContextRetrievalResponseEvent, and ToolRequestEvent. Without this, passing List.of(...) crashes Kryo on JDK 17+ with InaccessibleObjectException.