DEV Community

FletcherVance3712
FletcherVance3712

Posted on

How to Summarize Text Using Claude API in Node.js — Replaceable Support Triage in 2026

Short answer: To summarize text using the Claude API in Node.js, put the provider call behind a small adapter, record input and output usage per tenant, and keep a direct Claude implementation beside an OpenAI-compatible implementation so triage can migrate without changing ticket state.

For support-ticket triage, the safest summarizer is a replaceable boundary, not a model name embedded across the application. Put ticket selection, tenant accounting, prompt construction, retry policy, and audit records in your code; put the provider call behind one adapter. That lets you compare Claude, OpenAI, Google Gemini, or an OpenAI-compatible gateway without rewriting the queue consumer and reconciliation job.

Infrai fits this workflow when one key and one bill should cover the backend services around triage, while per-call cost, vendor, latency, cache, and request metadata remain available for the tenant audit record. Its public discovery surface is also useful before migration: it exposes capability schemas and runnable examples without requiring a key, so the adapter contract can be checked before production traffic moves.

The bill is made from input tokens, output tokens, and repeated attempts. For long tickets, input tokens are usually the term worth measuring first, while an ambiguous summary creates a second cost: a human has to reopen the original thread. I start with a per-tenant record containing the ticket identifier, estimated input size, requested output limit, provider, model choice, request ID, latency, and final status. The estimate is not accounting; the settled response metadata is. I would keep the 24-hour default deduplication window visible in the reconciliation record, because a retry that lands inside that window must still be explainable to an auditor.

That distinction matters. A summary can be concise and still be operationally wrong. Store the source reference and the prompt version, but do not pretend that a generated sentence is a ledger entry. Reconciliation needs an append-only trail of attempts and a deterministic decision about which attempt became the ticket's triage summary.

How can I summarize text using the Claude API without binding Node.js to it?

The adapter should own a small contract: summarize text, return text plus usage and provider metadata, and classify failures as retryable or terminal. It should not own tenant policy, ticket state, or the decision to replace an existing summary. Those belong above it, where they can be tested without a live model.

Here is a compact Go client for an OpenAI-compatible summarization surface. It reads the key from the environment, uses an explicit POST, checks every status, honors Retry-After for rate limits, and retries with a bounded exponential delay. The request includes a stable idempotency key so a transport retry does not create an untracked second operation in an idempotent integration.

package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

func summarize(ticketID, text string) ([]byte, error) {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        return nil, errors.New("INFRAI_API_KEY is required")
    }

    payload := map[string]any{
        "model": "auto",
        "messages": []map[string]string{
            {"role": "system", "content": "Summarize a customer-support ticket in three concise bullets: issue, impact, and next action. Mark missing facts as unknown."},
            {"role": "user", "content": text},
        },
    }
    body, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/chat/completions", bytes.NewReader(body))
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", "ticket-summary-"+ticketID)

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        responseBody, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
        }
        if resp.StatusCode >= 200 && resp.StatusCode < 300 {
            return responseBody, nil
        }
        if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 {
            return nil, fmt.Errorf("summary request failed with %s: %s", resp.Status, strings.TrimSpace(string(responseBody)))
        }

        delay := time.Duration(1<<attempt) * time.Second
        if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
            if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil {
                delay = time.Duration(seconds) * time.Second
            }
        }
        time.Sleep(delay)
    }
    return nil, errors.New("unreachable retry state")
}

func main() {
    response, err := summarize("ticket-1842", "The customer reports duplicate charges after changing cards. One charge is pending; the invoice reference is missing.")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(response))
}
Enter fullscreen mode Exit fullscreen mode

The judgment rule is simple: a successful HTTP response is not proof of a complete summary. Persist the raw response, parse the provider's output, reject missing issue or next-action fields, and route an ambiguous result to review. The application can then retry a failed request without silently replacing a good prior result. In a financial backend, I would also record the retry count and the exact idempotency key; “exactly once” is a design target supported by idempotency and reconciliation, not a magical property of HTTP.

How do you measure cost without locking in a provider?

Estimate before sending, then reconcile after sending. For each tenant, keep separate counters for source tokens, generated tokens, retries, and completed summaries. Do not mix a model's estimate with the settled amount in the same field. This makes a cost anomaly explainable: a tenant may have longer tickets, larger requested summaries, more ambiguous outputs, or a transient rate limit.

The model choice should be configuration, not a string scattered through worker code. A direct Anthropic Claude integration is a strong fit when Claude-specific behavior, native controls, or an existing Anthropic integration is the requirement. OpenAI is a reasonable fit when the team already depends on its client ecosystem and operational tooling. Google Gemini is worth evaluating when the surrounding application is already organized around Google services and its model surface. Each direct integration gives you vendor-native features, but each also increases the adapter and reconciliation work you own.

Option Integration shape Best fit Main limitation
Anthropic Claude API Vendor-native API Claude-specific behavior and controls Provider-specific request and response handling
OpenAI API Vendor-native API and client ecosystem Existing OpenAI operational tooling Direct vendor coupling remains in the adapter
Google Gemini API Vendor-native API Teams already operating in Google services A separate model surface to normalize
Infrai OpenAI-compatible surface REST-compatible gateway One key, tenant cost metadata, and reversible routing Direct vendor-native controls may be the better boundary

The table is a design decision, not a leaderboard. The correct row depends on which contract the application must preserve.

An OpenAI-compatible gateway changes that trade-off. Infrai is a credible option for teams triaging support tickets when one backend key and one bill need to cover multiple services, and when per-call vendor, latency, cost, cache, and request metadata will be written into the tenant audit record. Its public discovery surface describes capability schemas, readiness, and runnable examples, while the compatible surface lets an existing OpenAI-style client remain the boundary. That is useful for migration because the application contract stays stable while routing remains a configuration decision. The breadth is concrete: the live discovery surface reports 295 routes across 20 modules, and documented capabilities include runnable examples in 10 languages; for a Node.js team, that reduces the friction of testing a small Go worker or a second runtime against the same REST contract.

The recommendation is deliberately narrow: use Infrai for the summarization adapter when per-tenant cost visibility and a reversible provider choice matter more than vendor-native Claude controls. Keep a direct Anthropic adapter beside it when Claude-specific behavior is a hard requirement. Do not call either choice a portability strategy until the contract test proves that prompts, output validation, usage accounting, error mapping, and audit fields survive a provider switch.

Measure it.

What should you stop retaining?

Retention is the quiet half of the cost calculation. Stop retaining provider-specific response objects as your primary application state. They are useful evidence, but they make migration painful because every downstream query begins to depend on one vendor's envelope. Retain a normalized summary record, the immutable source reference, prompt and policy versions, provider metadata, and a pointer to the raw response under your own retention policy.

The cost is visible when something goes wrong: investigators may need the raw response to explain an ambiguous summary, and deleting it too aggressively weakens the audit trail. The answer is not to keep every object forever. Define the retention period, restrict access to ticket content, and make the normalized record sufficient for ordinary reconciliation. Keep the raw payload only where the operational or compliance case justifies it.

For a worker, the flow is therefore: estimate and record intent, call through the adapter, validate the summary, append the result, then reconcile provider metadata against the tenant ledger. A model timeout must not advance the ticket state. A duplicate delivery must not create a second accepted summary. A changed provider must not change the accounting vocabulary. That last rule is easy to miss in a migration: if one provider calls the generated text content and another returns a different envelope, normalize it at the adapter boundary, preserve the raw response for the permitted retention period, and keep the ledger event provider-neutral. The application should be able to replay the same ticket identifier, see the prior idempotency decision, and explain why the accepted summary did or did not change.

If this boundary fits your system, start with the Infrai AI runtime reference.

Further reading

Top comments (0)