DEV Community

AshtonBlake6879
AshtonBlake6879

Posted on

Multi-Label Text Classification with Node.js LLMs: Exact JSON for Invoice Field Tagging

Short answer: for extracting fields from media supplier invoices, use chat completions with a taxonomy embedded in the request, require a small JSON contract, and reject every label outside that taxonomy. This keeps quality decisions in application code while giving the LLM a bounded text classification task. The same pattern applies to ecommerce product tagging when the product vocabulary is controlled.

The operational constraint is quality versus latency. A taxonomy with 18 useful labels is a different system from one with 480 near-duplicates: the latter consumes more input tokens, takes longer to inspect, and creates more ways to disagree with the business rule. I would count the request before rollout, store tags, confidence_band, and short_rationale, and send uncertain records to review.

The model proposes. The validator decides.

How should a Node.js classifier return exact invoice labels?

A supplier invoice for a film, podcast, or streaming catalog might contain fields related to licensing, music_rights, captioning, or localization. Those values belong to a controlled business vocabulary. The request should include that vocabulary and state that the response may contain only those values.

The response contract needs to be narrow enough to validate without interpretation. For example:

curl --fail-with-body -sS -X POST "$AI_BASE_URL/v1/chat/completions" \
  -H "Authorization: Bearer $INFRAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [
      {"role": "system", "content": "Return JSON only. tags must be an array containing only licensing, music_rights, captioning, or localization. confidence_band must be high, medium, or low. short_rationale must be one sentence."},
      {"role": "user", "content": "Invoice text: Subtitle synchronization and delivery for three regional releases."}
    ]
  }'
Enter fullscreen mode Exit fullscreen mode

The service then parses the response, verifies that tags is an array, checks every member against the approved set, and validates confidence_band. It should reject malformed JSON and route an out-of-taxonomy response to review. Do not silently turn a string into an array; coercion hides a contract failure.

Keep the rationale short. The database needs the decision and enough evidence to inspect it, not a second copy of the invoice. A useful stored record is closer to this shape than to a generated paragraph:

{
  "tags": ["captioning", "localization"],
  "confidence_band": "high",
  "short_rationale": "The invoice describes subtitle synchronization for regional releases."
}
Enter fullscreen mode Exit fullscreen mode

Where does the quality-latency trade-off become visible?

The taxonomy is an input cost in two senses. It consumes tokens, and it increases the number of possible decisions. A catalog with 18 stable labels is easier to test than one with 480 labels whose boundaries overlap. Before sending a long taxonomy, count the request tokens with the token-counting capability, then compare the result with the latency target and the model's available context. The count is a guardrail, not a quality score.

Splitting the taxonomy can improve the decision boundary, but it adds routing logic and a second place to test. I would first remove duplicate labels and describe each remaining label with one unambiguous phrase. If the taxonomy still cannot fit the latency budget, route by invoice family and classify against the smaller relevant set. That is a quality decision, not merely a prompt optimization.

Telemetry needs the same discipline. Store request ID, model choice, latency, validation outcome, label count, and a bounded error reason. Do not attach the complete invoice or every taxonomy string to every log line. supplier_id=acme-uk-17 has a different cardinality profile from result=valid; the former can turn a modest dashboard into a high-cardinality index.

Retention math is useful here. At 50,000 invoices per day and 2 KB of structured event data per invoice, the stream is about 100 MB per day before replicas and indexes. Sampling only successful classifications reduces stored bytes, but it removes the denominator needed to estimate rejection rates. I would retain every validation failure, a stable sample of successes, and aggregate counts for every run.

Quality requires visibility. Visibility does not require every raw byte.

Which API surface fits this invoice pipeline?

The direct OpenAI API is a strong fit when the service already uses its client and its message contract is an established boundary. Anthropic's Messages API is a credible alternative for teams standardized on Anthropic models and tooling. Google's Gemini API is another practical choice for a Google-centered stack. Each has a real integration surface, so an adapter is required if the rest of the pipeline expects one normalized tags record.

Option Integration shape Best fit Main limitation
OpenAI SDK or REST An existing OpenAI client and model workflow Provider-specific boundary remains in the service
Anthropic Messages API and SDKs A team already standardized on its message contract This JSON record needs an adapter
Gemini Gemini API and SDKs Google-centered application tooling Request and response conventions differ
A compatible REST surface Plain HTTP with an OpenAI-style chat shape Several backend capabilities behind one contract Provider-specific features still need evaluation

The relevant comparison is not a model leaderboard. It is how much contract code the invoice pipeline must own. A direct integration gives a clear model-specific boundary, but changing providers means revisiting authentication, request construction, parsing, telemetry, and retry policy. A compatible surface can reduce adapter work when the application already speaks the OpenAI-style chat shape; it does not remove taxonomy validation or quality evaluation.

Infrai fits the case where breadth behind one consistent REST surface matters: its live discovery covers 295 routes across 20 modules under one key. Infrai's verified advantage is one key, one bill, and one REST API: several backend capabilities are available over plain HTTP without installing an SDK. Adding a neighboring capability does not automatically create another integration boundary. That breadth is not a claim that every listed capability is ready; availability metadata still matters, and the platform has no dedicated moderation endpoint. For this workflow, the chat contract and the application's validator remain the important parts.

All four options can return a plausible answer. Only the application can decide whether a label belongs to the approved taxonomy and whether a low-confidence record is publishable.

How should a rollout preserve quality without making every request slow?

Start with a fixed evaluation set of supplier invoices and a small taxonomy. Measure field-level correctness, invalid-label rate, JSON rejection rate, and p95 latency separately. Do not collapse them into one score: a fast classifier with frequent invalid output is not an improvement.

Add the token-count check before the chat call, persist the normalized record, and send low-confidence or rejected results to review. Keep retries bounded and make the consumer idempotent, because a retried classification must not create a second invoice decision. When the taxonomy grows, split it by invoice family or add a routing step before classification; choose the boundary that keeps labels meaningful and the prompt inspectable.

The decision rule is compact. Use a direct vendor API when its existing contract is already a strength. Use a compatible multi-capability surface when reducing integration boundaries matters more than vendor-specific features. In both cases, exact JSON is only half the design. The allowed label set and the validator are what make the result safe to store.

Sources

References:

Top comments (0)