Concepts
Collections
filter, some, every, rank, find and compare over lists. Which cost one request and which cost N.
Batching sends many questions about one state. Collections are the other axis: one question about many items. Because a request carries a single state, most collection operations cost one request per item. Two of them do not.
Cost at a glance#
| Operation | Requests | Use when |
|---|---|---|
find(items, condition) | 1 | You want the single best match, or none. |
compare(a, b, { by }) | 1 | You want to know which of two is better. |
filter(items, condition) | N | You need every match. |
some / every | N | Built on filter. |
rank(items, { by }) | N | You need the whole order. |
N-request operations run up to concurrency items at a time, 8 by default.
find() — the best item, in one request#
const dupe = await semantic.find(openBugs, "This report describes the same bug as the new one.", {
label: (bug) => `${bug.title}\n${bug.body}`,
})
// the matching bug, or undefinedThe items become the options of a single choice. Alongside it travels an existence check: does any candidate actually satisfy the condition? That second question is not optional. Choice probabilities always sum to 1, so something always wins, including when nothing fits. Without the existence check, an empty match returns confident nonsense. find returns undefined when the existence probability is at or below threshold.
If there are more items than the provider's maxChoiceOptions (255 for Jev), find falls back to rank and returns the top item if it clears the threshold.
compare() — two values, one criterion#
const better = await semantic.compare(draftA, draftB, {
by: "Answers the customer's actual question directly.",
})
// "left" | "right" | "equal"Both values are sent in one state under left and right. Use label to control how each is rendered.
filter(), some(), every()#
const complaints = await semantic.filter(messages, "This message is a complaint.", {
falseWhen: "A question, a feature request, or praise.",
concurrency: 4,
})
await semantic.some(messages, "This message mentions a competitor.") // boolean
await semantic.every(messages, "This message is in English.") // booleanEach item is judged in its own request against the condition and kept when its probability exceeds threshold.
rank() — order by fit#
const ordered = await semantic.rank(candidates, {
by: "This candidate fits the query.",
context: query,
})
// [{ item, score }, ...] sorted by score, descendingcontext is shared state every candidate is judged against, sent with each one as { context, candidate }. The same operation is available on a context, with the wrapped value as the context:
const ordered = await semantic(query).rank(candidates, { by: "This candidate fits the query." })Options#
| Option | Type | Default | Description |
|---|---|---|---|
| label | (item, index) => string | — | How to render an item as state. Strings pass through; other values default to JSON. |
| concurrency | number | 8 | Items evaluated at the same time for N-request operations. |
| threshold | number | 0.5 | Probability above which an item counts as matching. |
| trueWhen | string | — | What clearly counts ( |
| falseWhen | string | — | What clearly does not ( |
| cache | string | number | — | TTL per item answer. |
| timeoutMs | number | — | Per-request timeout. |
| samples | number | — | Rounds per item. Multiplies the request count. |
| provider | SemanticProvider | — | Override the provider for this call. |
When items are small: pack them yourself#
If each item is short and there are not too many, putting them all into one state and asking one question per item is a single request instead of N. The RAG gate scenario does exactly this with twelve passages:
const context = semantic({ question, passages: { p0: "...", p1: "...", p2: "..." } })
const verdicts = await context.batch({
p0: is("Passage p0 contains information that helps answer the question."),
p1: is("Passage p1 contains information that helps answer the question."),
p2: is("Passage p2 contains information that helps answer the question."),
})The trade-off is context size: every passage is in every question's state. It pays off while the whole set fits comfortably in maxStateTokens.
Where they live#
Collections are available three ways, all with the same signatures:
import { filter, find } from "jevascript" // bound to the module-level instance
semantic.filter(items, "...") // on a createSemantic() instance
semantic(query).rank(items, { by: "..." }) // rank, with the wrapped value as context