Nurture TechnologiesNurture Tech
AI12 min read·July 24, 2026

OpenAI Responses Became Slow in Production

TypeScriptNode.jsOpenAI SDKExpressServer-Sent Events

OpenAI completions that took 2 seconds now take 15 seconds. The fix starts with streaming and ends with model selection and parallel request design.

Problem Summary

OpenAI completion latency is measured in two ways: Time to First Byte (TTFB) how long until the first token of the response arrives and total response time how long until the full response is complete. Most production latency complaints are about total response time because the application waits for the entire response before showing anything to the user. Implementing streaming eliminates perceived latency by showing output as it generates, but several other factors model choice, system prompt size, prompt token count, and network path affect even TTFB.

Symptoms

  • Chat responses take 10–30 seconds end-to-end before anything appears in the UI
  • API latency varies widely: some requests complete in 2 seconds, others take 20+
  • Server logs show the OpenAI call blocking the entire request handler thread
  • Users report the UI 'freezing' or 'hanging' while waiting for AI output
  • Latency increased after adding more context to the system prompt
  • gpt-4o requests are significantly slower than gpt-4o-mini for the same user prompt
  • Parallel API calls (e.g., generating multiple suggestions) run sequentially instead of concurrently
  • TTFB to the OpenAI API is fast but response arrives all at once after a long wait

Business Impact

Users perceive AI responses as 'thinking' when they see streaming text, but a blank screen for 15 seconds before text appears triggers frustration and abandonment. Research on web performance consistently shows that perceived performance matters more than actual performance. A 15-second streaming response that starts showing text in 800ms is experienced as faster than a 5-second response that shows nothing then appears all at once.

Root Cause

The most impactful root cause for perceived latency is the absence of streaming. Without stream: true, the Node.js server waits for the entire response, then forwards it to the browser in a single payload. The user sees nothing for the full generation time which for a 500-token response on gpt-4o at ~40 tokens/second is about 12 seconds. With streaming, the first token appears in the browser in approximately the same time as the TTFB from OpenAI to your server (~300–800ms).

gpt-4o-mini generates tokens approximately 3x faster than gpt-4o and has lower TTFB due to its smaller parameter count. For features where response quality is acceptable from the smaller model, switching to mini reduces both TTFB and total generation time.

Diagnosis

Step 1: Measure TTFB vs Total Response Time Separately

measure-latency.ts
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export async function measureLatency(messages: OpenAI.ChatCompletionMessageParam[]) {
  const start = performance.now();
  let ttfb: number | null = null;
  let tokenCount = 0;

  const stream = await client.chat.completions.create({
    model: "gpt-4o",
    messages,
    stream: true,
  });

  for await (const chunk of stream) {
    if (ttfb === null) {
      ttfb = performance.now() - start;
      console.log(`TTFB: ${ttfb.toFixed(0)}ms`);
    }
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) tokenCount++;
  }

  const total = performance.now() - start;
  console.log(`Total time: ${total.toFixed(0)}ms for ~${tokenCount} tokens`);
  console.log(`Generation speed: ${(tokenCount / (total / 1000)).toFixed(1)} tokens/sec`);
}

Step 2: Implement Server-Sent Events Streaming Endpoint

Pipe the OpenAI stream directly to the HTTP response using Server-Sent Events. This lets tokens appear in the browser as fast as OpenAI generates them.

streaming-endpoint.ts
import express from "express";
import OpenAI from "openai";

const app = express();
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

app.post("/api/chat/stream", express.json(), async (req, res) => {
  const { messages } = req.body;

  // Set SSE headers
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");
  res.flushHeaders(); // Send headers immediately so the browser knows to start reading

  try {
    const stream = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages,
      stream: true,
      max_tokens: 1024,
    });

    for await (const chunk of stream) {
      const delta = chunk.choices[0]?.delta?.content;
      if (delta) {
        // SSE format: "data: <payload>\n\n"
        res.write(`data: ${JSON.stringify({ content: delta })}\n\n`);
      }

      if (chunk.choices[0]?.finish_reason) {
        res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
        break;
      }
    }
  } catch (err) {
    res.write(`data: ${JSON.stringify({ error: "Stream failed" })}\n\n`);
  } finally {
    res.end();
  }
});

Step 3: Consume the Stream in the Browser

stream-client.ts
export async function streamChat(
  messages: { role: string; content: string }[],
  onToken: (token: string) => void,
  onDone: () => void
): Promise<void> {
  const response = await fetch("/api/chat/stream", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ messages }),
  });

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split("\n").filter((l) => l.startsWith("data: "));

    for (const line of lines) {
      const payload = JSON.parse(line.slice(6));
      if (payload.content) onToken(payload.content);
      if (payload.done) { onDone(); return; }
    }
  }
}

Step 4: Run Independent AI Calls in Parallel

If a page requires multiple independent AI completions (e.g., a title, a summary, and suggested tags), await them sequentially adds their latencies. Use Promise.all to run them concurrently.

parallel-requests.ts
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function generateDocumentMetadata(document: string) {
  // BAD: sequential  total time = t1 + t2 + t3
  // const title = await generateTitle(document);
  // const summary = await generateSummary(document);
  // const tags = await generateTags(document);

  // GOOD: parallel  total time = max(t1, t2, t3)
  const [title, summary, tags] = await Promise.all([
    client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [
        { role: "system", content: "Generate a concise title. Respond with the title only." },
        { role: "user", content: document },
      ],
      max_tokens: 30,
    }),
    client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [
        { role: "system", content: "Summarize in 2 sentences." },
        { role: "user", content: document },
      ],
      max_tokens: 100,
    }),
    client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [
        { role: "system", content: "Return 5 tags as a JSON array of strings." },
        { role: "user", content: document },
      ],
      max_tokens: 60,
      response_format: { type: "json_object" },
    }),
  ]);

  return {
    title: title.choices[0].message.content,
    summary: summary.choices[0].message.content,
    tags: JSON.parse(tags.choices[0].message.content!),
  };
}

Step 5: Reduce System Prompt Tokens to Lower TTFB

TTFB from OpenAI correlates with prompt token count. Longer prompts take more time to process before the first output token generates. Audit your system prompt and cut it to the minimum necessary instructions.

trim-system-prompt.ts
// BEFORE: verbose system prompt (~800 tokens)
const verboseSystemPrompt = `
You are a helpful, friendly, knowledgeable, and professional AI assistant
for our company. You should always be polite and courteous to users.
You should respond in a helpful manner and try to answer questions to
the best of your ability. If you don't know something, say so clearly.
You are an expert in customer support and should help users solve their problems...
[continues for 20 more paragraphs]
`;

// AFTER: minimal system prompt (~60 tokens)
const conciseSystemPrompt = `
Customer support assistant. Answer questions about our product clearly and concisely.
Escalate billing issues to support@example.com. Do not discuss competitors.
`;

Production Failure Scenarios

Scenario 1: Node.js Event Loop Blocked by Synchronous Token Counting

A tiktoken encoding operation on a 50,000-character document was running synchronously on the main Node.js thread before every API call, blocking the event loop for 80–120ms per request. Under concurrent load this serialized all requests through the encoder. Fix: run CPU-intensive token counting in a worker thread or use the async tiktoken API.

Scenario 2: Streaming Response Buffered by Proxy

An nginx reverse proxy had proxy_buffering on (the default), which buffered the entire streaming response before forwarding to the client. The browser received everything at once after the full generation time identical behavior to no streaming. Fix: add proxy_buffering off and X-Accel-Buffering: no headers for streaming endpoints.

Scenario 3: gpt-4o Used for All Tasks Including Classification

All API calls were hardcoded to 'gpt-4o' regardless of task complexity. Intent classification (which is a single-token output task) was experiencing the same 8–15 second latency as long-form generation. Switching classification, tagging, and extraction tasks to gpt-4o-mini reduced their P95 latency from 12 seconds to under 2 seconds.

Prevention Checklist

  • Implement streaming (stream: true) for all user-facing chat completions
  • Measure TTFB and total generation time separately in production monitoring
  • Use gpt-4o-mini for classification, extraction, and short-form generation tasks
  • Run independent AI calls with Promise.all instead of sequential awaits
  • Disable proxy buffering for streaming endpoints in nginx and other reverse proxies
  • Keep system prompts under 200 tokens unless the task genuinely requires more
  • Run CPU-intensive preprocessing (token counting, text cleaning) in worker threads
  • Add P50, P95, and P99 latency metrics to your monitoring dashboard by model and feature

Resolution Summary

The highest-impact fix for slow OpenAI responses is almost always implementing streaming, which reduces perceived latency from full generation time to TTFB. After streaming, the next gains come from model right-sizing (gpt-4o-mini for simple tasks), parallelizing independent calls, and trimming system prompt size. Together these changes can reduce perceived latency from 15 seconds to under 1 second for most user-facing features.

Lessons Learned

  • Streaming reduces perceived latency by 10x or more it should be the default for all chat features
  • Reverse proxies buffer streaming responses by default and must be explicitly configured for SSE
  • TTFB is the metric that matters for user experience, not total generation time
  • Sequential AI calls for independent tasks multiply latency unnecessarily
  • gpt-4o-mini generates tokens 3x faster than gpt-4o and should be the default model

Conclusion

Slow AI responses are a UX problem before they are a performance problem. Users who see text appearing immediately even slowly stay engaged. Users who stare at a loading spinner for 15 seconds leave. Streaming is the single most impactful change you can make to AI response latency, and it requires fewer than 50 lines of code to implement end to end. Do it first, then optimize model selection and parallelism for the remaining gains.

Nurture Technologies

NEED HELP WITH YOUR STACK?

Nurture Technologies builds and maintains production-quality software for startups and businesses. If engineering problems are slowing you down, our team can help.

Talk to our team →
FAQ

FREQUENTLY ASKED QUESTIONS

What is TTFB in the context of OpenAI API calls?+

TTFB (Time to First Byte) for OpenAI API calls is the time from when your server sends the API request until the first token of the response starts arriving. It includes network round-trip time, OpenAI's request queuing time, and the time to process the prompt before generation begins. Typical TTFB ranges from 300ms to 2 seconds depending on model size and current API load.

How do I enable streaming in the OpenAI Node.js SDK?+

Add stream: true to your chat.completions.create call. The return value becomes an async iterable. Iterate over it with for await (const chunk of stream) and extract content from chunk.choices[0]?.delta?.content. Each chunk contains a small number of tokens as they are generated.

Why is my streaming response still slow even after adding stream: true?+

The most common cause is a reverse proxy (nginx, Cloudflare, AWS ALB) buffering the response. nginx's proxy_buffering is on by default. Add proxy_buffering off to your nginx location block for streaming endpoints. If you are behind Cloudflare, disable response buffering for AI routes. AWS ALB does not buffer, but API Gateway does move streaming to Lambda function URLs or ALB directly.

How much faster is gpt-4o-mini than gpt-4o?+

gpt-4o-mini generates tokens approximately 3x faster than gpt-4o due to its smaller parameter count. TTFB is also lower because the smaller model processes input prompts faster. For a 200-token response, gpt-4o typically takes 8–12 seconds total while gpt-4o-mini takes 2–4 seconds.

Does OpenAI have SLAs for response time?+

OpenAI does not offer latency SLAs on the standard API. Enterprise agreements may include availability SLAs but typically not latency guarantees. Azure OpenAI Service offers Provisioned Throughput Units (PTUs) which provide reserved capacity and more predictable latency, suitable for latency-sensitive production workloads.

What is Azure OpenAI PTU and when should I use it?+

Azure OpenAI Provisioned Throughput Units reserve dedicated model capacity for your workload. PTUs provide consistent low latency and guaranteed throughput, eliminating the latency variance caused by shared capacity. Use PTUs when you have predictable high-volume workloads and need P99 latency guarantees. PTUs are billed by the hour regardless of actual usage.

How do I handle streaming in a Next.js API route?+

Use the Edge runtime with a ReadableStream response. Create the OpenAI stream, pipe it through a TransformStream that formats tokens as SSE events, and return it as a streaming Response. The Vercel AI SDK (ai package) provides the streamText helper that handles this pattern and works with Next.js App Router Route Handlers out of the box.

Does prompt length affect TTFB significantly?+

Yes. OpenAI processes the entire input prompt before generating the first output token. A 10,000-token prompt takes meaningfully longer to process than a 500-token prompt before the first response token appears. Reducing system prompt size and trimming conversation history both reduce TTFB in addition to reducing cost.

Can I set a timeout on OpenAI API calls?+

Yes. The OpenAI SDK accepts a timeout option in the constructor or per-request via the request_options. Example: client.chat.completions.create(params, { timeout: 30000 }) sets a 30-second timeout. For streaming, set the timeout high enough to cover the full generation time, or implement your own timeout logic using Promise.race with a timer.

Should I use connection pooling for OpenAI HTTP requests?+

The OpenAI Node.js SDK uses node-fetch or undici internally and manages HTTP keep-alive connections automatically. You do not need to implement connection pooling manually. However, if you are running behind a proxy, ensure the proxy does not have connection timeouts shorter than your typical request duration.