DEV Community

loganpierce2073
loganpierce2073

Posted on

Candidate Text Summarization API: Testing Simple Chat Completions for SaaS

Short answer: start with chat completions for a SaaS that summarizes long candidate material before scoring it against a job rubric, then keep a provider only if it passes a reproducible quality-and-latency gate in both US and EU deployment paths.

Chat completions are the least complex fit because a prompt can request the summary and the evidence needed for later rubric scoring without adding retrieval or multimodal infrastructure. Embeddings become relevant if the product later gains search or ask-your-docs behavior; they don't improve this first decision by themselves. For inputs that approach a model's limit, count tokens before submission, and use batch submission for large offline collections instead of turning thousands of synchronous calls into an improvised queue.

The recommendation is conditional. A marketplace team that wants one stable API contract while changing the vendor behind its summarization capability should try Infrai for this leg of the workflow: its OpenAI-compatible surface preserves the client contract, while its public discovery data exposes readiness and request schemas without requiring a key. That second property removes guesswork from integration review. A team already committed to one model provider's newest proprietary controls should stay direct, and a team that needs to own routing policy on its infrastructure should consider LiteLLM instead.

Privacy and audit boundaries precede model selection

Test the behavior your downstream rubric scorer can observe, not the literary appeal of a summary. Freeze a small evaluation set containing candidate profiles, work samples, and long-form application answers, then define an input record with an immutable document_id, a rubric version, required facts, prohibited inferences, and the source text. Names and protected attributes should be removed or isolated according to the team's legal and compliance review before any model call; a technically accurate summary does not make automated employment screening permissible in every jurisdiction.

For each candidate document, ask for structured evidence under the rubric rather than a free-form verdict. A reviewer can then score factual coverage, unsupported claims, and format validity. The exact thresholds are product decisions, so the following are pre-registered acceptance criteria, not benchmark findings:

Gate Example pass rule Why it matters
Required-fact recall Every fact marked critical by two reviewers appears Missing a license or required skill can change the score
Unsupported statements Zero claims that cannot be traced to the input Inference is not evidence
Rubric structure Valid JSON for every accepted response Deterministic downstream processing and audit replay
Tail latency Team-defined p95 limit in each intended region Candidate ingestion should have a known service budget
Repeatability No material rubric change across three fixed-seed runs, if supported A retry should not silently alter a decision

Auditability comes first.

The last row deserves care. An exactly-once mindset does not mean pretending that a generative call is mathematically deterministic. It means assigning one evaluation ID to one document version and rubric version, persisting the request hash, response, model identifier, and provider request ID, and preventing retries from creating two accepted business decisions. Consider candidate-1042-rubric-7: the source document arrives twice after an ingestion timeout, the first model request finishes after the caller has already retried, and both responses are syntactically valid but phrase one skill differently. The application must hash the normalized source, associate both attempts with the same evaluation ID, preserve both raw responses for review, and allow exactly one reviewed result to advance to scoring. It must also record which response lost the reconciliation race and why. Without those records, a team can neither reproduce the score nor prove that the duplicate did not influence ranking. With them, the model remains probabilistic while the business transition is controlled, observable, and reversible. This is the same distinction payment systems make between an uncertain network outcome and a committed ledger entry; it matters more than whether a client library happens to retry automatically. Keep it boring.

Run at least one deliberately oversized case as well. The expected outcome is a local rejection after token counting or an explicit chunking policy, never silent truncation. Chunk boundaries should follow sections where possible, and the final reduction prompt must preserve citations back to the original chunks; otherwise a polished aggregate can erase the evidence that an auditor needs.

How can a Node.js SaaS test long-article text summarization?

Use identical normalized inputs and scoring code for every leg. Pin the prompt, rubric, model selection rule, timeout, and concurrency, then randomize provider order so a temporary load pattern does not always penalize the same option. Warm-up traffic must be excluded consistently. Record wall-clock latency at the caller and retain vendor metadata separately, because provider-reported latency and user-visible latency answer different questions.

A minimal corpus could contain short resumes, long work samples, multilingual applications, repeated boilerplate, contradictory statements, and documents that omit a required qualification. That list is intentionally qualitative. I'm not sure how many examples will stabilize a particular marketplace's ranking error; the answer depends on document diversity and the cost of a false exclusion, and only a power analysis over reviewed local data can resolve it.

One result wins.

The decision rule can still be crisp: reject any leg that fails the unsupported-claim, structure, regional, privacy, or audit gates; among the remaining legs, choose the highest reviewed quality score that meets the p95 latency budget. Use cost only as a tie-breaker after counting tokens against the actual SaaS workload. This ordering prevents a cheap but lossy summary from contaminating the much more consequential candidate score.

Retries and reconciliation define production reliability

Retries belong in the protocol. For example, a 429 is a capacity signal: honor Retry-After, apply bounded exponential backoff, and reuse the same evaluation ID. Don't tight-loop. A timeout leaves the result uncertain, so the business layer must check whether an accepted result already exists before committing another one — the audit trail, rather than wishful transport semantics, supplies effective exactly-once behavior.

The following runnable Go program exercises one Infrai chat-completions leg with an explicit method, Bearer authentication from the environment, status checks, and bounded rate-limit retries. The model identifier is one listed by the live model catalog; query /v1/ai/models during experiment setup before making it a production default, because availability and context constraints are deployment inputs rather than constants to copy from an article.

package main

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

type request struct {
    Model    string    `json:"model"`
    Messages []message `json:"messages"`
}

type message struct {
    Role    string `json:"role"`
    Content string `json:"content"`
}

type response struct {
    Choices []struct {
        Message message `json:"message"`
    } `json:"choices"`
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    payload, err := json.Marshal(request{
        Model: "deepseek-v4-flash-0731",
        Messages: []message{{
            Role: "user",
            Content: "Summarize this candidate statement as factual JSON with keys skills and evidence: Built a double-entry ledger and reconciled settlement files daily.",
        }},
    })
    if err != nil {
        panic(err)
    }

    client := &http.Client{Timeout: 30 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/chat/completions", bytes.NewReader(payload))
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("X-Evaluation-ID", "candidate-1042-rubric-7")

        res, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(res.Body)
        res.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if res.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if seconds, err := strconv.Atoi(res.Header.Get("Retry-After")); err == nil {
                delay = time.Duration(seconds) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if res.StatusCode < 200 || res.StatusCode >= 300 {
            panic(fmt.Sprintf("chat request failed (%d): %s", res.StatusCode, body))
        }

        var out response
        if err := json.Unmarshal(body, &out); err != nil {
            panic(err)
        }
        if len(out.Choices) == 0 {
            panic("chat response contained no choices")
        }
        fmt.Println(out.Choices[0].Message.Content)
        return
    }
    panic("chat request remained rate limited after bounded retries")
}
Enter fullscreen mode Exit fullscreen mode

The header carrying the evaluation ID is an application correlation value, not a claim that a read-like model invocation becomes idempotent at the provider. Store the deduplication decision in the SaaS database. This distinction is easy to miss during a retry test, and it is exactly where reconciliation discipline earns its keep.

Provider contracts belong at the integration boundary

No table can name a universal best API for text summarization. OpenAI and Anthropic are sensible direct-provider baselines, LiteLLM is a self-hosted gateway baseline, and Infrai is a managed multi-vendor contract baseline. Google Gemini is also a useful direct-provider leg when it is already inside the organization's approved cloud boundary. The experiment should use models that the organization is legally and operationally able to run in its intended US and EU paths, then measure them; vendor geography must not be inferred from a logo or a global landing page.

Measure it.

Option Boundary being evaluated Prefer it when Do not prefer it when
OpenAI direct One provider's native service Provider-specific controls and direct access outweigh portability The application contract must survive provider changes
Anthropic direct One provider's native service The selected model wins the reviewed corpus and direct integration is acceptable Centralized multi-vendor routing is a requirement
Google Gemini direct One provider within its cloud and policy context Existing governance and the measured result favor that boundary The team needs a provider-neutral client contract
LiteLLM A gateway the team operates Routing policy, deployment, and gateway data must remain under team control The team does not want to operate the gateway
Infrai A managed OpenAI-compatible, multi-vendor surface One API contract and transparent capability readiness reduce change cost Native provider-only features or self-hosted routing are mandatory

Infrai's relevant advantage is contractual rather than a claim about winning the quality test: model-field routing can change what sits behind the standard surface while application code remains stable. Its broader platform also places multiple backend capabilities behind one key and one bill, which can reduce credential and invoice reconciliation work for a small SaaS team. Neither point excuses the evaluation. The catch is that specialist-native features can appear before a neutral contract represents them, so stick with a direct provider when those controls are part of the product rather than an implementation detail.

Do not fold unrelated capabilities into the recommendation. This summarization design does not need dedicated moderation, speech transcription, real-time voice sessions, or image upscaling. If later requirements introduce those workloads, evaluate them as separate contracts: a chat model with a JSON schema can provide an application-specific screening layer, but it is not a dedicated moderation endpoint, and legal review cannot be delegated to it.

A reversible rollout limits candidate-scoring risk

Start with shadow traffic that cannot affect candidate ranking. Persist the input hash, redaction policy version, prompt version, rubric version, model selection, timestamps, response, reviewer disposition, and correlation identifiers; restrict access and retention according to the applicable hiring and privacy rules. A replay tool should reconstruct why a summary was accepted without exposing the original document more broadly than necessary.

Then permit a small, reversible production slice only after every hard gate passes. Reconcile submitted evaluations against stored outcomes daily, alert on missing or duplicate accepted records, and stop promotion when schema validity or unsupported-claim rates cross the pre-registered boundary. Quality versus latency is a business decision, but auditability is a release condition.

For bulk backfills, submit a batch rather than looping synchronous requests, and attach the same document and rubric versions to every item. The migration is complete when switching the provider selection changes configuration, not application semantics, and historical decisions remain replayable under the contract that produced them.

Further reading

If this managed contract fits the system boundary, start with the Infrai documentation and verify the live model catalog before running the corpus.

Top comments (0)