Decision Model
Decision Model evaluates one event with Jev through the System One API. It returns typed answers for use in filters, switches and Decision Tables. Questions independently evaluate the shared context. Use another node when instructions need a previous answer. The component does not retain conversational state across events.
When to use it
Use Decision Model to interpret unstructured input against answers you define: classify a customer reply, assess an incident on an ordered scale, or estimate whether a request meets a criterion. The result is a typed assessment rather than generated prose.
| Answer type | Configure | Read downstream |
|---|---|---|
| Choice | Named options and descriptions of their meaning | Selected option, probabilities and confidence |
| Score | Descriptions of ordered levels, from low to high | Weighted position on the scale and confidence |
| Probability of yes | A question and optional yes/no criteria | A number from 0 to 1 |
Use Decision Table after the model to combine the assessment with business facts and explicit rules. For example, the model interprets a customer's reply; the table decides whether to continue a SIM replacement, block and escalate, or ask for clarification.
Quick start: interpret a SIM replacement reply
- Configure a Decision AI integration and select a Jev model.
- Add Decision Model after the source. Select the saved Integration and Model.
- Set Context to the evidence the model needs. For an input with
customerStatement, use:
{
questionAsked: 'Did you request this SIM card replacement?',
customerStatement: #input.customerStatement
}
- In Questions, choose Edit as JSON and paste the following questions object. This editor uses System One question names and criteria; the stored scenario parameter has a different versioned schema, shown under Parameters.
{
"authorization": {
"type": "choice",
"instructions": "Does customerStatement confirm or deny that the customer requested the specific SIM replacement in questionAsked? Interpret the reply, not the customer's identity or actual authorization. Uncertain, contradictory or unrelated replies are insufficient evidence.",
"criteria": {
"confirmed": "The customer clearly says they requested this SIM replacement.",
"denied": "The customer clearly says they did not request this SIM replacement.",
"insufficient_evidence": "The reply does not clearly confirm or deny this specific replacement."
}
},
"needsClarification": {
"type": "noul",
"instructions": "Does customerStatement need clarification before we can tell whether the customer says they requested this particular SIM replacement? Judge the reply against questionAsked.",
"criteria": {
"true": "The reply is uncertain, contradictory, unrelated, conditional, or refers to another person or change.",
"false": "The reply clearly confirms or denies that the customer requested this specific SIM replacement."
}
}
}
- Set Output variable name to
assessment. Optionally enable Treat context as data to append an instruction that context text is evidence, not instructions; this does not prevent all prompt injection. Expand the output structure preview to inspect the available fields. - Test representative replies in the Testing tab before configuring routing:
| Customer reply | Interpretation to check |
|---|---|
| Yes, that was me. I lost my phone over the weekend. | Confirmation |
| Yes, that's exactly the problem: I did NOT request this SIM replacement. Please block it immediately. | Denial despite the opening “Yes” |
| No idea, my son did something with the phone yesterday. | Insufficient evidence |
These are test expectations, not guaranteed model answers. Inspect
#assessment.authorization.choice, #assessment.authorization.probabilities,
#assessment.authorization.confidence and #assessment.needsClarification.probabilityTrue.
Use a Decision Table to turn them into actions, with an explicit clarification path and thresholds
validated on your examples. A classified reply is not proof of the customer's identity or authorization.
Configure it with Nu Assistant
After saving the integration, open Nu Assistant in an existing scenario and describe the change:
Replace YES/NO matching with Jev to understand the customer's SIM replacement reply, keeping the same three outcomes.
The assistant can use the component's configuration guidance and the available integration and model definitions to configure Context and Questions and update downstream expressions. Review the proposed questions and routing, then test the same replies before saving and deploying the changed scenario.
Configure the integration
Create a Decision AI integration with a System One Base URL and saved Jev model IDs.
The initial TypeSafe URL is https://api.typesafe.ai/v1; inference uses
POST <base URL>/systemone. The saved Base URL takes precedence over application defaults.
Changing the model does not change the address. An API key is sent as a Bearer token when present.
Check connection lists models with GET <base URL>/models without running inference.
A successful listing does not prove inference permissions or question compatibility. After changing
the URL, check it again and confirm the models. Scenario validation uses saved configuration and
never connects to the service.
The password field hides the key on screen. The current Configurator API returns configured keys; this feature does not introduce a secret store or change those access controls.
Parameters
| Parameter | Meaning |
|---|---|
| Integration | Saved Decision AI integration |
| Model | Model ID saved in that integration |
| Context | Text or record computed for the current event; use SpEL or the in-field context picker |
| Questions | Static version 1 schema; the form changes with each question's answer type |
| Result mode (advanced) | Assessment (default) or Detailed result, which includes status, timings and safe error information |
| Output variable name | Name for the typed result, for example assessment |
Each question has a stable ID and instructions. IDs must match [A-Za-z_][A-Za-z0-9_]*, be unique,
and differ from the reserved metadata field model. Choice options have unique IDs and nonempty
descriptions. Nonempty descriptions are an intentional quality requirement stricter than the API's
nullable descriptions. Score questions have 2–10 ordered, nonempty level descriptions. Yes/no
questions can optionally describe each answer.
Changing type preserves ID and instructions. Choice and Score convert descriptions in order;
conversion to Score drops option IDs, and conversion back generates level_N IDs. Losing
structured details or switching a populated body to/from Probability of yes requires confirmation.
Undo restores the preceding Questions configuration without changing the context toggle.
Unknown or malformed schemas remain available in the JSON editor without automatic rewriting.
{
"version": 1,
"treatContextAsData": true,
"questions": [
{
"id": "intent", "type": "choice", "instructions": "What does the customer request?",
"options": [
{"id": "refund", "description": "Requests money back"},
{"id": "other", "description": "Another request or insufficient information"}
]
},
{
"id": "urgency", "type": "score", "instructions": "How urgently does the text request help?",
"levels": ["Routine", "During current shift", "Immediate review"]
},
{"id": "refundRequested", "type": "yesNo", "instructions": "Is a refund explicitly requested?"}
]
}
Results and policy
| Expression | Type / meaning |
|---|---|
#assessment.model | String: model identifier reported by the service; may be an alias or request echo |
#assessment.intent.choice | String: selected option ID |
#assessment.intent.probabilities.refund | Double: probability for that option |
#assessment.intent.confidence | Double: provider-defined confidence, not calibrated correctness |
#assessment.urgency.score | Double: probability-weighted, zero-based level index |
#assessment.urgency.probabilities | List of Doubles in configured level order |
#assessment.urgency.levels | List of level descriptions in configured order |
#assessment.urgency.confidence | Double: provider-defined confidence |
#assessment.refundRequested.probabilityTrue | Double between 0 and 1, with no hidden Boolean threshold |
A valid format does not imply a correct interpretation. Choose thresholds using labelled examples;
do not interpret confidence as the probability that an answer is correct. Question types and
limits also depend on the selected model. Jev reports confidence as
(pMax - 1/n) / (1 - 1/n), where pMax is the highest answer probability and n is the number
of options or levels. This describes concentration relative to a uniform distribution. A returned model ID
is not proof of a pinned underlying model version. Keep the requested integration/model separately
when comparing branches. HTTP 400/422 indicates a rejected request, which may
include model-specific option limits; HTTP 501 means the service does not support the operation.
Use a Decision Table to combine the assessment with measured facts, permissions and limits. Decision Table returns all matching rows: handle an empty list and conflicting multiple matches explicitly, for example with manual review. Do not assume first-match semantics. A text requesting a refund is not permission to issue one. Keep absent evidence distinct from a negative answer.
Detailed results and errors
Assessment preserves the existing output and fails the event on errors. To route failures in the
scenario, open the advanced parameters and choose Detailed result (API parameter Result mode, SpEL expression
'Detailed result'). The output variable then contains:
| Field | Value |
|---|---|
status | "success" or "error" |
assessment | The typed assessment above on success; null on error |
metadata.integration | Configured integration name |
metadata.requestedModel | Configured model ID |
metadata.resolvedModel | Service-reported model ID on success; null on error |
metadata.queueMs | Milliseconds waiting for a request slot |
metadata.httpMs | Milliseconds from HTTP dispatch to completion/failure; null if HTTP was not attempted |
metadata.durationMs | Total invocation milliseconds, including input evaluation, queue wait and HTTP |
metadata.requestId | Response x-request-id header, when available; otherwise null |
error | Null on success; {type: String, message: String} on error |
Filter on #assessment.status == 'success' before reading
#assessment.assessment.intent.choice. Route the other branch explicitly. The enricher has no
separate error edge. An error never becomes a negative answer or a fabricated distribution.
Timing metadata is retained on failures; it measures component execution, not model-only latency.
Error types are http (non-success HTTP status), invalid-response (malformed assessment), timeout, overloaded,
closed, request-too-large, connection, context (input evaluation) and options (runtime
choice resolution). Messages omit provider error bodies and original transport exception details.
Numerical validation
The adapter checks answer IDs, types, probability keys, finite values, range, sum, scale legend and consistency of the selected answer. It does not renormalize distributions, retry, change models or substitute a default assessment. Returned numbers remain unchanged. A rejected probability sum reports the observed sum, accepted tolerance and option count, without including the response body.
System One probability sums normally use tolerance 1e-6. One empirical compatibility exception
applies only to Choice answers reporting model jev-1.13.0: when every value is within 1e-6 of a
two-decimal value, the sum may differ from one by at most 0.01 + 1e-6. Repeated live responses
with 42 options summed to 0.9900000000000001, and TypeSafe's
OpenAPI schema describes sums as approximately one.
This is a bounded compatibility policy, not a vendor tolerance guarantee or proof of a rounding
cause. Sums of 0.98 or 1.02 still fail. Other models and Score answers retain strict sum validation; keys, ranges and the selected winner are always checked.
The captured Jev 1.13.0 scores and probabilities are consistent with independent rounding to two decimals. Score consistency therefore allows
0.005 * (1 + sum(zero-based level indices)) + 1e-6, about 0.02 for three levels.
Runtime limits and metrics
Defaults per component invoker are 4 active requests, 16 queued requests, an 8-second total timeout
including queue wait, and a 1 MiB serialized request limit. Limits apply to each Flink subtask or
request-response component instance, not globally across a deployment. Size limits apply to the
UTF-8 request including questions and context. Filter irrelevant events before calling the model,
and tune the timeout and concurrency on target hardware. The Flink async operator timeout (timeout in the
model configuration, 10 seconds by default) must exceed the component's total timeout, which is why the default
is 8 seconds: otherwise the Flink task fails before the component can handle its own timeout, which may restart the job.
An exhausted queue or timeout follows the selected Result mode. Transport failures are detached from their original exception classes so a refused connection does not trigger the default Flink transient-exception restart policy. HTTP resources open with the invoker and close when it closes. There is no execution or side-effect deduplication; upstream replays may call the provider again.
Metrics follow LLM Chat: llm.calls and llm.latency count successful evaluations;
llm.errors{error} counts failures; llm.tokens{tokenType=input} records usage.input_tokens when
provided, including an answer rejected by validation. Tags are nodeId, component=decision-model,
integration, provider=system-one and the configured model ID.
The response model identifier is available in the graph for audit rather than creating another metric
series. Detailed-result errors still count as failures in these metrics.
Examples and evaluation
Repository examples under examples/decision-model cover FMS analyst triage, a SIM outage runbook
suggestion, support routing and catalog classification. They use synthetic responses for deterministic
integration checks. They do not establish Jev accuracy, prompt-injection resistance, cost or
latency. Compare a rule-only baseline and actual models in a separate evaluation before a pilot.
Choice supports candidate lists resolved per event with fixed fallbacks. The resolved-options preview
shows historical evidence from a selected run, collected before HTTP; editing the configuration
does not recompute that evidence. The structure preview makes no model call.
Editing descriptions and context
Choice descriptions accept either non-blank text or a JSON object with non-blank what,
optional text not_for and an optional list of strings examples. The compact row shows
what; expand its details to edit the optional fields. Collapsing details preserves their values. Use the same field names across options
so they can be compared. For example:
{
"id": "authorized",
"description": {
"what": "Sensor activity explained by authorized work",
"not_for": "An order exists but its scope does not explain the activity",
"examples": ["Battery removal during a confirmed battery replacement"]
}
}
When any option has structured details, the adapter sends every option in that question as an
object with the same keys. Missing not_for and examples become empty values; missing custom
keys become null. Additional fields survive editing and appear in an extras fold.
Option IDs and descriptions are sent to the service. Whether Jev exposes option IDs to the model
is unknown. IDs are not private metadata.
Question IDs are used for output mapping. See TypeSafe Choice.
The optional schema-level treatContextAsData flag appends a reminder to each question to
evaluate the state as data and not follow instructions in it. The toggle defaults to off for new
configurations. Existing schemas retain their saved setting and instructions.
It is an instruction reminder, not guaranteed prompt-injection protection. Do not repeat the
same clause in every question when using the flag.
Only instructions have a character counter, shown on focus or above 200 characters. It measures
characters, not tokens or price. Output preview shows the
result structure and types derived from questions; it does not call the model or invent values.
The wand icon inside Context opens the data mapper directly. Use the arrow beside a variable
to select its whole value, including records such as #context, or compose a record from its fields.
The mapper preserves an existing whole-value expression when reopened.
Output preview starts collapsed and can be expanded without calling the model.
The form has Model, Questions and Output sections. The context-as-data toggle is beside Context; its explanation is available from the information button. New options start as a draft with focus on ID: an entirely empty draft is discarded on leaving the row, while a partial option is kept and validated. Existing saved options are never removed automatically. Option errors highlight the relevant field with a short message; incomplete options have one count on the question. JSON and API validation retain the full question and option locations.
The Edit as JSON dialog uses the System One questions map (choice, score, noul with
criteria). Its read-only preview shows outgoing questions after applying the context reminder
and description normalization. Scenario storage and API authoring still use the version 1 schema
shown above; do not replace the stored Questions expression with the native request map.
Integration capabilities
Configure question, option and level limits on the integration. Changing integration revalidates existing questions without deleting them. A blank optional token budget means unknown. Configured token limits are displayed as service limits; the form does not equate characters with tokens or predict the size of per-event context. Request rejection follows the selected Result mode.
The editor receives fixed System One semantics: independent questions, unknown option-ID visibility
and confidence formula (pMax - 1/n) / (1 - 1/n). These are not integration settings. Defaults are a 64,000-token
context window and a 32,000-token state/question budget. Verify configured numeric limits against
the selected service. The component does not estimate tokens or silently trim candidates.
Configured versioned Jev models are preferred for new nodes. Moving aliases are marked (moves);
existing selections remain unchanged. The service-reported identifier is available as #assessment.model in Assessment mode.
The structure preview documents permitted Choice IDs as a union; the expression checker currently
exposes Choice as String. Score retains its probabilities and levels in the output.
The Context type appears below the expression as a collapsible tree. Only the root is expanded
initially; nested records and list element types expand independently. The tree is limited to
260 px with scrolling. Clicking a field copies a relative path in backticks for question
instructions, such as site.siteId; readings[].sensor denotes fields of list elements, not a
specific runtime index. This is a type view, not a sample of event values.