Learn how to fix the "Cannot read property choices of undefined" error in OpenAI Node.js by checking API keys, handling errors, validating responses, and using the correct SDK version.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
The "Cannot read property choices of undefined" error in the OpenAI node typically occurs when your code attempts to access the 'choices' property of an undefined response object. This usually happens when the API call fails to return the expected structure, often due to incorrect API parameters, network issues, or when the OpenAI service returns an error instead of the expected response.
Comprehensive Guide to Fixing "Cannot read property choices of undefined" in OpenAI Node
Step 1: Understand the Error
The error "Cannot read property choices of undefined" means your code is trying to access the 'choices' property on an object that is undefined. In the context of OpenAI API:
Step 2: Check Your OpenAI API Key
One common cause is an invalid or expired API key:
// Ensure your API key is correctly set
const { Configuration, OpenAIApi } = require("openai");
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY, // Make sure this environment variable is set correctly
});
const openai = new OpenAIApi(configuration);
To verify your API key:
Step 3: Implement Proper Error Handling
Always use try/catch blocks when making API calls:
async function generateResponse() {
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50
});
// Check if response exists and has the expected structure
if (response && response.data && response.data.choices) {
return response.data.choices[0].text;
} else {
console.error("Response structure is not as expected:", response);
return "Error: Unexpected response structure";
}
} catch (error) {
console.error("OpenAI API error:", error);
return "Error calling OpenAI API";
}
}
Step 4: Check Response Structure
Different OpenAI API endpoints return different response structures. Ensure you're accessing the correct properties:
// For completions API
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50
});
// Correct access pattern
const result = response.data.choices[0].text;
} catch (error) {
console.error(error);
}
// For chat completions API
try {
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{"role": "user", "content": "Hello, world!"}]
});
// Different access pattern
const result = response.data.choices[0].message.content;
} catch (error) {
console.error(error);
}
Step 5: Debug the Response Object
Add debugging code to inspect the full response:
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50
});
// Debug the full response structure
console.log("Full response:", JSON.stringify(response, null, 2));
// Cautiously access the choices property
if (response && response.data && response.data.choices && response.data.choices.length > 0) {
return response.data.choices[0].text;
} else {
console.error("Invalid response structure");
return null;
}
} catch (error) {
console.error("API call error:", error);
return null;
}
Step 6: Check for API Version Compatibility
The OpenAI Node.js library has evolved over time. Make sure you're using the correct version:
// For newer versions (openai >= 3.0.0)
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function generateText() {
try {
const completion = await openai.completions.create({
model: "text-davinci-003",
prompt: "Hello, world!",
});
// New structure
return completion.choices[0].text;
} catch (error) {
console.error("Error:", error);
return null;
}
}
// For older versions (openai < 3.0.0)
import { Configuration, OpenAIApi } from "openai";
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
async function generateText() {
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
});
// Old structure
return response.data.choices[0].text;
} catch (error) {
console.error("Error:", error);
return null;
}
}
Step 7: Handle Axios Response Structure (for older versions)
If you're using an older version of the OpenAI library (before v3.0.0), the response is wrapped in an Axios response object:
const { Configuration, OpenAIApi } = require("openai");
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
async function generateText() {
try {
// The response is an Axios response object
const axiosResponse = await openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
});
// The actual OpenAI response is in response.data
const openaiResponse = axiosResponse.data;
// Now we can safely access the choices
if (openaiResponse && openaiResponse.choices && openaiResponse.choices.length > 0) {
return openaiResponse.choices[0].text;
} else {
console.error("Unexpected response structure:", openaiResponse);
return null;
}
} catch (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error("API error:", error.response.data);
} else if (error.request) {
// The request was made but no response was received
console.error("No response received:", error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error("Error:", error.message);
}
return null;
}
}
Step 8: Check for Rate Limiting and Quota Issues
Your API calls might fail if you hit rate limits or exceed your quota:
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50
});
return response.data.choices[0].text;
} catch (error) {
if (error.response) {
const status = error.response.status;
const data = error.response.data;
if (status === 429) {
console.error("Rate limit exceeded or quota exceeded:", data);
// Implement backoff strategy or user notification
return "Rate limit reached. Please try again later.";
} else {
console.error(`API error (${status}):`, data);
return `Error: ${data.error.message || "Unknown API error"}`;
}
} else {
console.error("Network or client error:", error);
return "Error connecting to OpenAI";
}
}
Step 9: Implement Request Validation
Validate your request parameters before sending:
function validateCompletionRequest(params) {
const errors = [];
if (!params.model) {
errors.push("Missing 'model' parameter");
}
if (!params.prompt && !params.messages) {
errors.push("Either 'prompt' or 'messages' must be provided");
}
if (params.max_tokens && (typeof params.max_tokens !== 'number' || params.max\_tokens < 1)) {
errors.push("'max\_tokens' must be a positive number");
}
return errors;
}
async function safeCreateCompletion(params) {
const validationErrors = validateCompletionRequest(params);
if (validationErrors.length > 0) {
console.error("Validation errors:", validationErrors);
throw new Error(`Invalid request parameters: ${validationErrors.join(", ")}`);
}
try {
const response = await openai.createCompletion(params);
return response.data.choices[0].text;
} catch (error) {
console.error("API error:", error);
throw error;
}
}
Step 10: Use a Wrapper Function with Default Fallback
Create a wrapper function that provides a default result if the API call fails:
async function safeOpenAICall(apiCallFn, defaultResult = null) {
try {
const result = await apiCallFn();
// Safely navigate the response object
if (result &&
result.data &&
result.data.choices &&
result.data.choices.length > 0) {
return result.data.choices[0].text;
} else {
console.warn("Unexpected API response structure:", result);
return defaultResult;
}
} catch (error) {
console.error("OpenAI API error:", error);
return defaultResult;
}
}
// Usage example
const response = await safeOpenAICall(
() => openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50
}),
"Sorry, I couldn't generate a response at this time."
);
Step 11: Check for Specific Model Compatibility
Different models require different API endpoints and have different response structures:
async function generateWithCorrectEndpoint(prompt, model) {
try {
let response;
// Chat models use createChatCompletion
if (model.includes("gpt-3.5-turbo") || model.includes("gpt-4")) {
response = await openai.createChatCompletion({
model: model,
messages: [{ role: "user", content: prompt }],
});
return response.data.choices[0].message.content;
}
// Completion models use createCompletion
else if (model.includes("text-davinci") || model.includes("text-curie") ||
model.includes("text-babbage") || model.includes("text-ada")) {
response = await openai.createCompletion({
model: model,
prompt: prompt,
max\_tokens: 100,
});
return response.data.choices[0].text;
}
// Handle other model types like DALL-E, embeddings, etc.
else {
throw new Error(`Unsupported model: ${model}`);
}
} catch (error) {
console.error(`Error with model ${model}:`, error);
return null;
}
}
Step 12: Update to the Latest OpenAI SDK Version
The newest OpenAI SDK (v3+) has a different structure that's more reliable:
// Install the latest version:
// npm install openai@latest
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function generateText() {
try {
const completion = await openai.completions.create({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50,
});
// New structure is simpler and less prone to the undefined error
if (completion.choices && completion.choices.length > 0) {
return completion.choices[0].text;
} else {
console.error("Unexpected response structure:", completion);
return null;
}
} catch (error) {
if (error instanceof OpenAI.APIError) {
console.error("OpenAI API Error:", error);
// The new SDK provides better error types
if (error.status === 429) {
return "Rate limit exceeded. Please try again later.";
}
} else {
console.error("Unexpected error:", error);
}
return null;
}
}
Step 13: Implement a Retry Mechanism
For intermittent failures, implement a retry mechanism with exponential backoff:
async function retryOpenAICall(apiCallFn, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await apiCallFn();
if (response &&
response.data &&
response.data.choices &&
response.data.choices.length > 0) {
return response.data.choices[0].text;
} else {
console.warn(`Attempt ${attempt}: Unexpected response structure`);
// Continue to retry
}
} catch (error) {
lastError = error;
// Don't retry if it's a 400 error (bad request)
if (error.response && error.response.status === 400) {
console.error("Bad request, not retrying:", error.response.data);
throw error;
}
// For rate limiting (429) or server errors (5xx), retry with backoff
const retryAfter = error.response?.headers?.['retry-after'] || Math.pow(2, attempt) \* 1000;
console.warn(`Attempt ${attempt} failed, retrying in ${retryAfter}ms:`, error.message);
// Wait before the next retry
await new Promise(resolve => setTimeout(resolve, retryAfter));
}
}
console.error(`Failed after ${maxRetries} attempts`, lastError);
throw lastError || new Error(`Failed after ${maxRetries} attempts`);
}
// Usage
try {
const text = await retryOpenAICall(() =>
openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50
})
);
console.log("Generated text:", text);
} catch (error) {
console.error("All retries failed:", error);
}
Step 14: Check Network Connectivity
Ensure your application has proper internet connectivity:
const axios = require('axios');
async function checkConnectivity() {
try {
// Try to reach OpenAI's API endpoint
await axios.get('https://api.openai.com/v1/models', {
timeout: 5000, // 5 second timeout
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
}
});
return true;
} catch (error) {
console.error("Connectivity check failed:", error.message);
return false;
}
}
async function safeOpenAICall() {
// First check connectivity
const isConnected = await checkConnectivity();
if (!isConnected) {
console.error("Network connectivity issues detected. Cannot reach OpenAI API.");
return "Network connectivity issues. Please check your internet connection.";
}
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: "Hello, world!",
max\_tokens: 50
});
return response.data.choices[0].text;
} catch (error) {
console.error("API call failed:", error);
return null;
}
}
Step 15: Use TypeScript for Better Type Safety
If you're using TypeScript, leverage type checking to avoid these errors:
import { Configuration, OpenAIApi } from "openai";
// Define expected response types
interface OpenAIChoice {
text: string;
index: number;
logprobs: null | any;
finish\_reason: string;
}
interface OpenAIResponse {
id: string;
object: string;
created: number;
model: string;
choices: OpenAIChoice[];
usage: {
prompt\_tokens: number;
completion\_tokens: number;
total\_tokens: number;
};
}
async function generateText(prompt: string): Promise {
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt,
max\_tokens: 50,
});
// TypeScript helps ensure the response has the expected structure
const data = response.data as OpenAIResponse;
if (!data.choices || data.choices.length === 0) {
console.error("No choices in response:", data);
return null;
}
return data.choices[0].text;
} catch (error) {
console.error("Error generating text:", error);
return null;
}
}
Step 16: Create a Comprehensive Error Handling Utility
Build a comprehensive error handling utility specifically for OpenAI API calls:
class OpenAIErrorHandler {
static async safeExecute(apiCallFn, options = {}) {
const {
maxRetries = 3,
retryableStatuses = [429, 500, 502, 503, 504],
defaultResponse = null,
timeout = 30000,
onError = null,
} = options;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
// Add timeout to the API call
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await Promise.race([
apiCallFn({ signal: controller.signal }),
new Promise((\_, reject) =>
setTimeout(() => reject(new Error("API call timed out")), timeout)
)
]);
clearTimeout(timeoutId);
// Validate the response structure
if (response &&
response.data &&
response.data.choices &&
response.data.choices.length > 0) {
// Check if the API returned an error in a successful response
if (response.data.error) {
throw new Error(`API returned error: ${response.data.error.message}`);
}
return response.data.choices[0].text;
} else {
console.warn(`Attempt ${attempt}: Unexpected response structure`, response);
// Continue to retry
}
} catch (error) {
lastError = error;
// Custom error handler if provided
if (onError) {
await onError(error, attempt);
}
// Check if we should retry based on error type
const status = error.response?.status;
if (error.name === 'AbortError') {
console.error(`Attempt ${attempt}: Request timed out`);
} else if (status && !retryableStatuses.includes(status)) {
// Don't retry client errors except specified ones
console.error(`Error not retryable (${status}):`, error.response?.data || error.message);
throw error;
} else {
// For retryable errors, implement exponential backoff
const retryAfter = error.response?.headers?.['retry-after'] ||
Math.min(Math.pow(2, attempt) \* 1000, 30000); // Max 30s
console.warn(`Attempt ${attempt} failed, retrying in ${retryAfter}ms:`,
error.response?.data?.error?.message || error.message);
await new Promise(resolve => setTimeout(resolve, retryAfter));
}
}
}
console.error(`Failed after ${maxRetries} attempts`, lastError);
if (defaultResponse !== undefined) {
return defaultResponse;
}
throw lastError || new Error(`Failed after ${maxRetries} attempts`);
}
}
// Usage example
async function generateTextSafely(prompt) {
return await OpenAIErrorHandler.safeExecute(
() => openai.createCompletion({
model: "text-davinci-003",
prompt: prompt,
max\_tokens: 100
}),
{
maxRetries: 3,
defaultResponse: "I couldn't generate a response at this time.",
onError: (error, attempt) => {
// Log to monitoring service or take other actions
console.error(`API Error on attempt ${attempt}:`, error);
}
}
);
}
Step 17: Verify the Model Availability
Ensure you're using a model that's available in your account:
async function checkModelAvailability(modelName) {
try {
const response = await openai.listModels();
const availableModels = response.data.data;
const modelExists = availableModels.some(model => model.id === modelName);
if (!modelExists) {
console.warn(`Model "${modelName}" not found in available models`);
return false;
}
return true;
} catch (error) {
console.error("Error checking model availability:", error);
return false;
}
}
async function generateWithFallbackModels(prompt) {
// Try models in order of preference
const modelPreference = [
"gpt-4",
"gpt-3.5-turbo",
"text-davinci-003",
"text-curie-001"
];
for (const model of modelPreference) {
const isAvailable = await checkModelAvailability(model);
if (!isAvailable) {
console.log(`Model ${model} not available, trying next...`);
continue;
}
try {
let response;
if (model.includes("gpt-")) {
// Chat completion for GPT models
response = await openai.createChatCompletion({
model: model,
messages: [{ role: "user", content: prompt }]
});
return response.data.choices[0].message.content;
} else {
// Text completion for other models
response = await openai.createCompletion({
model: model,
prompt: prompt,
max\_tokens: 100
});
return response.data.choices[0].text;
}
} catch (error) {
console.error(`Error with model ${model}:`, error);
// Try the next model
}
}
throw new Error("All models failed to generate a response");
}
Step 18: Use the Stream Parameter with Care
If you're using the stream parameter, handle it differently:
// Using stream parameter with the OpenAI API
async function streamCompletion(prompt) {
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: prompt,
max\_tokens: 50,
stream: true,
}, { responseType: 'stream' });
// Stream responses have a different structure
// The response is a stream, not an object with choices
const stream = response.data;
let result = '';
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => {
// Parse the chunk as a data stream
const lines = chunk
.toString()
.split('\n')
.filter(line => line.trim() !== '');
for (const line of lines) {
const message = line.replace(/^data: /, '');
if (message === '[DONE]') {
resolve(result);
return;
}
try {
const parsed = JSON.parse(message);
const text = parsed.choices[0].text;
if (text) {
result += text;
// Process each chunk as it arrives
process.stdout.write(text);
}
} catch (error) {
console.error("Error parsing chunk:", error);
}
}
});
stream.on('end', () => {
resolve(result);
});
stream.on('error', (error) => {
reject(error);
});
});
} catch (error) {
console.error("Stream error:", error);
throw error;
}
}
Step 19: Use a Complete Error Classification System
Create a comprehensive system to classify and handle different types of OpenAI API errors:
// Error classification and handling
class OpenAIErrorClassifier {
static classifyError(error) {
// Default classification
let classification = {
type: 'unknown',
retryable: false,
message: error.message,
originalError: error
};
// No response from the server
if (!error.response) {
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
classification = {
type: 'network',
retryable: true,
message: 'Network connectivity issue',
originalError: error
};
} else if (error.code === 'ETIMEDOUT') {
classification = {
type: 'timeout',
retryable: true,
message: 'Request timed out',
originalError: error
};
}
return classification;
}
// Server responded with an error
const status = error.response.status;
const data = error.response.data;
// Authentication errors
if (status === 401) {
classification = {
type: 'authentication',
retryable: false,
message: 'Invalid API key or authentication token',
originalError: error
};
}
// Permission errors
else if (status === 403) {
classification = {
type: 'permission',
retryable: false,
message: 'You do not have permission to access this resource',
originalError: error
};
}
// Bad request errors
else if (status === 400) {
classification = {
type: 'bad\_request',
retryable: false,
message: data?.error?.message || 'Bad request',
originalError: error
};
}
// Not found errors
else if (status === 404) {
classification = {
type: 'not\_found',
retryable: false,
message: 'The requested resource was not found',
originalError: error
};
}
// Rate limiting
else if (status === 429) {
classification = {
type: 'rate\_limit',
retryable: true,
message: 'Rate limit exceeded',
retryAfter: error.response.headers['retry-after'] || 60,
originalError: error
};
}
// Server errors
else if (status >= 500) {
classification = {
type: 'server\_error',
retryable: true,
message: 'OpenAI server error',
originalError: error
};
}
return classification;
}
static async handleError(error, options = {}) {
const classification = this.classifyError(error);
console.error(`OpenAI API error [${classification.type}]: ${classification.message}`);
// Handle specific error types
switch (classification.type) {
case 'authentication':
console.error("Check your API key and ensure it's valid");
break;
case 'rate\_limit':
const retryAfter = classification.retryAfter || 60;
console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
if (options.retry && classification.retryable) {
await new Promise(resolve => setTimeout(resolve, retryAfter \* 1000));
return true; // Indicate retry is needed
}
break;
case 'network':
case 'timeout':
case 'server\_error':
if (options.retry && classification.retryable) {
const backoffTime = options.backoffTime || 2000;
console.log(`Retrying after ${backoffTime/1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, backoffTime));
return true; // Indicate retry is needed
}
break;
default:
// For non-retryable errors, just log them
console.error("Error details:", error.response?.data || error);
}
return false; // Indicate no retry by default
}
}
// Example usage
async function robustOpenAICall(apiCallFn, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await apiCallFn();
if (response &&
response.data &&
response.data.choices &&
response.data.choices.length > 0) {
return response.data.choices[0].text;
} else {
throw new Error("Response missing expected structure");
}
} catch (error) {
attempt++;
const shouldRetry = await OpenAIErrorClassifier.handleError(error, {
retry: attempt < maxRetries,
backoffTime: Math.pow(2, attempt) \* 1000, // Exponential backoff
});
if (!shouldRetry) {
throw error; // Rethrow if no retry is recommended
}
}
}
throw new Error(`Failed after ${maxRetries} attempts`);
}
Step 20: Implement Monitoring and Logging
Add comprehensive monitoring and logging to help troubleshoot issues:
// Implement a logger with context
class APILogger {
static log(level, message, context = {}) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
...context
};
// In production, you might send this to a logging service
console.log(JSON.stringify(logEntry));
// For errors, you could also log to a file or monitoring service
if (level === 'error' || level === 'fatal') {
// Example: log to file or send to error tracking service
}
}
static trackAPICall(apiName, params) {
const startTime = Date.now();
const requestId = Math.random().toString(36).substring(2, 15);
this.log('info', `API call started: ${apiName}`, {
requestId,
params: JSON.stringify(params)
});
return {
success: (response) => {
const duration = Date.now() - startTime;
this.log('info', `API call succeeded: ${apiName}`, {
requestId,
duration,
responseSize: JSON.stringify(response).length
});
return response;
},
failure: (error) => {
const duration = Date.now() - startTime;
this.log('error', `API call failed: ${apiName}`, {
requestId,
duration,
error: error.message,
stack: error.stack,
response: error.response ? JSON.stringify(error.response.data) : null
});
throw error;
}
};
}
}
// Using the logger with OpenAI API calls
async function monitoredCompletion(prompt) {
const tracker = APILogger.trackAPICall('openai.createCompletion', { prompt });
try {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: prompt,
max\_tokens: 50
});
return tracker.success(response).data.choices[0].text;
} catch (error) {
return tracker.failure(error);
}
}
By following these steps, you should be able to diagnose and fix the "Cannot read property choices of undefined" error in your OpenAI Node.js application. The key is proper error handling, validation of API responses, and making sure you're using the correct response structure for the specific OpenAI API endpoint you're calling.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.