Skip to content

Commit 6315bfc

Browse files
authored
fix: accept empty delta strings in content events (#1419)
Models like GPT-5 legitimately send empty deltas during streaming. Removed min_length=1 (Python) and .refine(s.length > 0) (TypeScript) from TextMessageContentEvent, ThinkingTextMessageContentEvent, and ReasoningMessageContentEvent. Empty deltas are harmless — concatenating an empty string is a no-op. Fixes #337
1 parent 173b44a commit 6315bfc

4 files changed

Lines changed: 16 additions & 17 deletions

File tree

sdks/python/ag_ui/core/events.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class TextMessageContentEvent(BaseEvent):
7777
"""
7878
type: Literal[EventType.TEXT_MESSAGE_CONTENT] = EventType.TEXT_MESSAGE_CONTENT # pyright: ignore[reportIncompatibleVariableOverride]
7979
message_id: str
80-
delta: str = Field(min_length=1)
80+
delta: str
8181

8282

8383
class TextMessageEndEvent(BaseEvent):
@@ -108,7 +108,7 @@ class ThinkingTextMessageContentEvent(BaseEvent):
108108
Event indicating a piece of a thinking text message.
109109
"""
110110
type: Literal[EventType.THINKING_TEXT_MESSAGE_CONTENT] = EventType.THINKING_TEXT_MESSAGE_CONTENT # pyright: ignore[reportIncompatibleVariableOverride]
111-
delta: str = Field(min_length=1)
111+
delta: str
112112

113113
class ThinkingTextMessageEndEvent(BaseEvent):
114114
"""
@@ -312,7 +312,7 @@ class ReasoningMessageContentEvent(BaseEvent):
312312
"""
313313
type: Literal[EventType.REASONING_MESSAGE_CONTENT] = EventType.REASONING_MESSAGE_CONTENT # pyright: ignore[reportIncompatibleVariableOverride]
314314
message_id: str
315-
delta: str = Field(min_length=1)
315+
delta: str
316316

317317

318318
class ReasoningMessageEndEvent(BaseEvent):

sdks/python/tests/test_events.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -424,14 +424,13 @@ def test_event_union_deserialization(self):
424424
self.assertEqual(event.type.value, data["type"])
425425
self.assertEqual(event.timestamp, data["timestamp"])
426426

427-
def test_validation_constraints(self):
428-
"""Test validation constraints for different event types"""
429-
# TextMessageContentEvent delta cannot be empty
430-
with self.assertRaises(ValueError):
431-
TextMessageContentEvent(
432-
message_id="msg_123",
433-
delta="" # Empty delta, should fail
434-
)
427+
def test_empty_delta_accepted(self):
428+
"""Models like GPT-5 legitimately send empty deltas during streaming"""
429+
event = TextMessageContentEvent(
430+
message_id="msg_123",
431+
delta=""
432+
)
433+
self.assertEqual(event.delta, "")
435434

436435
def test_serialization_round_trip(self):
437436
"""Test serialization and deserialization for different event types"""

sdks/typescript/packages/core/src/__tests__/event-factories.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ describe("event factories", () => {
4343
expect(event.role).toBe("user");
4444
});
4545

46-
it("rejects empty deltas in TEXT_MESSAGE_CONTENT", () => {
47-
expect(() => createTextMessageContentEvent({ messageId: "msg-3", delta: "" })).toThrow(
48-
/Delta must not be an empty string/,
49-
);
46+
it("accepts empty deltas in TEXT_MESSAGE_CONTENT", () => {
47+
const event = createTextMessageContentEvent({ messageId: "msg-3", delta: "" });
48+
expect(event.type).toBe(EventType.TEXT_MESSAGE_CONTENT);
49+
expect(event.delta).toBe("");
5050
});
5151

5252
it("creates TEXT_MESSAGE_CONTENT when delta provided", () => {

sdks/typescript/packages/core/src/events.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const TextMessageStartEventSchema = BaseEventSchema.extend({
7878
export const TextMessageContentEventSchema = BaseEventSchema.extend({
7979
type: z.literal(EventType.TEXT_MESSAGE_CONTENT),
8080
messageId: z.string(),
81-
delta: z.string().refine((s) => s.length > 0, "Delta must not be an empty string"),
81+
delta: z.string(),
8282
});
8383

8484
export const TextMessageEndEventSchema = BaseEventSchema.extend({
@@ -260,7 +260,7 @@ export const ReasoningMessageStartEventSchema = BaseEventSchema.extend({
260260
export const ReasoningMessageContentEventSchema = BaseEventSchema.extend({
261261
type: z.literal(EventType.REASONING_MESSAGE_CONTENT),
262262
messageId: z.string(),
263-
delta: z.string().refine((s) => s.length > 0, "Delta must not be an empty string"),
263+
delta: z.string(),
264264
});
265265

266266
export const ReasoningMessageEndEventSchema = BaseEventSchema.extend({

0 commit comments

Comments
 (0)