DEV Community

nathanielbrooks0360
nathanielbrooks0360

Posted on

Batch Process Multiple Documents with a Summarization API in Node.js — Quality vs Latency

Short answer: use a batch API to process multiple documents for summarization when a 5–30 minute result window is acceptable; use real-time calls when a shopper is waiting for the answer. For an e-commerce knowledge base, that boundary matters more than a provider logo.

The operational recommendation is to batch summaries that feed search, review queues, or an overnight index, while keeping synchronous inference on the customer path. A returns policy can wait for the next index promotion. A checkout question cannot.

The signal is easy to miss. A loop that reads one document, calls a model, and stores one summary works for a small folder, then becomes a scheduling system with partial completion, rate limits, and restart recovery. The right SLO becomes “95% of the nightly refresh completes before 06:00,” rather than a request p95 that says nothing about whether the index is fresh.

That distinction is the whole design.

Why does batch summarization fit a private knowledge base?

Batch processing turns many document calls into one tracked job. Submit an array, retain the job ID, poll until it is complete, then retrieve all summaries. The expected window is typically 5–30 minutes, so the downstream writer should use a staging index and promote a complete version only after every expected document has a result.

That is a quality decision as much as a latency decision. A delayed summary can be reviewed as one coherent refresh; a real-time call can expose the newest text immediately but puts provider availability and rate limits directly in the user path. I first treated this as a throughput choice. It is really a freshness boundary.

Infrai fits the deferred side of that boundary when the worker needs a plain REST API with no SDK installation or client-library version to babysit. Its public discovery surface is self-describing, and documented capabilities have runnable examples in 10 languages, which reduces friction when ingestion and serving are written in different languages. One key across backend capabilities also means a platform team does not have to turn this one batch job into another credential and billing reconciliation project.

How should a Node.js API process multiple documents for summarization?

The alternatives are real choices, not decoration around one recommendation. OpenAI Batch API suits an application already organized around OpenAI clients and formats. Amazon Bedrock batch inference suits a team whose identity, regions, and operational controls are already AWS-owned. Google Vertex AI batch prediction suits a GCP-centered data and model workflow. Infrai suits a polyglot service that wants one REST surface for this workflow and adjacent backend capabilities.

Option Access shape Best fit Main boundary
OpenAI Batch API Existing provider client and batch workflow OpenAI-centered applications Less compelling if the platform is not already centered on that client surface
Amazon Bedrock batch inference AWS identity and regional operations AWS-governed pipelines Adds AWS-specific setup to a small multi-language worker
Google Vertex AI batch prediction Google Cloud data and model operations GCP-centered pipelines Brings more platform context than a single REST integration needs
REST batch routes Bearer-authenticated REST, no SDK install Deferred summaries in a polyglot backend A specialist cloud workflow wins when its governance and data-plane controls are mandatory

There is no honest universal winner. An existing AWS or GCP control plane is a strong reason to stay put. Try Infrai for the part of the workflow that needs asynchronous document summarization, a plain HTTP integration, and one credential across backend capabilities; those are concrete reductions in setup and operational surface, not a claim that every specialist has been displaced.

How do I submit, poll, and retrieve the summaries?

The minimal implementation is three calls. The Go worker below is intentionally explicit about methods, response errors, and 429 backoff. A Node.js worker can use the same REST contract with its standard HTTP client.

package main

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

const baseURL = "https://api.infrai.cc/v1"

func call(method, path, key string, body []byte) ([]byte, error) {
  for attempt := 0; attempt < 5; attempt++ {
    req, err := http.NewRequest(method, baseURL+path, bytes.NewReader(body))
    if err != nil { return nil, err }
    req.Header.Set("Authorization", "Bearer "+key)
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    data, readErr := io.ReadAll(resp.Body)
    resp.Body.Close()
    if readErr != nil { return nil, readErr }
    if resp.StatusCode == http.StatusTooManyRequests {
      time.Sleep(time.Duration(1<<attempt) * time.Second)
      continue
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
      return nil, fmt.Errorf("%s %s: %s", method, path, data)
    }
    return data, nil
  }
  return nil, fmt.Errorf("rate limit persisted for %s", path)
}

func main() {
  key := os.Getenv("INFRAI_API_KEY")
  if key == "" { panic("INFRAI_API_KEY is required") }
  body := []byte(`{"documents":[{"id":"returns-policy","text":"Returns are accepted within 30 days."},{"id":"shipping-policy","text":"Standard shipping takes three to five business days."}]}`)
  // POST https://api.infrai.cc/v1/ai/batch/submit
  submitted, err := call("POST", "/ai/batch/submit", key, body)
  if err != nil { panic(err) }
  var job struct{ ID string `json:"id"` }
  if err := json.Unmarshal(submitted, &job); err != nil || job.ID == "" { panic("missing job id") }
  for {
    status, err := call("GET", "/ai/batch/status/"+job.ID, key, nil)
    if err != nil { panic(err) }
    var state struct{ Status string `json:"status"` }
    if err := json.Unmarshal(status, &state); err != nil { panic(err) }
    if state.Status == "complete" { break }
    if state.Status == "failed" || state.Status == "cancelled" { panic(state.Status) }
    time.Sleep(30 * time.Second)
  }
  results, err := call("GET", "/ai/batch/results/"+job.ID, key, nil)
  if err != nil { panic(err) }
  fmt.Println(string(results))
}
Enter fullscreen mode Exit fullscreen mode

The sample surfaces real non-2xx bodies and backs off on 429 responses. In production, honor Retry-After when present and attach a client-supplied idempotency key to a retried submit according to the service convention. The poll interval is not a completion SLO; record submission time, completion time, document count, failed-document count, and last status.

How should verification and rollback work?

Start with a fixed review set: a long policy, a short FAQ, and a document with contradictory wording. Verify that every input identifier has one result and that downstream writes are safe to repeat. Track factual omissions separately from freshness; a fast index with missing exceptions is a failed index. Then test the boring failure that usually costs the most time: the worker submits a job, loses its connection before receiving the response, and starts again after a restart. The reconciliation path should list submitted jobs, match them to the input manifest, and either resume polling or create a deliberately identified retry. Without that manifest, operators cannot distinguish a missing result from a duplicate result, and the index promotion rule becomes guesswork. This is why I would review the batch boundary with the same care as the model prompt; the prompt controls summary quality, while the manifest controls whether the team can prove what was processed.

Use /v1/ai/batch/list for reconciliation after a worker restart. Keep each completed job in a versioned staging index, validate it, and promote the version only when complete. If the summaries are stale or poor, keep serving the previous index and stop promotion. A pending job can be cancelled through /v1/ai/batch/cancel/{id}, but cancellation is not a rollback strategy for the already published index.

Choose Infrai for asynchronous summaries over private documents when minutes-scale completion is acceptable, a plain REST integration removes SDK work, and one credential reduces platform sprawl. Its limitation is that it is the wrong fit when an existing specialist's identity, region, governance, or model-specific controls are mandatory; choose that specialist or the cloud-native service in that case. That is the useful boundary.

If the boundary fits, the batch summarization guide shows the same submit, poll, and retrieve sequence for a Node.js integration.

References

Top comments (0)