Fix: OpenAI Node SDK Functions Not Working with Streaming
Error message
Functions not working with streamingDiagnosis
When using the OpenAI Node SDK with streaming enabled (stream: true), you may encounter a situation where the model correctly decides to call a function (the finish_reason is 'function_call'), but the response chunk contains an empty delta object with no function_call name or arguments. This is a known issue in the openai-node library, particularly in early v4 beta versions. The root cause is that the function call name and arguments are not included in the final chunk where the finish_reason is set; they are delivered in one or more preceding chunks that the developer may not be handling correctly.
What Causes This Error
1. The function call data arrives in earlier chunks, not the final one
The most common cause, confirmed by the GitHub issue discussion, is that the streaming API sends the function name and arguments incrementally across multiple chunks before the chunk that carries the finish_reason: 'function_call'. If your streaming loop only checks the delta object when finish_reason is present, you will miss the function call data entirely. The final chunk with finish_reason contains an empty delta: {}.
2. Using an outdated version of the openai-node library
The issue was originally reported against openai-node 4.0.0-beta.1. Later versions of the library may have improved handling, but the underlying API behavior remains the same: function call data is streamed incrementally. If you are on an old beta, upgrading may resolve some edge cases, but you still need to handle the streaming correctly.
3. Misunderstanding the streaming contract
The OpenAI streaming API for chat completions with functions sends the function name in one chunk and the arguments (as a JSON string) across potentially many chunks. If your code assumes the entire function call arrives in a single chunk, you will see incomplete or missing data.
4. Not accumulating the delta across chunks
If you are not accumulating the delta content across all chunks (including those with empty content but with function_call), you will lose the function name and arguments.
How to Fix It
Solution 1: Accumulate function call data across all chunks (Recommended)
This is the definitive fix, derived from the GitHub issue discussion and the official API behavior. Instead of only looking at the chunk where finish_reason is 'function_call', you must listen for delta.function_call in every chunk and accumulate the name and arguments.
Steps:
- Initialize variables to hold the function name and arguments before the stream loop.
- In each chunk, check if
delta.function_callexists. - If
delta.function_call.nameis present, set the function name. - If
delta.function_call.argumentsis present, append it to the accumulated arguments string. - When
finish_reasonis'function_call', use the accumulated name and arguments.
Example code (JavaScript/Node.js):
import OpenAI from 'openai';
const openai = new OpenAI();
async function streamWithFunctions() {
const stream = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'user', content: 'What is the weather in Boston?' }
],
functions: [
{
name: 'get_current_weather',
description: 'Get the current weather in a given location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and state, e.g. San Francisco, CA'
},
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location']
}
}
],
stream: true,
});
let functionName = '';
let functionArguments = '';
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.function_call) {
if (delta.function_call.name) {
functionName = delta.function_call.name;
}
if (delta.function_call.arguments) {
functionArguments += delta.function_call.arguments;
}
}
if (chunk.choices[0]?.finish_reason === 'function_call') {
// Now functionName and functionArguments are complete
console.log('Function name:', functionName);
console.log('Function arguments:', functionArguments);
// Parse arguments and execute the function
const args = JSON.parse(functionArguments);
// ... call your function with args
}
}
}
streamWithFunctions();
What to expect when it works:
- The first chunk(s) may contain
delta.function_call.namewith the function name. - Subsequent chunks will contain
delta.function_call.argumentswith partial JSON strings (e.g.,'{"location":','"Boston"','}'). - The final chunk will have
finish_reason: 'function_call'and an emptydelta: {}. - After the loop,
functionNamewill be the full function name andfunctionArgumentswill be the complete JSON arguments string.
Solution 2: Upgrade to the latest openai-node version
While not a complete fix on its own, upgrading from beta versions (like 4.0.0-beta.1) to the latest stable release may resolve some edge cases where the library itself mishandles the streaming. The official documentation and community reports indicate that the library has matured.
Steps:
- Check your current version:
npm list openai - Upgrade to the latest version:
npm install openai@latest - Verify the upgrade:
npm list openai
What to expect:
- The library will still stream function call data incrementally, so you must still implement Solution 1.
- However, newer versions may have better error handling and more consistent chunk delivery.
Solution 3: Use non-streaming as a fallback for function calls
If you cannot get streaming to work reliably with functions, you can fall back to non-streaming requests when you expect a function call. This is a workaround, not a fix, but it is mentioned in the community discussion as a pragmatic approach.
Steps:
- Make a non-streaming request first to determine if the model wants to call a function.
- If the response contains a
function_call, execute the function and then make a second request (streaming or non-streaming) with the function result. - If no function call is needed, proceed with streaming for the content.
Example code (JavaScript/Node.js):
import OpenAI from 'openai';
const openai = new OpenAI();
async function nonStreamingFallback() {
// First, non-streaming request to check for function call
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'user', content: 'What is the weather in Boston?' }
],
functions: [
{
name: 'get_current_weather',
description: 'Get the current weather in a given location',
parameters: {
type: 'object',
properties: {
location: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location']
}
}
],
stream: false,
});
const message = response.choices[0].message;
if (message.function_call) {
console.log('Function call detected:', message.function_call);
// Execute the function and then make a second request
const functionResult = executeFunction(message.function_call);
// Now you can stream the final response
const stream = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'user', content: 'What is the weather in Boston?' },
message,
{ role: 'function', name: message.function_call.name, content: JSON.stringify(functionResult) }
],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
} else {
// No function call, proceed with streaming directly
console.log(message.content);
}
}
function executeFunction(functionCall) {
// Your function execution logic here
return { temperature: 22, unit: 'celsius' };
}
What to expect:
- This approach avoids the streaming function call issue entirely.
- It adds an extra round trip, which increases latency.
- It is best used when you know the model will likely call a function.
Solution 4: Check for function_call in every chunk, not just when finish_reason is set
This is a more defensive version of Solution 1. Some developers have reported that the function_call data may appear in chunks that do not have a finish_reason at all. Your code should always check for delta.function_call regardless of the presence of finish_reason.
Steps:
- In your streaming loop, always check for
delta.function_call. - Accumulate the name and arguments as shown in Solution 1.
- Do not assume that the function call data is complete until you see
finish_reason: 'function_call'.
Example code (JavaScript/Node.js):
import OpenAI from 'openai';
const openai = new OpenAI();
async function defensiveStream() {
const stream = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'user', content: 'What is the weather in Boston?' }
],
functions: [
{
name: 'get_current_weather',
description: 'Get the current weather in a given location',
parameters: {
type: 'object',
properties: {
location: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location']
}
}
],
stream: true,
});
let functionName = '';
let functionArguments = '';
let isFunctionCall = false;
for await (const chunk of stream) {
const choice = chunk.choices[0];
if (!choice) continue;
const delta = choice.delta;
// Always check for function_call data
if (delta?.function_call) {
isFunctionCall = true;
if (delta.function_call.name) {
functionName = delta.function_call.name;
}
if (delta.function_call.arguments) {
functionArguments += delta.function_call.arguments;
}
}
// Handle regular content
if (delta?.content) {
process.stdout.write(delta.content);
}
// When finish_reason is function_call, we have all data
if (choice.finish_reason === 'function_call') {
console.log('\nFunction call complete:');
console.log('Name:', functionName);
console.log('Arguments:', functionArguments);
// Parse and execute
const args = JSON.parse(functionArguments);
// ...
}
}
if (isFunctionCall && !functionName) {
console.error('Warning: finish_reason was function_call but no name was accumulated.');
}
}
What to expect:
- This code will correctly capture function call data even if it arrives in chunks without
finish_reason. - It also handles the edge case where
finish_reasonis'function_call'but no data was accumulated (which would indicate a different bug).
If Nothing Works
If you have tried all the solutions above and the function call data is still missing, consider the following escalation paths:
1. Check the OpenAI API status
Visit the OpenAI Status Page to see if there is an ongoing incident affecting the chat completions API.
2. File a new issue on the openai-node GitHub repository
The original issue (Issue #188) was resolved in the discussion, but if you are experiencing a different variant, file a new issue at https://github.com/openai/openai-node/issues. Include:
- Your Node.js version
- Your openai-node library version
- A minimal reproducible code snippet
- The exact streaming chunks you receive (you can log them)
3. Contact OpenAI support
If you have a paid account, you can contact OpenAI support through the Help Center. Provide the same details as above.
4. Use the non-streaming workaround permanently
If streaming with functions is not critical for your use case, you can always use non-streaming requests for any conversation that might involve function calls. This is the most reliable approach, though it increases latency.
5. Consider using a different library or SDK
Some community-maintained SDKs may have better support for streaming with functions. However, the underlying API behavior is the same, so the issue is likely to persist unless the library handles the accumulation for you.
How to Prevent It
1. Always accumulate function call data across chunks
Make it a standard practice in your streaming code to accumulate function_call.name and function_call.arguments from every chunk, not just the final one. This is the single most important preventive measure.
2. Use the latest stable version of openai-node
Keep your library up to date. Run npm update openai regularly. The library may introduce helper methods or better documentation for streaming with functions.
3. Test with a simple function first
Before integrating complex functions, test with a minimal function (like get_current_weather) to ensure your streaming loop handles function calls correctly. Log every chunk to see the exact data structure.
4. Understand the streaming API contract
Read the OpenAI API documentation on streaming carefully. The key points:
- Function calls are streamed incrementally.
- The
namefield appears in the first chunk that contains the function call. - The
argumentsfield is a JSON string that may be split across multiple chunks. - The
finish_reasonis only set on the last chunk.
5. Use TypeScript for better type safety
If you are using TypeScript, the openai-node library provides types for streaming chunks. Use them to ensure you are accessing the correct properties. For example:
import OpenAI from 'openai';
const openai = new OpenAI();
async function streamWithFunctionsTS() {
const stream = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'user', content: 'What is the weather in Boston?' }
],
functions: [
{
name: 'get_current_weather',
description: 'Get the current weather in a given location',
parameters: {
type: 'object',
properties: {
location: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location']
}
}
],
stream: true,
});
let functionName = '';
let functionArguments = '';
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.function_call) {
if (delta.function_call.name) {
functionName = delta.function_call.name;
}
if (delta.function_call.arguments) {
functionArguments += delta.function_call.arguments;
}
}
if (chunk.choices[0]?.finish_reason === 'function_call') {
// Use the accumulated data
console.log(`Function: ${functionName}, Args: ${functionArguments}`);
}
}
}
6. Log chunks during development
During development, log every chunk to see the exact structure:
for await (const chunk of stream) {
console.log(JSON.stringify(chunk, null, 2));
// Your accumulation logic here
}
This will help you understand the streaming pattern and verify that your accumulation logic is correct.
7. Handle edge cases
- Empty arguments: If the function has no parameters, the arguments may be an empty string or
{}. Your code should handle this. - Multiple function calls: The API can return multiple function calls in a single response (though this is rare). Your code should handle accumulating multiple function calls if needed.
- Interrupted streams: If the stream is interrupted (network error, timeout), you may have partial function call data. Implement retry logic or discard partial data.
Conclusion
The issue of functions not working with streaming in the OpenAI Node SDK is caused by the streaming API delivering function call data incrementally across multiple chunks, with the final chunk only containing the finish_reason. By accumulating the function_call.name and function_call.arguments from every chunk, you can reliably capture the complete function call. Upgrade to the latest library version, test with simple functions, and log chunks during development to ensure your implementation is correct. If all else fails, fall back to non-streaming requests for function calls.
The #1 Chatgpt Newsletter
The most important chatgpt updates, guides, and fixes — one weekly email.
No spam, unsubscribe anytime. Privacy policy
Sources & References
This page was researched from 3 independent sources, combined and verified for completeness.
- 1.OpenAI Documentation — EmbeddingsOfficial documentation · primary source
- 2.Functions not working with streamingGitHub issue
- 3.Solution comment by mark-ramsey-ri (1 reactions)GitHub issue
Related Error Solutions
Keep exploring ChatGPT
ChatGPT resources
Latest AI answers
Skip the manual work
Ready-made AI workflows and automation templates — import and run instead of building from scratch.