Cross Language Unified Event#631
Conversation
|
Hi @wenjin272, I closed the PR #561 and opened this. Do take a first pass when you get a chance. |
There was a problem hiding this comment.
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
PythonEventin java, unified inEvent - Subclasses of
Eventprovidefrom_eventmethod 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.
| private final String type; | ||
| private final Map<String, Object> attributes; | ||
| /** The timestamp of the source record. */ | ||
| private Long sourceTimestamp; |
There was a problem hiding this comment.
I think we can remove this field to keep the align between python Event and java Event
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
|
@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. |
aff5f51 to
744e498
Compare
|
@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. |
| @SuppressWarnings("unchecked") | ||
| public List<ChatMessage> getMessages() { | ||
| return messages; | ||
| return (List<ChatMessage>) getAttr("messages"); |
There was a problem hiding this comment.
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.
|
@wenjin272 I have addressed the comments related to |
Sorry for the delay in reviewing — I'll get to this PR today or tomorrow. |
wenjin272
left a comment
There was a problem hiding this comment.
Hi, @addu390, thanks for your work.
Personally, I think this PR is nearly complete. I left one remaining comment. However, this PR has far-reaching implications, so I’ll also ask others to review it.
The current commit history is somewhat disorganized, which may make it difficult for others to review. If possible, please reorganize the commits. For example, organize the changes into two commits: one for event refactoring and another for test and example adaptations. Python and Java can also be separated.
There was a problem hiding this comment.
I think this should be changed to ChatRequestEvent.fromEvent(event). The same applies to other actions, including built-in actions, as well as actions in tests and examples. I've checked, and it seems that everything on the Python side has been handled.
42660b0 to
71bce68
Compare
591b89e to
1389e87
Compare
|
@wenjin272 Thanks again for the quick reviews, I have addressed the comments and organized the commits, do have a look again! And agreed on the far-reaching implications, @xintongsong could you do a review as well? |
wenjin272
left a comment
There was a problem hiding this comment.
Hi, @addu390. Sorry for the delayed reply due to the holiday. I left a few comments regarding the details.
This PR introduces significant changes to the framework, please take a look at your convenience @xintongsong.
| @@ -41,7 +41,7 @@ public static Action getToolCallAction() throws Exception { | |||
| ToolCallAction.class, | |||
| "processToolRequest", | |||
| new Class[] {ToolRequestEvent.class, RunnerContext.class}), | |||
There was a problem hiding this comment.
Here, we constrain the first parameter of processToolRequest to be of type ToolRequestEvent. However, in cross-language invocations, only generic Event objects are passed rather than specific subclasses, which results in an argument type mismatch error.
I think this can be aligned with ChatModelAction by changing the parameter type to Event and calling ToolRequestEvent.fromEvent(event) within the function to convert the Event into a ToolRequestEvent.
There was a problem hiding this comment.
Yes, that's the pattern. I thought I fixed this in all places, I'll do another full sweep again.
| } | ||
| } | ||
| } | ||
| return new ChatRequestEvent(model, messages, event.getAttr("output_schema")); |
There was a problem hiding this comment.
Here, we do not copy the event's id. Therefore, ChatRequestEvent generates a new random id, which causes the same ChatRequestEvent to have different id on the sender and receiver sides.
This issue also exists for other built-in Event subclasses.
There was a problem hiding this comment.
The purpose of ActionState is to enable action-level recovery after a job failover. However, the HashMap storing attributes does not guarantee strict consistency in key order before and after a JVM restart, which may lead to inconsistent UUIDs.
After the Event refactoring, it seems that there is no distinction made for InputEvent here. I think we can unify it into
return String.valueOf(
UUID.nameUUIDFromBytes(MAPPER.writeValueAsBytes(event.getAttributes())));
And the MAPPER needs to have sorting enabled.
private static final ObjectMapper MAPPER =
JsonMapper.builder()
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.build();
There was a problem hiding this comment.
Nice find! Now that all event data lives in attributes, can unify the branches and use a sorted ObjectMapper for key ordering. Updated.
xintongsong
left a comment
There was a problem hiding this comment.
Thank you both for working on this. I have no more comments except for the following two.
| {{< tab "Python" >}} | ||
| ```python | ||
| @action(InputEvent) | ||
| @action("_input_event") |
There was a problem hiding this comment.
In the documentation, we should also use InputEvent.EVENT_TYPE rather than the string literal. Please also check for other occurrences in the documentation.
| @@ -41,7 +41,7 @@ public static Action getToolCallAction() throws Exception { | |||
| ToolCallAction.class, | |||
| "processToolRequest", | |||
| new Class[] {ToolRequestEvent.class, RunnerContext.class}), | |||
| """ | ||
|
|
||
| id: UUID = Field(default=None) | ||
| type: str | None = Field(default=None) |
There was a problem hiding this comment.
Why do we allow type to be None?
There was a problem hiding this comment.
Not anymore, I'll remove the None support.
|
@wenjin272 @xintongsong Did a full cross-language consistency sweep, all |
f4ee31e to
6b9ecb6
Compare
- Standardize Event on type strings and attributes map for cross-language interop - Add Event.fromEvent() base method; subclasses override for typed reconstruction - Store built-in event fields in attributes map with snake_case keys - Add @JsonIgnore on getters to prevent duplicate serialization - Move @JsonCreator type validation into Event constructor - Remove listenEvents() from @action annotation, use listenEventTypes() exclusively - Replace instanceof casts with EVENT_TYPE checks and fromEvent() in built-in actions - Remove EventContext.eventClass; simplify event log deserialization to base Event - Remove PythonEvent wrapper; use unified Event with JSON serde Co-authored-by: Cursor <cursoragent@cursor.com>
- Update all @action annotations from listenEvents to listenEventTypes with EVENT_TYPE - Replace direct event casts with fromEvent() in e2e agents and test assertions - Update JSON test resources for new event format (type strings, attributes map) - Update documentation examples to use string-based event types Co-authored-by: Cursor <cursoragent@cursor.com>
- Standardize Event on type strings and attributes dict for cross-language interop - Remove _type_registry auto-dispatch; require explicit SubClass.from_event() in actions - Add get_attr()/set_attr() to base Event; update property accessors to use them - Enforce string-only @action decorator arguments - Handle JSON round-trip for Row, MemoryRef, OutputSchema, and UUID keys - Remove cloudpickle dependency from event serialization path Co-authored-by: Cursor <cursoragent@cursor.com>
- Update all @action decorators to use EVENT_TYPE string constants - Replace direct event attribute access with SubClass.from_event() pattern - Add return type annotations to staticmethod actions for linting compliance - Update test assertions and JSON resources for new event format Co-authored-by: Cursor <cursoragent@cursor.com>
…s and ActionState ordering
6b9ecb6 to
3f52c6b
Compare
|
Hi, @addu390. Some Thanks for your contribution, merged. |
Linked issue: #424
Purpose of change
Add unified event support. Users can create events with a
typestring andattributesmap 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.
Eventclass (Java/Python) gainstype,attributes,getType()/get_type().@Actionannotation gainslistenEventTypes().More details about the design in the issue #424
Documentation
doc-neededdoc-not-neededdoc-included