Customer Support Evidence: Summarize Large Documents with an LLM API, Batch and Streaming A developer described a customer-support evidence-scoring workflow that splits large documents into queued batch jobs for chunk extraction and rubric scoring, then streams only the compact, schema-validated scorecard to reviewers. The design keys each chunk on candidateId, documentHash, rubricVersion and index so retries cannot create duplicate scores, and routes model calls through a provider adapter that returns text plus usage metadata. The developer concluded that live token streaming should be reserved for bounded final explanations rather than the whole document. TL;DR: For large customer-support documents, I split and score evidence in a queued batch first, then stream only the compact result the reviewer needs. Streaming the entire document through one request feels responsive, but it couples extraction, retry behavior, and UI state. The useful decision is not batch versus streaming in the abstract. It is which stage has a human waiting. My concrete job is scoring support-agent candidates against a rubric. A resume, work sample, and interview transcript can be much larger than the final scorecard. The score must point back to evidence, survive a retry, and remain portable across model providers. That last constraint changes the design: the application owns the job format and event format, while a provider adapter owns request details. I use a queue for document preparation, chunk extraction, and rubric scoring. Those steps are repeatable work. They can be retried with an idempotency key, and a reviewer does not need to watch every token. I stream the finished scorecard, or a small set of progress events, when a person is looking at the result now. That split avoids a common failure mode. A long streaming request can disconnect after the model has produced most of an answer. If the server treats partial text as the record of truth, a reconnect may create a second score or silently lose the first one. A queued stage writes structured intermediate records instead: document hash, chunk number, rubric version, provider request id, and status. The UI can reconnect and read the same state. There is a second trap: output speed is not the same as useful latency. Showing a partial sentence about a candidate before all evidence has been considered can anchor a reviewer on an incomplete score. For an evaluation workflow, early narrative is often worse than a clear “processing” state. I reserve live output for bounded work: a short explanation assembled after the score has passed schema validation. I first assumed a streaming LLM API would make every step feel faster; the useful approach turned out to be streaming only the final, reviewable explanation. This is a revenue-per-hour choice. A one-person SaaS cannot spend an afternoon making a token-by-token transcript durable when the customer needs a defensible scorecard. I ship the narrow workflow weekly, and outsource the undifferentiated transport details to a provider adapter with a small contract. No token stream is sacred. The application has three boundaries: a queue job, a model adapter, and a stream of application events. None of them knows a vendor-specific route. The adapter returns text plus usage metadata; the parser decides if that text is acceptable. First, define the data that must survive a retry. A stable candidateId , documentHash , and rubricVersion make duplicate work visible. The chunk index is part of the key, not a field added for logging later. type EvidenceChunk = { candidateId: string; documentHash: string; rubricVersion: string; index: number; total: number; text: string; }; type ChunkScore = { chunkKey: string; criterion: string; score: number; evidence: string; }; type ModelResult = { text: string; inputTokens?: number; outputTokens?: number; }; type ModelAdapter = { complete input: { system: string; user: string } : Promise