Skip to content

Batches

systemOneAll answers several independent requests concurrently, with a bounded number in flight and one result per request.

When a batch is the right shape

Most of this API is used by packing many questions into one call. The service reads the state once and answers every question against it in parallel, so a third question costs far less than a third call.

A batch is the other shape — the same question asked about many different states. That is irreducibly one call per item, and it is what several of the documented recipes need:

Recipe Why it is one call per item
Reranking each (query, passage) pair is scored on its own
Passage classification each passage is screened on its own
Self-consistency the same request drawn N times
Hierarchical classification one choice per level of the tree

Not a substitute for batching questions

If the states are the same and only the questions differ, you want one systemOne call, not a batch. It is cheaper, faster and returns the same answers.

Quick Start

List<JevBatchResult<SystemOneResponse>> results =
        client.systemOneAll(requests, JevBatchOptions.ofConcurrency(4));

for (JevBatchResult<SystemOneResponse> result : results) {
    if (result.succeeded()) {
        handle(result.orThrow());
    }
    else {
        log.warn("request {} failed: {}", result.index(), result.failure().getMessage());
    }
}

The single-argument overload uses JevBatchOptions.defaults().

Results stay aligned with requests

results.get(i) always answers requests.get(i), whatever order the calls completed in. Each result also carries its own index().

public record JevBatchResult<T>(int index, @Nullable T value, @Nullable TypeSafeException failure) {
    boolean succeeded();
    T orThrow();       // rethrows the original typed exception
    T orElse(T fallback);
}

One failure does not cost the batch

A request that fails for good puts its typed exception in its own slot and the rest still run. That matters for exactly the recipes above: losing forty good passage scores to one bad call would be a poor trade.

// Everything that came back, ignoring the failures
List<SystemOneResponse> ok = results.stream()
        .filter(JevBatchResult::succeeded)
        .map(JevBatchResult::orThrow)
        .toList();

Transient failures are retried inside each request by the client's own retry policy before a slot is recorded as failed, so a slot holding a failure means the request was retried and still did not succeed.

Configuration

JevBatchOptions options = JevBatchOptions.ofConcurrency(8)
        .withFailFast(true)
        .withExecutor(myExecutor);
Method Default Description
JevBatchOptions.defaults() concurrency 4, collect failures, own pool The usual case.
ofConcurrency(int) How many requests may be in flight at once.
withFailFast(boolean) false Stop submitting after the first failure. Requests that had not started report "Batch aborted".
withExecutor(Executor) null Run on your executor, which the batch never shuts down. Without one, a pool is created and disposed per batch.

Why four? It is the pooling the TypeSafe passage-classification cookbook uses, and it sits far below the published limit of 1,200 requests per minute. Raise it when your per-batch latency matters more than your headroom against the rate limit; a 429 is retried by the retry policy, but it is still a wasted round trip.

Java 17

The SDK targets Java 17 and so cannot create a virtual-thread executor itself. On a newer runtime, pass one with withExecutor(...).

Worked example: scoring a candidate list

List<SystemOneRequest> requests = passages.stream()
        .map(passage -> SystemOneRequest.builder()
                .state(Map.of("query", query, "passage", passage.text()))
                .model(client.defaultModel())
                .question("answers_query", relevanceQuestion)
                .build())
        .toList();

List<JevBatchResult<SystemOneResponse>> results = client.systemOneAll(requests);

List<Passage> ranked = IntStream.range(0, passages.size())
        .filter(i -> results.get(i).succeeded())
        .boxed()
        .sorted(Comparator.comparingDouble(
                i -> -results.get(i).orThrow().noulValue("answers_query")))
        .map(passages::get)
        .toList();

JevDocumentReranker is this, packaged as a Spring AI DocumentPostProcessor.

See Also