TypeSafe
TypeSafe's Jev answers typed decision questions with probabilities. It does not generate chat text or run an agent loop. Use it for classification, scoring and routing, then let ordinary code act on the answer.
Setup and first decision
import Config
config :beam_weaver, :typesafe, api_key: System.get_env("TYPESAFE_API_KEY") || System.fetch_env!("TYPESAFE_API")
Put this in your application's runtime configuration, then call the decision interface rather than the chat interface:
alias BeamWeaver.Core.DecisionModel
alias BeamWeaver.TypeSafe.Question
{:ok, jev} = BeamWeaver.Models.init_decision_model("typesafe:jev-1.13.0", timeout: 60_000)
{:ok, response} =
DecisionModel.invoke(jev, %{
state: %{ticket: "Checkout crashes for every customer. Nobody can pay."},
questions: %{
urgent: Question.noul(instructions: "Does `ticket` describe an outage blocking customers?"),
team:
Question.choice(
instructions: "Who should fix the problem in `ticket`?",
criteria: %{
engineering: %{covers: "Software failures and outages"},
billing: %{covers: "Invoice corrections"}
}
)
}
})
"engineering" = response.answers["team"].choice
true = response.answers["urgent"].noul > 0.5
true = is_binary(response.model)
This verifies a meaningful live decision, not just construction or response shape. Pin versions when evaluating questions/thresholds; aliases can move.
Questions and answers
| Question | Criteria | Answer |
|---|---|---|
choice/1
| 1–255 named options | Choice, confidence and probabilities |
score/1
| 2–10 ordered levels | Expected zero-based level, confidence, probabilities and legend |
noul/1
| Optional true/false descriptions | Probability of yes |
Answer keys are strings. Confidence is not proof of correctness; calibrate thresholds on held-out examples. Put the complete question in instructions and name the relevant state fields. Question IDs identify results rather than supplying semantic instructions. Keep arithmetic and hard policy rules in code.
Malformed questions fail before HTTP execution. State is text/JSON; describe unsupported media in text. The request limit is 64,000 tokens, and BeamWeaver has no local Jev tokenizer guaranteeing an exact preflight count.
A score uses ordered levels and returns an expected zero-based level. Check that this clear outage is near the blocked end of the scale:
{:ok, scored} =
DecisionModel.invoke(jev, %{
state: %{ticket: "Checkout fails for all customers; nobody can complete a purchase."},
questions: %{
severity:
Question.score(
instructions: "Rate the operational severity described in ticket.",
criteria: ["Cosmetic only", "Degraded functionality", "Blocked functionality"]
)
}
})
true = scored.answers["severity"].score > 1.5
Options
Decision constructors accept credentials, timeout, base URL and transport options. DecisionModel.batch/3 supports bounded concurrency. Models.cached/3 can cache decision responses; choose keys and freshness requirements deliberately. Native version/usage data remains on the response.
Route an agent
Routing requires both the classifier key and credentials for the selected chat models. Configure OpenAI as in Installation before this example. A simple greeting should select the fast route:
defmodule MyApp.RoutedAgent do
use BeamWeaver.Agent
model "openai:gpt-5.4-mini", max_tokens: 256, timeout: 120_000
system_prompt "Answer briefly."
middleware [
BeamWeaver.Agent.Middleware.TypeSafeModelRouter.new(
classifier: "typesafe:jev-1.13.0",
choices: %{
"fast" => %{model: "openai:gpt-5.4-mini", criteria: "Greetings and simple everyday questions."},
"careful" => %{
model: "openai:gpt-5.6-sol",
criteria: "Complex mathematical proofs or multi-step software architecture analysis."
}
}
)
]
end
{:ok, routed} = MyApp.RoutedAgent.invoke(%{messages: ["Hello! Please say hello back."]})
%{"accepted" => true, "choice" => "fast"} = routed.model_route
true = byte_size(routed.messages |> List.last() |> BeamWeaver.Core.Message.text()) > 0
The router chooses once per run. Below min_confidence (default 0.8), on refusal or on classifier failure, it retains the agent's base model and records why. Check model_route.accepted; a normal chat answer alone does not prove classification worked. The chosen model uses its own connection/credentials and compatible options. Put routing before fallback when combining them.
classifier: also accepts another supported decision model, including OpenAI decisions. Image-aware classifiers can receive supported images; Jev receives text with placeholders for unsupported media.
Models
Limits are local profile data. A dash means not applicable or not recorded, not an unlimited allowance.
| Model | Input / output tokens | Operations and capabilities |
|---|---|---|
typesafe:jev-1.13.0
| 64,000 / — | decision; text |
typesafe:jev-1.14.0
| 64,000 / — | decision; text |
typesafe:jev-latest
| 64,000 / — | decision; text |
typesafe:jev-preview
| 64,000 / — | decision; text |
jev-preview resolves to Jev 1.14.0; jev-latest resolves to Jev 1.13.0. Pin a version for repeatable evaluations, and use response.model to identify the served version. Both models cost $0.042 per million input tokens, with free output, and accept 64K tokens per request and 32K per question. See
TypeSafe's model reference
for pricing, limits and account availability.
Testing
There is no fake decision-model family. Use a controlled transport or cassette to return a typed native response in application tests, then assert choice, probability and fallback behavior. Such a fixture must not be reported as successful live Jev execution. Testing explains replay and how to separate these checks.
Further reading
The official TypeSafe documentation describes Jev decisions. Use Testing to check application routing independently of the live classifier.