GuidesOneMux

Migrating a Node.js OpenAI SDK Application: Base URL, Streaming, and Human-in-the-Loop Triage

Learn how to migrate an existing Node.js OpenAI SDK application to an OpenAI-compatible gateway like OneMux, handle streaming, diagnose errors, and build a customer support triage workflow with human approval.

On this page

Migrating an established Node.js application from direct OpenAI API endpoints to an OpenAI-compatible proxy or unified gateway does not require rewriting your core logic. Because the official openai Node.js library exposes client-level configuration for network origins and headers, you can redirect traffic, monitor usage, and leverage alternative routing without swapping out your SDK.

However, routing through a proxy or gateway introduces practical considerations: managing authentication headers, handling streaming events over HTTP, diagnosing edge errors, and ensuring that autonomous outputs remain under human supervision. In customer support triage—where an AI model categorizes incoming requests, drafts responses, and suggests ticket actions—enforcing human review before executing business logic is critical.

This guide demonstrates how to reconfigure the Node.js OpenAI SDK for an OpenAI-compatible gateway like OneMux, stream triage proposals, and enforce human approval before taking high-stakes actions.


Architecture: Support Triage with Human Approval

Automated customer support triage involves reading an incoming ticket, assessing user sentiment, categorizing urgency, and recommending next steps (such as drafting a response, applying tags, or issuing a partial credit). As outlined in the OpenAI Agents Guide, agent architectures that manage real-world side effects benefit from keeping the orchestration loop in application code where guardrails and approval checkpoints can be maintained.

Rather than giving the model direct execution authority, the triage pipeline splits into two distinct phases:

  1. Analysis and Proposal (AI Execution): The model processes the user's message, streams an internal rationale and a draft reply, and generates a structured tool call proposing specific actions.
  2. Approval and Dispatch (Application Execution): The application halts the workflow, persists the proposal in a review queue, and waits for a human agent's confirmation before modifying ticket status or contacting the user.
Incoming Ticket ──> Node.js Service ──> OneMux Gateway ──> Model Inference
                                                                 │
                                                                 ▼
Ticket Updated  <── Support Agent  <── Review Queue  <── Proposed Actions
   (Execution)        (Approves)       (Intercepted)       & Draft Stream

Configuring the Node.js SDK: Base URL and Authentication

The official openai npm package allows you to point requests to any compliant endpoint by modifying the baseURL property. You can explore available models on the OneMux model catalog to pick the appropriate completion model.

First, install the official package:

npm install openai

To point to OneMux, configure the client instance with your OneMux API key and the base URL https://api.onemux.net/v1:

import OpenAI from 'openai';

// Initialize the OpenAI client pointing to the OneMux gateway
const client = new OpenAI({
  apiKey: process.env.ONEMUX_API_KEY,
  baseURL: 'https://api.onemux.net/v1',
});

Verifying Authentication

When routing through a gateway, authentication failure patterns shift slightly. If your API key is missing, invalid, or carries insufficient balance on the platform, the client will catch an AuthenticationError (HTTP 401). Before running triage jobs in production, verify your credentials by consulting the OneMux quickstart guide.


Implementing Streaming for Real-Time Triage Drafting

Support agents benefit from seeing generated triage drafts incrementally rather than waiting for an entire payload to resolve. When invoking chat.completions.create with stream: true, the SDK returns an async iterable stream yielding Server-Sent Events (SSE).

The following implementation streams the draft response to standard output (or your internal agent dashboard) while capturing structured tool calls for triage categorization:

import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.ONEMUX_API_KEY,
  baseURL: 'https://api.onemux.net/v1',
});

interface TriageProposal {
  draftResponse: string;
  category?: string;
  priority?: 'low' | 'medium' | 'high' | 'urgent';
  suggestedRefundAmount?: number;
}

export async function triageTicketStream(
  ticketId: string,
  customerMessage: string
): Promise<TriageProposal> {
  const tools: OpenAI.ChatCompletionTool[] = [
    {
      type: 'function',
      function: {
        name: 'propose_ticket_action',
        description: 'Propose triage tags, priority updates, or billing adjustments.',
        parameters: {
          type: 'object',
          properties: {
            category: {
              type: 'string',
              enum: ['billing', 'technical_bug', 'account_access', 'general'],
            },
            priority: {
              type: 'string',
              enum: ['low', 'medium', 'high', 'urgent'],
            },
            suggestedRefundAmount: {
              type: 'number',
              description: 'Refund amount in USD if the user experienced downtime.',
            },
          },
          required: ['category', 'priority'],
        },
      },
    },
  ];

  const stream = await client.chat.completions.create({
    model: 'gpt-4o',
    stream: true,
    tools,
    messages: [
      {
        role: 'system',
        content:
          'You are a support triage assistant. Analyze the ticket, stream a helpful draft reply for the customer, and call propose_ticket_action with triage metadata.',
      },
      {
        role: 'user',
        content: customerMessage,
      },
    ],
  });

  let draftResponse = '';
  let toolCallArguments = '';
  let toolName = '';

  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta;

    // Stream conversational draft chunks
    if (delta?.content) {
      draftResponse += delta.content;
      process.stdout.write(delta.content);
    }

    // Accumulate tool call chunks
    if (delta?.tool_calls) {
      for (const toolCall of delta.tool_calls) {
        if (toolCall.function?.name) {
          toolName = toolCall.function.name;
        }
        if (toolCall.function?.arguments) {
          toolCallArguments += toolCall.function.arguments;
        }
      }
    }
  }

  let parsedAction = {};
  if (toolName === 'propose_ticket_action' && toolCallArguments) {
    try {
      parsedAction = JSON.parse(toolCallArguments);
    } catch (err) {
      console.error('Failed to parse tool call JSON payload:', err);
    }
  }

  return {
    draftResponse,
    ...parsedAction,
  };
}

The Human Approval Gate

Autonomous execution of tool calls can lead to unvetted refunds or incorrect priority escalation. The application must store the generated TriageProposal in an intermediate state—such as a database record with a PENDING_APPROVAL status—instead of dispatching the actions directly.

interface PendingReviewTicket {
  ticketId: string;
  status: 'PENDING_APPROVAL' | 'APPROVED' | 'REJECTED';
  proposal: TriageProposal;
}

// Step 1: Queue the proposal for human verification
async function queueForHumanReview(ticketId: string, proposal: TriageProposal) {
  console.log(`[Review Queue] Storing proposal for ticket ${ticketId}:`, proposal);
  // Persistence logic: await db.triageReviews.create({ ticketId, proposal, status: 'PENDING_APPROVAL' });
}

// Step 2: Human operator action handler
async function handleHumanDecision(
  reviewId: string,
  decision: 'APPROVE' | 'REJECT',
  modifiedProposal?: Partial<TriageProposal>
) {
  if (decision === 'REJECT') {
    console.log(`Review ${reviewId} rejected by operator.`);
    return;
  }

  // Execute business logic only after approval
  console.log(`Review ${reviewId} approved. Executing refund and priority assignment...`);
  // e.g., await paymentGateway.issueRefund(modifiedProposal.suggestedRefundAmount);
}

This separation guarantees that even if a model misclassifies a standard query as urgent or suggests an unwarranted credit, a human operator intercepts the action prior to execution.


Error Diagnosis: Differentiating Client, Gateway, and Upstream Errors

When using an OpenAI-compatible gateway like OneMux, failure modes can originate from three places: your local application code, the gateway proxy, or the downstream model provider. Inspecting the standard APIError instance from the openai SDK helps isolate root causes:

import OpenAI from 'openai';

try {
  await triageTicketStream('ticket-101', 'System is down since morning!');
} catch (error) {
  if (error instanceof OpenAI.APIError) {
    console.error(`Status Code: ${error.status}`);
    console.error(`Error Message: ${error.message}`);
    console.error(`Error Code: ${error.code}`);
    console.error(`Error Type: ${error.type}`);

    switch (error.status) {
      case 401:
        console.error('Check your OneMux API key in environment variables.');
        break;
      case 402:
        console.error('Account balance exhausted. Top up credits in the dashboard.');
        break;
      case 429:
        console.error('Rate limit reached or concurrent request ceiling exceeded.');
        break;
      case 502:
      case 504:
        console.error('Upstream model provider timeout or network disruption via gateway.');
        break;
      default:
        console.error('Unhandled API exception.');
    }
  } else {
    console.error('Non-API runtime error:', error);
  }
}

Common Status Code Breakdown

HTTP StatusPrimary Root CauseRecommended Remediation
401 UnauthorizedMissing or invalid OneMux API key.Check ONEMUX_API_KEY configuration and header forwarding.
402 Payment RequiredDepleted account balance.Review usage and top up balance via OneMux pricing.
429 Too Many RequestsRate limit or concurrency threshold hit.Implement exponential backoff or raise provider limits.
500 Internal ErrorUpstream model failure or unhandled gateway exception.Check gateway operational status and retry request.
502 / 504 Bad GatewayGateway cannot reach upstream provider.Configure fallback routing or verify model availability via the OneMux documentation.

Limitations and Untested Parameters

While OneMux provides drop-in compatibility for OpenAI standard chat completion endpoints (/v1/chat/completions), keep the following limitations in mind:

  1. Advanced Beta Endpoints: Endpoints outside of core chat, embeddings, and completions—such as specific hosted sandbox environments or managed assistant runtime primitives detailed in the OpenAI documentation—depend on explicit gateway support. Verify endpoint availability in gateway documentation before deploying.
  2. Custom Header Passthrough: Custom organization or project headers (OpenAI-Organization, OpenAI-Project) may be ignored or mapped differently by proxy layers. Ensure your auth token is passed via standard Bearer authorization.
  3. State Management: If you plan to migrate from the hosted OpenAI Assistants API to an application-managed agent loop, your Node.js application is responsible for session history and persistence.

Conclusion

Migrating an existing Node.js OpenAI SDK application to an OpenAI-compatible gateway involves minimal friction: update your baseURL, supply the relevant gateway API key, and adapt error logging to track gateway-specific status codes. By decoupling model proposals from immediate side effects through an approval gate, your customer support triage pipeline retains high operational speed while preventing unintended actions.


Sources

FAQ

Can I use the official OpenAI Node.js SDK with OneMux without modifying my existing method calls?

Yes. Because OneMux implements an OpenAI-compatible REST API, you only need to configure the `baseURL` to point to `https://api.onemux.net/v1` and supply your OneMux API key in the client constructor. Existing methods like `client.chat.completions.create` function identically.

How does streaming handle both text tokens and tool call arguments simultaneously?

During a streaming request, chunks yield deltas. When the model outputs text, `delta.content` contains the streamed text. When the model invokes a function or tool, `delta.tool_calls` streams the name and incremental JSON string fragments in `function.arguments`, which must be buffered and parsed after the stream finishes.

Why is a human approval gate necessary for support triage workflows?

AI models can misread customer tone or hallucinate policies. Placing an application-level approval gate between model recommendations (such as issuing refunds, altering billing statuses, or closing priority tickets) and execution prevents automated errors from reaching end users.

What should my application do if it encounters an HTTP 502 or 504 error through the gateway?

A 502 or 504 error typically signifies that the gateway encountered a timeout or connectivity issue when reaching the underlying model provider. Implement retry logic with exponential backoff and jitter, or switch to a secondary fallback model if high availability is required.

Related articles