feat: improve API reliability with retries and timeouts#82
feat: improve API reliability with retries and timeouts#82shahrear33 merged 1 commit intovlm-run:mainfrom
Conversation
whereisfarukk
commented
Dec 23, 2025
- Add retry mechanism (3 attempts) with exponential backoff
- Configure timeouts: 60s default, 4min for chat completions
- Centralize error handling and use NodeOperationError
…s for API requests
Summary of ChangesHello @whereisfarukk, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the reliability of API interactions by introducing mechanisms for automatic retries and configurable request timeouts. It also streamlines error reporting by centralizing the logic for processing and re-throwing API-related exceptions, ensuring a more consistent and robust client-side experience. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors the VlmRunClient to introduce centralized error handling and a retry mechanism for API requests. A new handleRequestError method was added to parse and throw detailed errors from HTTP responses, replacing previous inline error handling. The makeRequest method now includes retry logic with exponential backoff for transient network errors (e.g., ETIMEDOUT, ECONNRESET), and dynamically sets request timeouts, with a longer duration for chat completion endpoints. New constants for MAX_RETRIES, DEFAULT_TIMEOUT, and CHAT_COMPLETION_TIMEOUT were added to config.ts. Review comments suggest improving the exponential backoff calculation, utilizing NodeOperationError for better n8n integration in the error handler, and extracting the /openai/chat/completions endpoint string into a constant for maintainability.
| const isRetryable = | ||
| errorCode === 'ETIMEDOUT' || | ||
| errorCode === 'ECONNRESET' || | ||
| errorCode === 'EPIPE' || | ||
| (!error.response && attempt < MAX_RETRIES); // Network errors without response | ||
|
|
||
| if (isRetryable && attempt < MAX_RETRIES) { | ||
| const delay = RETRY_DELAY * attempt; // Exponential backoff | ||
| await new Promise(resolve => setTimeout(resolve, delay)); | ||
| continue; |
There was a problem hiding this comment.
There are a couple of areas for improvement in the retry logic:
- The PR description mentions "exponential backoff", but the implementation
RETRY_DELAY * attemptis a linear backoff. For exponential backoff, you should use something likeRETRY_DELAY * Math.pow(2, attempt - 1). - The
isRetryablecondition(!error.response && attempt < MAX_RETRIES)mixes the concern of what is a retryable error with when to retry. This makes the code harder to read. It's clearer to defineisRetryablebased only on the error type.
| const isRetryable = | |
| errorCode === 'ETIMEDOUT' || | |
| errorCode === 'ECONNRESET' || | |
| errorCode === 'EPIPE' || | |
| (!error.response && attempt < MAX_RETRIES); // Network errors without response | |
| if (isRetryable && attempt < MAX_RETRIES) { | |
| const delay = RETRY_DELAY * attempt; // Exponential backoff | |
| await new Promise(resolve => setTimeout(resolve, delay)); | |
| continue; | |
| const isRetryable = | |
| errorCode === 'ETIMEDOUT' || | |
| errorCode === 'ECONNRESET' || | |
| errorCode === 'EPIPE' || | |
| !error.response; // Network errors without response | |
| if (isRetryable && attempt < MAX_RETRIES) { | |
| const delay = RETRY_DELAY * Math.pow(2, attempt - 1); // Exponential backoff | |
| await new Promise(resolve => setTimeout(resolve, delay)); | |
| continue; | |
| } |
| private handleRequestError(error: any): never { | ||
| // Extract error details from HTTP response | ||
| let errorDetail = error.message || 'Unknown error'; | ||
| if (error.response?.body) { | ||
| try { | ||
| const errorBody = | ||
| typeof error.response.body === 'string' | ||
| ? JSON.parse(error.response.body) | ||
| : error.response.body; | ||
| errorDetail = errorBody.detail || errorBody.message || errorDetail; | ||
| } catch { | ||
| errorDetail = error.response.body || errorDetail; | ||
| } | ||
| } | ||
|
|
||
| // Throw error with status code if available, otherwise use generic error | ||
| if (error.response?.status) { | ||
| throw new Error(`HTTP ${error.response.status}: ${errorDetail}`); | ||
| } | ||
| throw new Error(errorDetail); | ||
| } |
There was a problem hiding this comment.
The PR description mentions using NodeOperationError, but the implementation throws a generic Error. Using NodeOperationError provides better integration with the n8n ecosystem for error handling. You can get the node instance via this.ef.getNode(). You will also need to import NodeOperationError from n8n-workflow.
private handleRequestError(error: any): never {
// Extract error details from HTTP response
let errorDetail = error.message || 'Unknown error';
if (error.response?.body) {
try {
const errorBody =
typeof error.response.body === 'string'
? JSON.parse(error.response.body)
: error.response.body;
errorDetail = errorBody.detail || errorBody.message || errorDetail;
} catch {
errorDetail = error.response.body || errorDetail;
}
}
// Throw error with status code if available, otherwise use generic error
if (error.response?.status) {
throw new NodeOperationError(this.ef.getNode(), `HTTP ${error.response.status}: ${errorDetail}`);
}
throw new NodeOperationError(this.ef.getNode(), errorDetail);
}|
|
||
| // Determine timeout based on endpoint | ||
| let timeout = DEFAULT_TIMEOUT; | ||
| if (endpoint === '/openai/chat/completions') { |