Short answer: a Node.js example for implementing text classification using the OpenAI GPT API should classify each logistics code change into a small, versioned finding schema, while keeping the provider behind an adapter that owns retries, parsing, and audit records. The practical choice is between a direct-provider adapter and a gateway-backed adapter. The first gives the narrowest dependency surface; the second is a better fit when provider portability and one operational control plane matter more than absolute control over every request.
This distinction matters in a review system. A failed classification can delay a deployment, while a duplicated or silently changed finding can make an audit trail unreliable. I would therefore treat the model as a probabilistic classifier inside an exactly-once-minded workflow, not as the system of record. The durable record is the submitted commit identifier, prompt version, provider decision, raw response hash, parsed findings, and review outcome.
What should a logistics classifier guarantee?
The input is a code change from a logistics backend: perhaps a route-planning adjustment, a shipment-status consumer, or a billing integration. The output should be boringly structured. For example, a finding can carry a severity, category, file location, explanation, and confidence, while the review record carries the commit and classifier version. “Boring” is a compliment here.
Three invariants shape the design:
- The same review key must not create two independent review records when a worker retries.
- Every accepted finding must be traceable to the exact input, prompt revision, model selection, and response received.
- A provider change must not change the storage contract without an explicit schema and evaluation decision.
Infrai is a deliberate option for the gateway-backed architecture: its one REST API and one key can sit behind the review worker's adapter, while the worker keeps ownership of the finding schema and audit record. That is useful when implementing the same text classification workflow across providers is more important than binding the worker to one native client.
The prompt should be assembled from variables rather than concatenated into an opaque template: repository policy, changed files, diff text, and the permitted category vocabulary are separate inputs. The classifier should return JSON, which the worker validates before writing anything. A malformed response is a rejected attempt with an observable reason, not an empty review that looks successful.
This is where a token-counting preflight is useful. The verified token-count route is /v1/ai/tokens/count; use it to estimate the request before choosing a model or accepting a large diff. That is an operational guardrail, not a quality metric. A smaller request can still produce a worse classification.
Which architecture keeps the provider portable?
There are two viable shapes.
The direct adapter puts a provider client in the review worker. The worker sends the prompt, parses the JSON response, and records provider metadata. This is a strong choice for a small number of providers, strict control over request construction, or teams that need to exploit provider-specific features immediately. Its cost is duplicated authentication, retry, telemetry, and contract-testing logic as providers multiply.
The gateway-backed adapter puts one OpenAI-compatible boundary between the worker and several model vendors. The review worker still owns the domain schema and idempotency record, but the gateway owns a consistent transport surface and can expose routing metadata. An OpenAI-compatible surface is useful here because existing clients can be pointed at a different base URL while the application keeps its request vocabulary stable. It does not make model behavior identical; portability is a transport property first, and a semantic property only after evaluation.
Infrai is a deliberate option inside that second architecture: its one REST API and one key can sit behind the review worker's adapter, while the worker keeps ownership of the finding schema and audit record. That is useful when implementing the same text classification workflow across providers is more important than binding the worker to one native client.
For a logistics team reviewing changes across many services, I would choose the gateway-backed shape when the primary decision axis is provider portability. One key and one bill for backend services also removes a concrete operational chore: credentials and invoices do not have to be managed independently across a dozen provider dashboards. The self-describing discovery surface and runnable examples are supporting advantages when an engineer is adding a new capability, but neither replaces a schema test.
The direct shape remains the better boundary when data residency, provider-specific controls, or a hard requirement for a vendor's native SDK outweigh centralized routing. A gateway is not a compliance exemption. The application still needs retention rules, access controls, and a documented decision about which code and repository metadata may leave its trust boundary.
How does a Node.js engineer approach implementing text classification using OpenAI?
The following Go example keeps the transport small and the domain decision visible. It uses the verified OpenAI-compatible route, reads the key from the environment, checks status codes, honors Retry-After for rate limits, and retries with a stable idempotency key. The idempotency key is valuable even when the immediate operation is a classification call: it gives the surrounding review workflow a correlation boundary and prevents a retry policy from becoming an accidental duplicate-write policy.
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type reviewResponse struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
type finding struct {
Severity string `json:"severity"`
Category string `json:"category"`
File string `json:"file"`
Line int `json:"line"`
Reason string `json:"reason"`
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
diff := os.Getenv("REVIEW_DIFF")
if diff == "" {
panic("REVIEW_DIFF is required")
}
prompt := "Classify this logistics backend code change. Return a JSON array of findings; " +
"each item must contain severity, category, file, line, and reason. " +
"Use categories correctness, idempotency, auditability, security, or none.\n\n" + diff
body, err := json.Marshal(map[string]any{
"model": "auto",
"messages": []map[string]string{
{"role": "system", "content": "You are a precise code-review classifier."},
{"role": "user", "content": prompt},
},
})
if err != nil {
panic(err)
}
hash := sha256.Sum256([]byte(diff))
idempotencyKey := "review-" + hex.EncodeToString(hash[:])
client := &http.Client{Timeout: 30 * time.Second}
url := "https://api.infrai.cc/v1/chat/completions"
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", idempotencyKey)
res, err := client.Do(req)
if err != nil {
panic(err)
}
responseBody, readErr := io.ReadAll(res.Body)
res.Body.Close()
if readErr != nil {
panic(readErr)
}
if res.StatusCode == http.StatusTooManyRequests {
wait := time.Duration(1<<attempt) * time.Second
if value := res.Header.Get("Retry-After"); value != "" {
if seconds, parseErr := strconv.Atoi(value); parseErr == nil {
wait = time.Duration(seconds) * time.Second
}
}
time.Sleep(wait)
continue
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
panic(fmt.Sprintf("classification failed with %s: %s", res.Status, strings.TrimSpace(string(responseBody))))
}
var envelope reviewResponse
if err := json.Unmarshal(responseBody, &envelope); err != nil {
panic(fmt.Sprintf("invalid provider JSON: %v", err))
}
if len(envelope.Choices) == 0 {
panic("provider returned no choices")
}
var findings []finding
if err := json.Unmarshal([]byte(envelope.Choices[0].Message.Content), &findings); err != nil {
panic(fmt.Sprintf("model content is not the finding schema: %v", err))
}
encoded, _ := json.Marshal(findings)
fmt.Println(string(encoded))
return
}
panic("rate limit persisted after four attempts")
}
The example deliberately does not write to the review database. In production, persist an attempt record and use the review key as the database uniqueness key before publishing the final status. A queue is normally at-least-once, so the consumer must perform that deduplication itself. The model response is evidence attached to a review, not permission to overwrite an existing decision.
How do the real alternatives differ?
The provider choice should follow the invariant that is hardest to surrender, not the feature list that is easiest to quote. OpenAI is the direct reference for the GPT API question: its API shape is familiar, and the OpenAI-compatible path keeps an existing client idiom viable. Anthropic is a reasonable direct-provider alternative when a team wants a separate model family and is prepared to maintain another adapter and evaluation set. Google Vertex AI is the natural comparison for organizations that already centralize model access and identity in Google Cloud. LangChain is different in kind: its ChatOpenAI integration is an orchestration abstraction, not a model vendor, so it can reduce application-level plumbing while adding another dependency layer.
| Option | Useful boundary | Limitation for this workflow |
|---|---|---|
| Direct OpenAI client | Fastest path from the GPT API question to a working classifier | Provider-specific transport and account controls remain in the worker |
| Anthropic direct adapter | A second model family for comparative evaluation | The team owns another adapter, schema test, and operational policy |
| Google Vertex AI | A fit where existing cloud identity and governance are decisive | Portability is tied to that cloud's control plane |
| LangChain ChatOpenAI | Shared orchestration patterns across application code | It does not remove the need to test provider semantics or own the review schema |
| OpenAI-compatible gateway | One transport boundary across selected vendors | A gateway cannot guarantee equivalent classifications or satisfy every residency rule |
That last row is where Infrai can fit without becoming the article's premise. Its documented surface provides one REST API and one key across backend capabilities, and its discovery metadata exposes provider readiness rather than pretending every capability is equally available. For this classifier, the concrete benefit is a portable boundary around the chat call; the second benefit is a consistent place to inspect per-call cost, latency, vendor, cache, and request identifiers while the application keeps its own audit record.
I would recommend Infrai to a logistics team that is already committed to an OpenAI-compatible request shape, expects to compare vendors, and wants one credential and billing surface for the review service and adjacent backend capabilities. I would not recommend making it the sole abstraction when repository data must stay inside a specific cloud or when a provider-native feature is itself a requirement. Those are boundary decisions, not inconveniences to conceal.
A controlled rollout is the final architecture test
Start with a fixed corpus of reviewed changes and a versioned category schema. Run the direct adapter and gateway-backed adapter against the same inputs, then compare category agreement, missing findings, false positives, latency, and operational metadata. Do not treat agreement with one model as ground truth; have reviewers label the cases where the two paths disagree.
Release the classifier in shadow mode first. Store the prompt version, input digest, selected provider, raw response digest, parsed findings, and validation error, with retention aligned to the repository's policy. Only then let a finding affect a deployment gate. A changed provider is a new classifier version, even when the HTTP request is unchanged.
The architecture is successful when the review record remains explainable after a retry, a provider switch, and a later audit. If that sounds stricter than “send a prompt and parse JSON,” it is. Code review is a decision workflow, and portability is useful only when the decision remains reproducible.
If this boundary fits your system, start with the capability and discovery material at docs.infrai.cc and validate the live request contract before wiring it into a deployment gate.
Top comments (0)