← All ivy-nodes
IVYXSTUDIO · IVY NODE
J

Jev · Choice

ivy.node.jev-choice · v0.1.0

ivyx✓

Asks TypeSafe Jev which of the given options fits a text or JSON state and returns the chosen option with its confidence and the probability of every option. Use it to pick a category, team, label, route or risk level. Options are a list of names or a map of name to description; add an "other" option when none may fit. Several hundred milliseconds, no text generated, and the answer is always one of the options. When the state is a JSON object its fields come back beside the answer, so a for-each over rows keeps each row.

#jev#typesafe#classify#choice#route#decision

Inputs

FieldTypeDescription
staterequiredNot declaredThe text, or a JSON object or array, that Jev judges.
instructionsrequiredstringThe question, in words; the model never sees anything else about it.
optionsrequiredNot declaredThe options: a list of names, or a map of name to a description of when it applies.
jev_api_keyrequiredstringThe TypeSafe API key; reference the stored secret jev-api-key, never the key itself.
base_urlstringThe System One server.
modelstringThe Jev model; pin a version such as jev-1.13.0 before tuning a threshold.

Outputs

FieldTypeDescription
choicerequiredstringThe option Jev chose.
isobjectFor each option, as lowercase words joined by _, whether it is the one chosen; a branch reads is.<option>.
confidencerequirednumber0–1, how peaked the probabilities are.
probabilitiesrequiredobjectEach option's probability; they add up to 1.
modelstringThe model version that answered.

Source

python

inp = __ivy_ctx__["nodes"][__ivy_node_id__]["input"]

import json
import re
import requests

def slug(text):
    """The key a branch reads: "Needs action today" → "needs_action_today"."""
    return re.sub(r"[^a-z0-9]+", "_", str(text).lower()).strip("_")

def ask(question):
    state = inp.get("state")
    if state is None or (isinstance(state, str) and not state.strip()) or state == {} or state == []:
        raise ValueError("state is empty; give Jev the text or JSON to judge.")
    instructions = str(inp.get("instructions") or "").strip()
    if not instructions:
        raise ValueError("instructions is empty; say what Jev should decide.")
    key = inp.get("jev_api_key")
    if not key:
        raise ValueError("jev_api_key is missing; reference the stored secret jev-api-key.")
    question["instructions"] = instructions
    base = str(inp.get("base_url") or "https://api.typesafe.ai").rstrip("/")
    response = requests.post(
        base + "/v1/systemone",
        headers={"Authorization": "Bearer " + str(key), "Content-Type": "application/json"},
        json={"state": state, "model": inp.get("model") or "jev-latest", "questions": {"q": question}},
        timeout=60,
    )
    if response.status_code in (401, 403):
        raise PermissionError(f"Jev refused the API key (HTTP {response.status_code}).")
    if response.status_code != 200:
        raise RuntimeError(f"Jev answered HTTP {response.status_code}: {response.text[:300]}")
    body = response.json()
    answer = (body.get("answers") or {}).get("q")
    if not isinstance(answer, dict):
        raise RuntimeError("Jev's answer has no entry for the question.")
    return answer, body.get("model")

options = inp.get("options")
if isinstance(options, list):
    criteria = {str(o): None for o in options if str(o).strip()}
elif isinstance(options, dict):
    criteria = {str(k): v for k, v in options.items()}
else:
    criteria = {}
if len(criteria) < 2:
    raise ValueError("options needs at least two choices.")
answer, model = ask({"type": "choice", "criteria": criteria})
choice = answer.get("choice")
if choice not in criteria:
    raise RuntimeError(f"Jev chose {choice!r}, which is not one of the options.")

out = __ivy_ctx__["nodes"][__ivy_node_id__]["output"]
out["choice"] = choice
out["is"] = {slug(o): o == choice for o in criteria}
out["confidence"] = answer.get("confidence")
out["probabilities"] = answer.get("probabilities") or {}
out["model"] = model
# An object state's fields come back beside the answer, so a for-each over
# rows keeps each row (its date, its question) next to its score.
if isinstance(inp.get("state"), dict):
    for _k, _v in inp["state"].items():
        out.setdefault(_k, _v)

Tests

Requires: python:3.9, requests

  • billing

    A charge complaint is routed to billing.

  • described

    Options given as a map with descriptions, the last one is the fallback.

  • bad-key

    A rejected key says so.

  • one-option

    A single option is refused before any call.