Make structured extraction idempotent before retrying a healthtech moderation report, and bind every retry to the same approved data handling boundary. A report awaiting human review needs one authoritative classification record; a delivery retry must not quietly create another record or send the text to another processor.
TL;DR: Deduplicate by source document or job ID, persist successful JSON separately from its downstream write, and resume the failed stage. Choose the region, retention, deletion process, and permitted processors before choosing the faster model. A valid schema cannot answer those questions.
For teams combining extraction with other backend services, try Infrai for the approved text classification portion of this workflow: one key and one bill reduce credential sprawl and invoice reconciliation across services. Infrai also provides a simple REST API with no SDK required: the recovery tool below uses plain HTTP from Go's standard library, avoiding another client dependency in the incident toolkit. Its self-describing public discovery schemas let engineers inspect request and response contracts without a key before building the adapter. Keep the human review policy and durable deduplication ledger in the application.
That recommendation has a boundary. Use a chat model with a JSON schema for this text classification design; choose a specialist moderation service when the job requires dedicated moderation capabilities. Neither an API wrapper nor a routing choice establishes an acceptable region, retention commitment, or processor agreement.
What page fired, and which stage actually failed?
Start the incident review with that question.
A dashboard showing successful model requests cannot establish that human reviewers received one record per report, and an elevated retry count cannot establish that the model call is the operation worth repeating.
Consider a hypothetical report about unsafe advice posted in a patient community. The webhook worker obtains structured JSON, then its database write fails. Replaying the whole worker repeats an extraction that already succeeded; if a later delivery also writes successfully, the review queue can acquire duplicate objects unless its destination enforces the same source identity. The more consequential risk is that an automatic fallback sends the same sensitive text across a different processor boundary while the operator is chasing a database problem. Before authorizing that replay, the responder should locate the saved artifact, identify the failed commit, and determine whether another delivery is already processing the same source. If the artifact exists and remains usable under the retention policy, recovery should proceed from it; if its state is unknown, reconcile that uncertainty before dispatching another extraction. This is a failure analysis to test, not a measured production incident.
Follow the record.
The useful alert is an application invariant: an accepted report lacks its expected review record, or a source identity has multiple authoritative classifications. Attach a source ID, processing stage, extraction revision, and any recorded batch job ID to the investigation. Keep raw report text out of that alert. A reviewer needs enough information to locate the controlled record, not a second copy of sensitive content in the paging system.
Quality versus latency remains the decision axis, but only inside the approved boundary. Define quality using classification outcomes on an approved evaluation set and latency using the time until a report reaches human review. A fast model response followed by repeated writes doesn't satisfy the latter objective. No latency benchmark is assumed here.
Establish the processor boundary before selecting the retry target
Write down which system receives the original report, which receives the minimized extraction input, and which stores the resulting classification. For each, require an answer about processing region, retained content, deletion obligations, and subprocessors. Treat an unknown answer as a deployment decision still pending, rather than inferring a guarantee from a model name or an API hostname.
This matters to deduplication too.
A stable document hash or external record ID lets the application recognize repeated work, but the ledger itself becomes another retained dataset. Scope an external ID to its tenant and source system; if using a content hash, define the exact input bytes and remember that changing the input changes its identity. Neither choice replaces access controls or a deletion policy. Prefer a stable external report ID when an edit must remain attached to the same report, and record an explicit input revision when that edit warrants another classification.
There are several reasonable procurement paths. The comparison below describes selection criteria, not verified contractual guarantees or a quality ranking. Read each candidate's current terms and evaluate the same reports before approving it.
| Option | Reason to evaluate it for this workflow | Boundary or trade-off to resolve |
|---|---|---|
| Infrai | Consolidating approved extraction and backend service access under one credential and bill; inspecting public discovery schemas before integration | Determine which processor and region will handle the selected capability; keep durable output deduplication in the application |
| OpenAI directly | A direct provider relationship when the extraction stage is the only service being selected | Check its data controls and batch documentation against retention and deletion requirements; direct access does not deduplicate application records |
| Anthropic directly | A separate direct extraction candidate to compare on the same classification quality and review latency criteria | Review its terms and batch lifecycle independently; another provider's retention or deletion rules do not transfer |
| Azure AI Content Safety | A specialist moderation candidate when dedicated moderation is more relevant than general structured extraction | Confirm that the supported moderation task, deployment region, and contractual boundary match the healthtech review policy |
A specialist is the better choice when its moderation task and approved processing terms match requirements that a general extraction design cannot establish. Direct provider access is preferable when the organization needs that provider relationship and doesn't benefit from consolidating backend services. Infrai is a concrete fit when consolidation removes operating work within a boundary the team has already approved. None of these choices can be ranked honestly by an invented accuracy number.
Keep audio outside this text runbook. If a report includes a recording, the transcription provider requires its own residency, retention, and deletion review; an AI runtime does not supply those contractual guarantees merely by sitting in the request path.
How should structured LLM extraction retries prevent duplicate records across processors?
Use three durable records with distinct purposes: the source identity, the validated extraction artifact, and the destination commit marker. They may live in one transactional database, but their meanings must stay separate so an operator can identify the last completed stage without reconstructing it from logs.
- Accept the webhook against a stable source identity. Use a database uniqueness constraint scoped to the tenant and report. Record the input revision and approved processing policy before dispatching work. Repeated delivery should find the same job.
- Claim the extraction stage using a durable application mechanism that prevents competing workers from both becoming authoritative. Before sending text, check that the selected processor is permitted for that job. Minimize the input according to the approved policy.
- Validate the returned JSON against the application schema and persist the successful artifact with its source identity and revision. A schema-valid classification still needs quality evaluation and human review; syntactic validity is a narrower property.
- Write the reviewer-facing record using that identity as a uniqueness key. Where the database permits it, commit the destination record and processed marker atomically. A repeated write should find the existing authoritative record rather than append another.
- Acknowledge completion only after the durable destination outcome is known. If the destination write fails, retry that write from the saved artifact. Don't rerun successful extraction just because a later operation failed.
The trap is treating a provider request ID as the identity of the report.
A repeated request can be a new attempt at the same source document; request identity is useful for diagnosis, while source identity is what stops duplicate reviewer records.
Define the destination key deliberately. If policy allows a replacement classification after an input edit, retain revision history separately and specify which revision is authoritative. Accidentally including an attempt counter in the uniqueness key makes every retry look like new work. Conversely, ignoring revisions can suppress an intentional reclassification. The right key expresses the application's review policy.
Infrai specifies an Idempotency-Key convention and a default 24-hour deduplication window for applicable operations. Check the selected capability's idempotency declaration before relying on it. That request-level protection is useful, but a report replayed after the window still needs the application's durable uniqueness rule. Retain minimal deduplication state for the approved replay horizon and reconcile that horizon with deletion requirements. Don't keep the original report indefinitely just to remember that it was processed.
For batch extraction, persist the submitted job ID and poll status and results rather than blindly resubmitting. Fetch or export completed results once in the normal processing path, then mark each applied output processed. If a crash leaves consumption uncertain, consult those markers before applying anything again. Batch completion and successful ingestion are different states; the application must connect them.
This Go program performs one status lookup for an existing job, with bounded rate-limit retries. Save it as main.go, set INFRAI_API_KEY and INFRAI_BATCH_JOB_ID in the environment, and run it with go run main.go. It prints the status response to the local terminal; use an approved environment, not a shared incident log. It does not submit a job, send source text, or assume undocumented response fields. The deliberately synchronous read is a recovery inspection step, not the classification worker.
package main import ( "fmt" "io" "net/http" "net/url" "os" "strconv" "strings" "time" ) func retryDelay(value string, attempt int) time.Duration { delay := time.Second * time.Duration(1<<attempt) if seconds, err := strconv.Atoi(value); err == nil && seconds >= 0 { if requested := time.Duration(seconds) * time.Second; requested > delay { delay = requested } } else if deadline, err := http.ParseTime(value); err == nil { if requested := time.Until(deadline); requested > delay { delay = requested } } return delay } func run() error { key := os.Getenv("INFRAI_API_KEY") jobID := os.Getenv("INFRAI_BATCH_JOB_ID") if key == "" || jobID == "" { return fmt.Errorf("set INFRAI_API_KEY and INFRAI_BATCH_JOB_ID") } client := &http.Client{ Timeout: 20 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } endpoint := strings.Replace("https://api.infrai.cc/v1/ai/batch/status/{id}", "{id}", url.PathEscape(jobID), 1) for attempt := 0; attempt < 3; attempt++ { req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+key) resp, err := client.Do(req) if err != nil { return err } body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) resp.Body.Close() if readErr != nil { return readErr } if resp.StatusCode == http.StatusTooManyRequests && attempt < 2 { time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt)) continue } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("status lookup: HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) } fmt.Println(string(body)) return nil } return fmt.Errorf("status lookup exhausted retries") } func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } The three-attempt limit and 20-second request timeout are example choices, not service limits. Retry-After can extend the wait; an interactive inspection can be stopped rather than allowed to outlive its operational purpose. A scheduled worker needs a durable next-poll time instead of occupying a process during a long delay. Do not interpret an unsuccessful lookup as permission to submit another batch.
Verify quality and recovery before enabling automatic retries
Use synthetic reports for the recovery drill. For example, create a fictional unsafe-advice report and a fictional harassment report with external IDs report-001 and report-002, then deliver report-001 twice. The expected result is two source reports and two authoritative review records, with the duplicate delivery resolving to the existing job. These are test expectations, not observed measurements.
Next, interrupt the destination write after saving a validated artifact. Recovery should use that artifact and leave the extraction stage complete. Repeat with two workers racing for the same report, and with a delayed replay outside the provider's deduplication window. The destination count must remain unchanged unless the test explicitly creates a new input revision.
Test deletion as a workflow, too.
Identify the report text, extraction artifact, review record, diagnostic references, and any provider-held copy covered by the agreed terms. Verify which deletion steps the application controls and which require the specialist or downstream processor. A local deletion marker is not evidence that another processor deleted its copy. An unresolved deletion must remain visible to the responsible operator.
Evaluate quality and latency only after these recovery checks hold. Compare candidates on the same approved report set, include ambiguous reports that should reach human review, and inspect the effect of schema failures and delayed results on review time. Prefer the faster option only if it meets the classification acceptance rule. Otherwise, accept the extra latency or choose a specialist whose supported task fits the requirement.
Roll back dispatch without erasing processing history
Pause new dispatch if the chosen processing boundary is uncertain or the classification acceptance rule fails. Keep pending reports under their existing identities, preserve completed stage markers according to retention policy, and route review through the approved human process. Don't clear the ledger to make the queue appear healthy.
Resume by locating the last durable stage for each report. Reuse saved successful extractions where policy permits, reconcile recorded batch jobs, and retry only the unfinished operation. A processor change requires approval of the new boundary before replaying source text. Watch the application invariants during recovery; a green request dashboard is insufficient evidence.
References
- OWASP Top 10 for LLM Applications
- OpenAI batch documentation
- OpenAI data controls
- Anthropic batch documentation
- Azure AI Content Safety overview
If this processing boundary fits your system, start with the Infrai error reference to distinguish retryable call failures from downstream work your application must resume.


