Skip to content

Commit e249419

Browse files
committed
test: add comprehensive shape validation for fullChatStream events
- Validate all ChatStreamEvent types (content.delta, message.complete, tool.preliminary_result, pass-through) - Check required fields and types for each event type - Test content.delta has proper delta string - Test tool.preliminary_result has toolCallId and result - Use generator tool to test preliminary results when available
1 parent ed1a823 commit e249419

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed

tests/e2e/callModel.test.ts

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,189 @@ describe("callModel E2E Tests", () => {
627627
const hasContentDeltas = chunks.some((c) => c.type === "content.delta");
628628
expect(hasContentDeltas).toBe(true);
629629
}, 15000);
630+
631+
it("should return events with correct shape for each event type", async () => {
632+
const response = client.callModel({
633+
model: "meta-llama/llama-3.2-1b-instruct",
634+
input: [
635+
{
636+
role: "user",
637+
content: "Count from 1 to 3.",
638+
},
639+
],
640+
});
641+
642+
let hasContentDelta = false;
643+
let hasMessageComplete = false;
644+
645+
for await (const event of response.getFullChatStream()) {
646+
// Every event must have a type
647+
expect(event).toHaveProperty("type");
648+
expect(typeof event.type).toBe("string");
649+
expect(event.type.length).toBeGreaterThan(0);
650+
651+
// Validate shape based on event type
652+
switch (event.type) {
653+
case "content.delta":
654+
hasContentDelta = true;
655+
// Must have delta property
656+
expect(event).toHaveProperty("delta");
657+
expect(typeof event.delta).toBe("string");
658+
// Delta can be empty string but must be string
659+
break;
660+
661+
case "message.complete":
662+
hasMessageComplete = true;
663+
// Must have response property
664+
expect(event).toHaveProperty("response");
665+
expect(event.response).toBeDefined();
666+
// Response should be an object (the full response)
667+
expect(typeof event.response).toBe("object");
668+
expect(event.response).not.toBeNull();
669+
break;
670+
671+
case "tool.preliminary_result":
672+
// Must have toolCallId and result
673+
expect(event).toHaveProperty("toolCallId");
674+
expect(event).toHaveProperty("result");
675+
expect(typeof event.toolCallId).toBe("string");
676+
expect(event.toolCallId.length).toBeGreaterThan(0);
677+
// result can be any type
678+
break;
679+
680+
default:
681+
// Pass-through events must have event property
682+
expect(event).toHaveProperty("event");
683+
expect(event.event).toBeDefined();
684+
break;
685+
}
686+
}
687+
688+
// Should have at least content deltas for a text response
689+
expect(hasContentDelta).toBe(true);
690+
}, 15000);
691+
692+
it("should validate content.delta events have proper structure", async () => {
693+
const response = client.callModel({
694+
model: "meta-llama/llama-3.2-1b-instruct",
695+
input: [
696+
{
697+
role: "user",
698+
content: "Say 'hello world'.",
699+
},
700+
],
701+
});
702+
703+
const contentDeltas: any[] = [];
704+
705+
for await (const event of response.getFullChatStream()) {
706+
if (event.type === "content.delta") {
707+
contentDeltas.push(event);
708+
709+
// Validate exact shape
710+
const keys = Object.keys(event);
711+
expect(keys).toContain("type");
712+
expect(keys).toContain("delta");
713+
714+
// type must be exactly "content.delta"
715+
expect(event.type).toBe("content.delta");
716+
717+
// delta must be a string
718+
expect(typeof event.delta).toBe("string");
719+
}
720+
}
721+
722+
expect(contentDeltas.length).toBeGreaterThan(0);
723+
724+
// Concatenated deltas should form readable text
725+
const fullText = contentDeltas.map(e => e.delta).join("");
726+
expect(fullText.length).toBeGreaterThan(0);
727+
}, 15000);
728+
729+
it("should include tool.preliminary_result events with correct shape when generator tools are executed", async () => {
730+
const response = client.callModel({
731+
model: "openai/gpt-4o-mini",
732+
input: [
733+
{
734+
role: "user",
735+
content: "What time is it? Use the get_time tool.",
736+
},
737+
],
738+
tools: [
739+
{
740+
type: ToolType.Function,
741+
function: {
742+
name: "get_time",
743+
description: "Get current time",
744+
inputSchema: z.object({
745+
timezone: z.string().optional().describe("Timezone"),
746+
}),
747+
// Generator tools need eventSchema for intermediate results
748+
eventSchema: z.object({
749+
status: z.string(),
750+
}),
751+
outputSchema: z.object({
752+
time: z.string(),
753+
timezone: z.string(),
754+
}),
755+
// Use generator function to emit preliminary results
756+
execute: async function* (params: { timezone?: string }) {
757+
// Emit preliminary result (validated against eventSchema)
758+
yield { status: "fetching time..." };
759+
760+
// Final result (validated against outputSchema)
761+
yield {
762+
time: "14:30:00",
763+
timezone: params.timezone || "UTC",
764+
};
765+
},
766+
},
767+
},
768+
],
769+
});
770+
771+
let hasPreliminaryResult = false;
772+
const preliminaryResults: any[] = [];
773+
774+
for await (const event of response.getFullChatStream()) {
775+
expect(event).toHaveProperty("type");
776+
expect(typeof event.type).toBe("string");
777+
778+
if (event.type === "tool.preliminary_result") {
779+
hasPreliminaryResult = true;
780+
preliminaryResults.push(event);
781+
782+
// Validate exact shape
783+
expect(event).toHaveProperty("toolCallId");
784+
expect(event).toHaveProperty("result");
785+
786+
// toolCallId must be non-empty string
787+
expect(typeof event.toolCallId).toBe("string");
788+
expect(event.toolCallId.length).toBeGreaterThan(0);
789+
790+
// result is defined
791+
expect(event.result).toBeDefined();
792+
}
793+
}
794+
795+
// Validate that if we got preliminary results, they have the correct shape
796+
if (hasPreliminaryResult) {
797+
expect(preliminaryResults.length).toBeGreaterThan(0);
798+
799+
// Should have status update or final result
800+
const hasStatusUpdate = preliminaryResults.some(
801+
(e) => e.result && typeof e.result === "object" && "status" in e.result
802+
);
803+
const hasFinalResult = preliminaryResults.some(
804+
(e) => e.result && typeof e.result === "object" && "time" in e.result
805+
);
806+
807+
expect(hasStatusUpdate || hasFinalResult).toBe(true);
808+
}
809+
810+
// The stream should complete without errors regardless of tool execution
811+
expect(true).toBe(true);
812+
}, 30000);
630813
});
631814

632815
describe("Multiple concurrent consumption patterns", () => {

0 commit comments

Comments
 (0)