WeaveScope

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.