Skip to content

Cross Language Unified Event#631

Open
addu390 wants to merge 29 commits intoapache:mainfrom
addu390:feature/custom-event-def-v3
Open

Cross Language Unified Event#631
addu390 wants to merge 29 commits intoapache:mainfrom
addu390:feature/custom-event-def-v3

Conversation

@addu390
Copy link
Copy Markdown
Contributor

@addu390 addu390 commented Apr 15, 2026

Linked issue: #424

Purpose of change

Add unified event support. Users can create events with a type string and attributes map instead of defining subclasses. Enables string-based event routing and JSON-based cross-language transport. Migrates/removes backward compatible with existing subclassed events.

Tests

New unit tests in Java (EventTest, EventLogRecordJsonSerdeTest) and Python (test_event, test_decorators, test_agent_plan, test_local_execution_environment) covering unified event creation, serialization, routing, and backward compatibility.

API

Yes. Event class (Java/Python) gains type, attributes, getType()/get_type(). @Action annotation gains listenEventTypes().

More details about the design in the issue #424

Documentation

  • doc-needed
  • doc-not-needed
  • doc-included

@github-actions github-actions Bot added doc-needed Your PR changes impact docs. fixVersion/0.3.0 The feature or bug should be implemented/fixed in the 0.3.0 version. priority/major Default priority of the PR or issue. labels Apr 15, 2026
@addu390 addu390 changed the title Feature/custom event def v3 Cross Language Unified Event Apr 15, 2026
@github-actions github-actions Bot added doc-needed Your PR changes impact docs. and removed doc-needed Your PR changes impact docs. labels Apr 15, 2026
@addu390 addu390 marked this pull request as ready for review April 16, 2026 00:24
@addu390
Copy link
Copy Markdown
Contributor Author

addu390 commented Apr 16, 2026

Hi @wenjin272, I closed the PR #561 and opened this.
I'll clean-up the PR a bit more, but up for a review otherwise.

Do take a first pass when you get a chance.

Copy link
Copy Markdown
Collaborator

@wenjin272 wenjin272 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @addu390, thanks for you work. Refactoring the Event will have a significant impact on the framework; therefore, this work requires careful design and attention to numerous details. Thanks Again.

I think though some details still need to be addressed, the current Python event design aligns with my expectations, including

  • action listen to event.type
  • json based DeSer
  • remove PythonEvent in java, unified in Event
  • Subclasses of Event provide from_event method to validate and reconstruct specific event object.

However, the Java event design does not appear to be aligned with the Python approach. In fact, after completing the Event refactoring, we will continue to support cross-language usage of Actions. This means a Python event sent by a Python action may be delivered to a Java action. Therefore, Python events and Java events must maintain consistency in both design and usage.

By the way, there are some conflicts between the PR and the main branch, likely because the main branch was recently reformatted.

* @return Array of Event classes that this action listens to
*/
Class<? extends Event>[] listenEvents();
Class<? extends Event>[] listenEvents() default {};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Python side, actions only support listening to event string identifiers; on the Java side, we should align with this behavior.

* @param eventJson JSON string with at least a {@code "type"} field
* @throws IOException if JSON parsing fails
*/
public void sendUnifiedEvent(String eventJson) throws IOException {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name sendUnifiedEvent is a bit ambiguous. I suggest renaming it to sendEventJson or sentPythonEvent.

/** Returns the event type string used for routing. */
@JsonIgnore
public String getType() {
return type != null ? type : this.getClass().getName();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can directly require that the type is not null, which would simplify the implementation of Event.

private final String type;
private final Map<String, Object> attributes;
/** The timestamp of the source record. */
private Long sourceTimestamp;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this field to keep the align between python Event and java Event

Copy link
Copy Markdown
Contributor Author

@addu390 addu390 Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sourceTimestamp seems to be used in runtime for timestamp propagation (ActionExecutionOperator, RunnerContextImpl, JavaActionTask, etc.). Safe to remove?

I could however, specifically not use in the cross-language event contract, without any repercussions.

Comment thread python/flink_agents/api/events/chat_event.py Outdated
attributes={
"model": model,
"messages": messages,
"output_schema": output_schema,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to ensure consistency between the built-in events on the Python and Java sides, so we must make the same changes on the Java side.

Comment thread python/flink_agents/api/events/event.py Outdated
Key-value properties for the event data.
"""

_type_registry: ClassVar[Dict[str, Type["Event"]]] = {}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that this variable is intended to handle the deserialization of subclasses, but it is not currently in use. I think the Event class doesn't need to handle this; deserialization of subclass objects can be managed within the subclass's from_event method. Because subclasses may have certain non-trivial fields that require special handling during deserialization.


def test_action_decorator() -> None: # noqa D103
@action(InputEvent)
@action("_input_event")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use InputEvent.EVENT_TYPE, and the same applies to other places where @action is used.

err_msg = (
"Failed to send event '"
+ event.get_type()
+ "' to runner context"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can also add the event json in err_msg.

event = Event.from_json(event_json)
event_class = Event._type_registry.get(event.type)
if event_class is not None and hasattr(event_class, "from_event"):
return event_class.from_event(event)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comment in Event, the framework does not need to be aware of the subclass types of Event. This would require the framework to handle many details of serialization and deserialization.
Fro subclass types of Event, user need use SubClass.from_event() to reconstruct the specific event object in action, like what you do in built-in tool call action and chat action.

@addu390
Copy link
Copy Markdown
Contributor Author

addu390 commented Apr 17, 2026

@wenjin272 Agreed on consistency between Java and Python, the reasoning was that it was quite a larger change, so, was considering having a follow-up PR.
However, It's a fair call-out, let me actually just do that too in this PR, doing so covers most of the review comments too.

@addu390 addu390 force-pushed the feature/custom-event-def-v3 branch from aff5f51 to 744e498 Compare April 17, 2026 23:51
@addu390 addu390 requested a review from wenjin272 April 19, 2026 19:29
@addu390
Copy link
Copy Markdown
Contributor Author

addu390 commented Apr 19, 2026

@wenjin272 Thanks again for the review! I’ve addressed the broader concern of consistency between Python and Java implementations, along side other review comments. Please take another look.

Copy link
Copy Markdown
Collaborator

@wenjin272 wenjin272 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @addu390. I left some comments, mainly because Java's Event lacks the fromEvent method.

@SuppressWarnings("unchecked")
public List<ChatMessage> getMessages() {
return messages;
return (List<ChatMessage>) getAttr("messages");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the python ChatRequestEvent, we deserialize the messages field in from_event method. But the java Event is missing corresponding method. This may cause ClassCastException.


/** Base class for all event types in the system. */
public abstract class Event {
public class Event {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The python Event contains a class method from_event. In this method, we will perform field validation and deserialization. However, Java's event lacks the corresponding method.

public static Event fromJson(String json) throws IOException {
Event event = MAPPER.readValue(json, Event.class);
if (event.type == null || event.type.isEmpty()) {
throw new IllegalArgumentException("Event JSON must contain a 'type' field.");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can move this validation to the construct method annotated @JsonCreator. This ensures that all code paths constructing an Event will enforce this validation.

* The fully qualified Java class name used for deserialization. For unified events (no
* subclass), this is {@code "org.apache.flink.agents.api.Event"}.
*/
private final String eventClass;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think EventContext does not need to know the specific event class.

This information will enable us to restore the specific subclass type when reconstructing events from the event log. However, I believe there is no requirement to deserialize events from the event log. Furthermore, being aware of specific event classes will make the framework's implementation more complex.

I think that if users utilize event subclasses instead of the base event directly, they should be responsible for handling event deserialization, specifically by uniformly using from_event/fromEvent.

@addu390 addu390 requested a review from wenjin272 April 22, 2026 15:47
@addu390
Copy link
Copy Markdown
Contributor Author

addu390 commented Apr 23, 2026

@wenjin272 I have addressed the comments related to fromEvent, can you take an other pass when you get chance?

@wenjin272
Copy link
Copy Markdown
Collaborator

@wenjin272 I have addressed the comments related to fromEvent, can you take an other pass when you get chance?

Sorry for the delay in reviewing — I'll get to this PR today or tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc-needed Your PR changes impact docs. fixVersion/0.3.0 The feature or bug should be implemented/fixed in the 0.3.0 version. priority/major Default priority of the PR or issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants