Skip to content

Commit 0da7652

Browse files
committed
fix: Leading whitespace chunks break partial parser
1 parent fb61fc2 commit 0da7652

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

src/lib/ChatCompletionStream.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,9 @@ export class ChatCompletionStream<ParsedT = null>
509509
choice.message.content = (choice.message.content || '') + content;
510510

511511
if (!choice.message.refusal && this.#getAutoParseableResponseFormat()) {
512-
choice.message.parsed = partialParse(choice.message.content);
512+
// Even a partial parser does not accept empty string
513+
const trimmed = choice.message.content.trimStart();
514+
choice.message.parsed = trimmed ? partialParse(trimmed) : null;
513515
}
514516
}
515517

tests/lib/ChatCompletionStream.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,47 @@ describe('.stream()', () => {
4545
`);
4646
});
4747

48+
it('is robust against leading newline chunks', async () => {
49+
const stream = await makeStreamSnapshotRequest((openai) =>
50+
openai.beta.chat.completions.stream({
51+
model: 'gpt-4o-2024-08-06',
52+
messages: [
53+
{
54+
role: 'user',
55+
content: "What's the weather like in SF?",
56+
},
57+
],
58+
response_format: zodResponseFormat(
59+
z.object({
60+
city: z.string(),
61+
units: z.enum(['c', 'f']).default('f'),
62+
}),
63+
'location',
64+
),
65+
}),
66+
);
67+
68+
expect((await stream.finalChatCompletion()).choices[0]).toMatchInlineSnapshot(`
69+
{
70+
"finish_reason": "stop",
71+
"index": 0,
72+
"logprobs": null,
73+
"message": {
74+
"content": "
75+
76+
{"city":"San Francisco","units":"c"}",
77+
"parsed": {
78+
"city": "San Francisco",
79+
"units": "c",
80+
},
81+
"refusal": null,
82+
"role": "assistant",
83+
"tool_calls": [],
84+
},
85+
}
86+
`);
87+
});
88+
4889
it('emits content logprobs events', async () => {
4990
var capturedLogProbs: ChatCompletionTokenLogprob[] | undefined;
5091

tests/lib/__snapshots__/ChatCompletionStream.test.ts.snap

+34
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,37 @@ data: [DONE]
9999
100100
"
101101
`;
102+
103+
exports[`.stream() is robust against leading newline chunks 1`] = `
104+
"data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
105+
106+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\\n\\n"},"logprobs":null,"finish_reason":null}]}
107+
108+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"{\\""},"logprobs":null,"finish_reason":null}]}
109+
110+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
111+
112+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\\":\\""},"logprobs":null,"finish_reason":null}]}
113+
114+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
115+
116+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
117+
118+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\\",\\""},"logprobs":null,"finish_reason":null}]}
119+
120+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
121+
122+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\\":\\""},"logprobs":null,"finish_reason":null}]}
123+
124+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"c"},"logprobs":null,"finish_reason":null}]}
125+
126+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{"content":"\\"}"},"logprobs":null,"finish_reason":null}]}
127+
128+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
129+
130+
data: {"id":"chatcmpl-9tZXEmwtoDf6vqCqEWSvDP8jx9OXe","object":"chat.completion.chunk","created":1723031664,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_845eaabc1f","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":10,"total_tokens":27}}
131+
132+
data: [DONE]
133+
134+
"
135+
`;

0 commit comments

Comments
 (0)