Skip to content

[Bug] ActionStateSerde cannot deserialize built-in event subclasses during durable recovery #868

Description

@rosemarYuan

Search before asking

  • I searched in the issues and found nothing similar.

Description

Problem

ActionStateSerdeTest currently covers InputEvent and OutputEvent round-trip only. Those two classes have @JsonCreator on their (UUID, Map<String, Object>) constructors, so they deserialize correctly.

However, the built-in event classes under org.apache.flink.agents.api.event.*, such as ChatRequestEvent, ChatResponseEvent, ToolRequestEvent, ToolResponseEvent, ContextRetrievalRequestEvent, and ContextRetrievalResponseEvent, also have (UUID, Map<String, Object>) constructors but do not annotate them with @JsonCreator/@JsonProperty.

This matters because durable execution persists events in ActionState, including the triggering taskEvent and completed outputEvents. Both Kafka and Fluss action state stores delegate to ActionStateSerde, so recovery/rebuild needs Jackson to reconstruct these concrete event subclasses from the serialized @class metadata.

A straightforward fix would be to annotate these (UUID, Map<String, Object>) constructors consistently with InputEvent/OutputEvent, or otherwise teach ActionStateSerde how to reconstruct these built-in event subclasses during durable state recovery.

One extra thing worth testing after the constructor fix: nested attributes should also come back as the expected types, e.g. ChatMessage, ToolResponse, and Document, rather than raw Map values.

Background

This looks related to #631, which introduced the cross-language unified event model. That change moved built-in event data into a common type + attributes representation and added (UUID id, Map<String, Object> attributes) constructors to built-in event subclasses for typed reconstruction via fromEvent().

However, in the #631 merge commit (071c3a0a), only InputEvent and OutputEvent annotated these constructors with @JsonCreator/@JsonProperty. Other built-in event subclasses, such as ChatRequestEvent, ChatResponseEvent, ToolRequestEvent, ToolResponseEvent, ContextRetrievalRequestEvent, and ContextRetrievalResponseEvent, have the same (UUID, Map<String, Object>) constructors but lack Jackson creator annotations.

That works for explicit fromEvent() conversion, but not for ActionStateSerde, which relies on Jackson polymorphic deserialization to restore durable ActionState records.

How to reproduce

A minimal regression test could be added to ActionStateSerdeTest:

@Test
public void testBuiltinEventSubclassesRoundTrip() throws Exception {
    ChatMessage message = new ChatMessage(MessageRole.USER, "hello");
    UUID requestId = UUID.randomUUID();

    assertEventRoundTrips(new ChatRequestEvent("model", List.of(message)));
    assertEventRoundTrips(new ChatResponseEvent(requestId, message));
    assertEventRoundTrips(new ToolRequestEvent("model", List.of(Map.of("name", "tool"))));
    assertEventRoundTrips(
            new ToolResponseEvent(
                    requestId,
                    Map.of("call-1", ToolResponse.success("result")),
                    Map.of("call-1", true),
                    Map.of()));
    assertEventRoundTrips(new ContextRetrievalRequestEvent("query", "vector-store", 5));
    assertEventRoundTrips(
            new ContextRetrievalResponseEvent(
                    requestId,
                    "query",
                    List.of(new Document("content", Map.of("source", "test"), "doc-1"))));
}

private static void assertEventRoundTrips(Event event) throws Exception {
    ActionState state = new ActionState(event);
    state.addEvent(event);

    ActionState restored = ActionStateSerde.deserialize(ActionStateSerde.serialize(state));

    assertEquals(event.getClass(), restored.getTaskEvent().getClass());
    assertEquals(event.getClass(), restored.getOutputEvents().get(0).getClass());
}

On the current code, the above fails with errors like:

Cannot construct instance of `org.apache.flink.agents.api.event.ChatRequestEvent`
(no Creators, like default constructor, exist): cannot deserialize from Object value
(no delegate- or property-based Creator)

Version and environment

0.3.0

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    affectVersion/0.1.0The bug affects the 0.1.0 version. The features are not needed.affectVersion/0.1.1The bug affects the 0.1.1 version. The features are not needed.affectVersion/0.2.0The bug affects the 0.2.0 version. The features are not needed.affectVersion/0.2.1affectVersion/0.3.0bug[Issue Type] Something isn't working as expected.fixVersion/0.3.1The feature or bug should be implemented/fixed in the 0.3.1 version.fixVersion/0.4.0priority/majorDefault priority of the PR or issue.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions