jevr
September 20, 2026 ยท View on GitHub
jevr
jevr is an R client for JEV / TypeSafe System
One. It sends a state and
a set of typed questions to a model and returns structured answers that
can be used directly in R.
JEV is designed for structured decisions rather than conversational chat. The package supports multiple questions in one evaluation, probabilities, confidence, usage, and provider metadata.
Installation
Install the development version from GitHub:
install.packages("pak")
pak::pak("simxnherrera/jevr")
Once jevr is available on CRAN, the stable version can be installed
with:
install.packages("jevr")
Authentication
Set the API key for the provider you want to use in your .Renviron
file:
usethis::edit_r_environ()
# Direct TypeSafe requests
TYPESAFE_API_KEY=your_typesafe_key
# OpenRouter requests
OPENROUTER_API_KEY=your_openrouter_key
Keys are read when jev_ask() is called and are not included in
returned objects.
Basic usage
Define one or more questions, then pass them together with the state to
jev_ask():
library(jevr)
questions <- list(
department = jev_choice(
instructions = "Which team should handle this?",
criteria = c(
billing = "Payments, invoicing, refunds",
technical = "Bugs, outages, integrations",
sales = "Pricing, upgrades, new accounts"
)
),
urgent = jev_noul(
instructions = "Does this request convey urgency?"
)
)
result <- jev_ask(
state = "Help! My payouts have been failing for three days.",
questions = questions
)
result$answers$department$choice
result$answers$department$probabilities
result$answers$urgent$noul
The names in questions become the IDs in result$answers. This makes
it possible to send several questions in one request without losing the
mapping between each question and its answer.
jev_ask(state, questions) represents one evaluation of one state. For
a collection of states, use jev_map(states, questions): each state is
evaluated independently, with bounded HTTP concurrency. jev_map() is
not model-level batching of multiple records into one state or one
request.
state can be a character string, a named list representing a JSON
object, or an unnamed list representing a JSON array.
Question types
Choice
jev_choice() selects one option from a named set. The answer contains
the selected option, the probability for each option, and model
confidence.
jev_choice(
instructions = "Which priority applies?",
criteria = c(
low = "Can wait",
high = "Needs attention soon"
)
)
Score
jev_score() places the state along an ordered scale of two to ten
levels. The answer contains the score, its legend, probabilities, and
confidence.
jev_score(
instructions = "How severe is the issue?",
criteria = c(
"Minor inconvenience",
"Material disruption",
"Service unavailable"
)
)
Noul
jev_noul() estimates how strongly a statement is true and returns a
numeric value between 0 and 1. The value is not converted to TRUE or
FALSE.
jev_noul(
instructions = "Does the request require immediate action?",
criteria = list(
true = "The request describes an immediate operational risk",
false = "The request can wait without immediate harm"
)
)
Providers
TypeSafe
TypeSafe is the default provider. It uses the jev-latest model alias
unless a different model is supplied:
result <- jev_ask(
state = list(
customer = "Acme",
message = "My payouts have been failing for three days."
),
questions = questions,
provider = "typesafe"
)
The direct TypeSafe provider supports structured JSON values for
state, instructions, and criteria.
OpenRouter
OpenRouter can be selected without changing the question definitions:
result <- jev_ask(
state = "Help! My payouts have been failing for three days.",
questions = questions,
provider = "openrouter"
)
It uses ~typesafe/jev-latest by default and authenticates with
OPENROUTER_API_KEY. Structured JSON instructions and criteria are
preserved when the provider contract accepts them.
Versioned specifications and multiple states
Use jev_spec() when a definition must be identified and compared over
time:
spec <- jev_spec(
name = "claim_validation",
version = "1.0.0",
questions = questions
)
The spec contains only data and a recalculable SHA-256 hash. It does not store credentials, provider clients, callbacks, or operational settings.
Use jev_definition_manifest() to inspect the canonical data-only
definition. jev_spec_hash() and jev_state_hash() provide stable
hashes for a definition and a state. jev_execution_id() combines the
state, definition, provider, model, and inference semantics into a
reproducible execution identity:
jev_definition_manifest(spec)
jev_spec_hash(spec)
jev_state_hash(list(document = "First document"))
jev_execution_id(
state = list(document = "First document"),
definition = spec,
provider = "typesafe",
model = "jev-latest"
)
jev_map() evaluates explicit states independently with bounded
concurrency and returns a named jev_result_set. Failures stay attached
to their state instead of aborting the whole collection:
states <- c(
document_001 = "First document",
document_002 = "Second document"
)
results <- jev_map(
states,
spec,
concurrency = 4,
on_result = function(result) {
# Persist the terminal result in caller-owned storage.
}
)
as.data.frame(results)
attr(results, "requests")
The callback is the persistence boundary: jevr does not manage a
database, checkpoint files, or a distributed job queue. For large
sources, read chunks outside the package and call jev_map() for each
chunk. concurrency limits requests in flight; rate_limit controls
admission separately.
When the provider returns a valid response envelope but an individual
answer cannot be validated, jev_map() returns status = "partial",
keeps the valid answers, and records the invalid questions in
question_errors. The raw response remains available for audit.
attr(results, "requests") contains one row per physical HTTP attempt;
duration_seconds is the per-response HTTP timing reported by httr2 and
can be NA when the backend does not provide a reliable value.
Per-request ledger timestamps are NA because the public parallel API
does not expose reliable start and finish timestamps after the wave
returns.
The development cancellation probe also documents a limitation of the
current public httr2 API: when an active wave is interrupted and httr2
drains every active request, it can return a complete response list
without an explicit interrupted flag. jevr handles observable NULL
results conservatively, but cannot distinguish that drained wave from a
normal wave. Closing that gate requires a transport adapter that returns
an explicit interrupted flag; jevr does not introduce a second transport
backend in this release.
Response object
jev_ask() returns an object of class jev_response with these main
fields:
model: model used for the request.answers: named answers corresponding to the supplied questions.usage: input and output token counts returned by the provider.metadata: provider and upstream request metadata.raw: the unmodified provider response.
Use ?jev_ask, ?jev_map, ?jev_spec, ?jev_definition_manifest,
?jev_spec_hash, ?jev_state_hash, ?jev_execution_id, ?jev_choice,
?jev_score, and ?jev_noul for the complete argument reference.