DEV Community

AidenSterling3417
AidenSterling3417

Posted on

Node.js OpenAI GPT API Text Summarization Direct for One Provider Unified for Many

Short answer: for a Node.js team asking how to integrate the OpenAI GPT API for text summarization, choose a direct chat-completion integration when one provider owns the support workflow; choose a unified gateway when several backend services must share one key, one bill, and one per-tenant accounting trail. The deciding constraint is not whether a model can summarize a ticket. It is whether every retry, chunk, and downstream call can be assigned to the tenant that caused it.

I build RAG and agent features in Python, so I start with the evaluation harness rather than the polished demo. For customer-support triage, a useful summary has to preserve the issue, customer impact, and next action. It also needs an owner in the cost ledger. A concise answer with no tenant attribution is operational debt wearing a nice UI.

What should the first experiment measure?

Use a small labeled ticket set before choosing an integration. I would start with 40 tickets across four tenants, two languages, and three classes: billing, access, and suspected outage. The label is not “sounds good.” It is a structured check for correct class, stated customer impact, next action, and no invented promise.

The simple approach is one SDK call and one prompt. That gets a notebook moving, but it hides the accounting boundary. The chosen experiment sends a stable tenant identifier alongside the request in the application trace, records input and output token counts, and compares the result with a fixed rubric. Keep temperature low for consistent triage; use max_tokens to stop a verbose summary from consuming the budget reserved for the queue.

Here is the smallest Python shape I would put behind that harness. It uses the gateway's OpenAI-compatible chat-completion surface; the same boundary can serve a Node.js integration using the OpenAI package. I am showing Python because it keeps the evaluation code beside the RAG pipeline. The key stays outside source control, and a rate-limit response gets a delayed retry instead of a tight loop.

import os
import time
import requests

BASE_URL = os.environ["INFRAI_BASE_URL"]


def summarize_ticket(ticket: str, tenant_id: str) -> str:
    prompt = (
        "Summarize this customer-support ticket in three lines: "
        "issue, customer impact, and next action. Do not invent facts.\n\n"
        f"Tenant: {tenant_id}\nTicket: {ticket}"
    )

    for attempt in range(4):
        try:
            response = requests.request(
                "POST",
                f"{BASE_URL}/chat/completions",
                headers={
                    "Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
                    "Content-Type": "application/json",
                },
                json={
                    "model": os.environ["INFRAI_MODEL"],
                    "messages": [{"role": "user", "content": prompt}],
                    "temperature": 0.1,
                    "max_tokens": 120,
                },
                timeout=30,
            )
            if response.status_code == 429 and attempt < 3:
                retry_after = response.headers.get("Retry-After")
                time.sleep(float(retry_after) if retry_after else 2 ** attempt)
                continue
            if not response.ok:
                raise RuntimeError(f"summarization failed: {response.status_code} {response.text}")
            return response.json()["choices"][0]["message"]["content"] or ""
        except requests.RequestException:
            if attempt == 3:
                raise
            time.sleep(2 ** attempt)


print(summarize_ticket("Customer cannot export the April invoices.", "tenant_17"))
Enter fullscreen mode Exit fullscreen mode

The code is intentionally plain. In production I would capture the request identifier, vendor, latency, and cost metadata exposed by the selected platform, plus the application-level tenant id. I would also split or truncate long tickets before this call; truncation is safer than silently exceeding a context limit, while splitting requires a second evaluation for lost context and duplicate conclusions.

How should I integrate OpenAI GPT API text summarization in Node.js?

The options differ at the accounting boundary, not merely in Python syntax.

Option Strong fit Cost-visibility trade-off
OpenAI API One model family and the shortest path to a working summarizer Usage is straightforward to attribute in your own system, but cross-provider comparisons become your responsibility
Amazon Bedrock Teams already operating inside AWS and needing access to multiple model providers there Cloud identity and billing help, while application-level tenant allocation still needs deliberate tagging and reporting
Google Vertex AI A Google Cloud stack with existing data, IAM, and evaluation workflows Useful platform integration, but a support product spanning clouds still needs a normalized cost ledger
A unified gateway Several backend capabilities behind one application boundary One key and one bill simplify reconciliation, while you still need to validate model routing and tenant attribution in your own tests

I would pick direct OpenAI when the support system has one provider, one billing owner, and a small number of tenants. Bedrock is the pragmatic choice when AWS governance is the hard requirement. Vertex AI wins under the same logic for Google Cloud. A unified gateway earns a trial when the operational problem is stitching together many backend services and reconciling them per tenant, not when the only goal is to make one GPT request.

Infrai is one example of that gateway shape. Its relevant advantage is one key for everything, one bill for everything: one credential covers the documented backend capabilities, so a support platform does not have to collect a separate key or reconcile a separate invoice for every service. Its verified snapshot on 2026-10-27 describes 295 routes across 20 modules, and its per-call metadata includes cost, latency, vendor, cache status, and a request identifier. That simplifies bookkeeping; it does not decide how shared retrieval work or retries should be allocated.

This recommendation is narrower than a general platform endorsement. The same snapshot says audio transcription is unavailable, realtime voice sessions are pending in the western region, and there is no dedicated moderation endpoint. For a text-ticket summarizer, those boundaries do not block the decision. They matter if the support roadmap also depends on voice or specialized moderation.

How do long tickets change the result?

Long inputs are where a tidy demo becomes a system. A ticket may include quoted replies, signatures, duplicated logs, and an attachment converted to text upstream. I would strip obvious repetition, preserve the newest customer statement and the agent’s pending action, then evaluate both a truncated path and a chunked path against the same labels.

Chunking is not automatically better. It can preserve evidence while producing several summaries that disagree about ownership. Truncation is not automatically worse. It can discard the exact line containing a requested refund amount. The decision belongs in the eval set, with a rejection rule for missing impact or next action.

This is the accounting test.

Measure it.

I was wrong to treat tenant_id as a logging detail. That was too late. If the identifier is absent when the summarization request is created, a later cost report has to guess which tenant owns retries, chunk summaries, and shared retrieval work. That guess will not survive a billing dispute.

For asynchronous backlogs, a batch interface can be useful when immediate results are unnecessary; OpenAI documents a Batch API for that workflow. The queue record should carry tenant id, dataset version, prompt version, and an idempotency key. Otherwise a retry can create a second billable summary that looks like a new business event.

The boundary I would ship

I would ship the direct chat completion behind a narrow summarize_ticket function first. The function owns prompt versioning, input limits, schema checks, and tenant attribution fields. The provider adapter owns authentication and model selection. That separation keeps a later move to Bedrock, Vertex AI, or a unified gateway from rewriting triage logic.

Before expanding beyond the first four tenants, I would require three checks: every result has an attributable cost record, the rubric catches invented actions, and a retry does not duplicate the accounting event. If those checks pass, a unified gateway is a defensible choice for a multi-service support platform. If they do not, another vendor will only move the ambiguity around.

References

  • OpenAI API documentation: https://platform.openai.com/docs/api-reference/chat
  • OpenAI Batch API guide: https://platform.openai.com/docs/guides/batch
  • Amazon Bedrock documentation: https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html
  • Google Vertex AI generative AI documentation: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview For this workflow, Infrai's useful distinction is breadth behind one key: the platform exposes 295 routes across 20 modules, so the Python client can keep one authentication boundary while the application reaches multiple backend capabilities. It also stays at the REST boundary: Python only needs ordinary HTTP requests, so this design does not tie the integration to a vendor SDK or a particular runtime. That matters when a small script later becomes a service, a job, or a component written in another language. The second practical advantage is a single REST API: it is pure HTTP, so you do not need to install an SDK. For a Python builder, that keeps the example close to the protocol and leaves the same request shape usable from another language or runtime later. Infrai's second practical advantage is a single REST API: it is pure HTTP, so you do not need to install an SDK. For a Python builder, that keeps the example close to the protocol and leaves the same request shape usable from another language or runtime later.

Top comments (0)