Skip to main content

Jueding

CI coverage python

Jueding (决定, pronounced jué dìng) means to decide or a decision in Chinese.

Jueding is a small, typed Python wrapper for TypeSafe's Jev model. It uses the official typesafe-sdk package. Jueding turns the probabilistic judgments of Jev into ordinary function calls. The boundary is simple:

Unstructured state goes in. A typed value, a confidence, and a complete probability distribution come out. Your Python code decides what happens next.

Jev answers three kinds of question. Jueding gives each kind a typed Python primitive:

  • a choice among labels
  • a yes/no judgment
  • a score on an ordered rubric

Jueding is not an agent framework. It does not take control of your application.

Install

uv add "jueding @ git+https://github.com/xiutian-cui/jueding"

This installs typesafe-sdk, which Jueding requires. Jueding adds no other runtime dependency.

Quick start

from enum import StrEnum

from typesafe_sdk import TypeSafeClient

from jueding import Decision
from jueding.typesafe import TypeSafeBackend


class Intent(StrEnum):
    SEARCH = "search"
    ANALYZE = "analyze"
    CHAT = "chat"


client = TypeSafeClient()          # reads TYPESAFE_API_KEY
backend = TypeSafeBackend(client=client)

decide_intent = Decision(
    instructions="Determine the user's primary intent.",
    choices=Intent,
    backend=backend,
    criteria={
        Intent.SEARCH: "Retrieve existing information.",
        Intent.ANALYZE: "Reason about or compare information.",
        Intent.CHAT: "Primarily conversational.",
    },
    threshold=0.8,
)

intent = decide_intent(
    user_input="Compare these two implementation approaches.",
    context={"surface": "developer_tool"},
)

if intent == Intent.ANALYZE:
    run_analysis()

The result compares directly against the enum. It also carries every value that the model reported:

intent.value          # Intent.ANALYZE
intent.confidence     # 0.91
intent.probabilities  # {Intent.SEARCH: 0.06, Intent.ANALYZE: 0.91, Intent.CHAT: 0.03}
intent.ranked         # ((Intent.ANALYZE, 0.91), (Intent.SEARCH, 0.06), (Intent.CHAT, 0.03))
intent.margin         # 0.85
intent.meets_threshold
intent.model, intent.usage, intent.request_id, intent.latency_seconds

What goes where

A call has two inputs. The difference between them is important:

What it holds Lifetime
The questioninstructions and criteria What to decide, and what each outcome means, with definitions and examples Fixed when you build the decision
The state — the call arguments The facts of this particular case Per call

Criteria accept text, mappings, or sequences. Put stable domain knowledge here:

criteria={
    "returns": {"what": "Exchanges, refunds, wrong or damaged items",
                "examples": ["wrong size", "arrived broken"]},
    "billing": {"what": "Charges, invoices, payment problems",
                "not_for": ["refund requests, which are returns"]},
}

State is an ordinary JSON document. The model reads the field names. Therefore the keyword names are part of the question, not plumbing:

decide(message=text, order={"id": "A-104"}, customer_tier="gold")   # keywords become state
decide("a plain string is used as state directly")                  # or one positional value

A positional argument can be a string, a mapping, a sequence, or a dataclass instance. If you mix positional state and keyword state, the call raises an error.

Primitives

Decision[Enum]

Decision selects one member of a string-valued enum. It returns a DecisionResult with the complete distribution, ranked, and margin.

if not intent.meets_threshold and intent.margin < 0.1:
    top_two = [member for member, _ in intent.ranked[:2]]
    ask_user_to_pick(top_two)

A narrow margin is different from diffuse uncertainty. It shows that two specific candidates compete. That is the case to disambiguate.

Condition

A yes/no judgment that does not collapse into a bare boolean.

from jueding import Condition

is_urgent = Condition(
    instructions="Does this request explicitly communicate urgency?",
    backend=backend,
    true="The request includes time pressure or a deadline.",
    false="The request does not communicate time pressure.",
)

result = is_urgent("Please fix this before payroll closes today.")
if result:
    escalate()

result.probabilities   # {True: 0.98, False: 0.02}

Confidence here is max(p, 1 - p). It never falls below 0.5. A threshold under 0.5 is always met.

Score

Score returns an expected score over an ordered rubric. The levels are ranked, so a value between two levels is meaningful. 1.3 means mostly level 1, with some level 2.

from jueding import Score

severity = Score(
    instructions="How severe is the reported issue?",
    rubric=[
        "Cosmetic; no impact to functionality",
        "Broken or degraded feature, but a workaround exists",
        "Blocking issue; no workaround exists",
    ],
    backend=backend,
)

result = severity(bug_report)
if result >= 1.5:
    page_oncall()

result.value          # 1.3 — the probability-weighted mean of the level numbers
result.probabilities  # {0: 0.0, 1: 0.7, 2: 0.3}
result.legend         # {0: "Cosmetic; ...", 1: ..., 2: ...}

Use Score when the outcomes lie on a spectrum. Use Decision when they do not. There is no midpoint between search and chat. There is one between "degraded" and "blocking".

Thresholds

threshold is optional and has no default. Without a threshold, Jueding applies no confidence policy. It reports what the model said.

With a threshold, a result below that threshold refuses to act as a certain value:

intent = decide_intent(user_input=text)      # confidence 0.42, threshold 0.8

intent == Intent.ANALYZE     # raises LowConfidenceError
bool(is_urgent(ticket))      # raises LowConfidenceError
severity(bug) >= 1.5         # raises LowConfidenceError

intent.value                 # Intent.ANALYZE — reading never raises
intent.confidence            # 0.42
intent.meets_threshold       # False

The guard fires only against the type of the answer. A comparison between two results is never guarded. A comparison against an unrelated type is never guarded. Therefore results stay usable in collections. A comparison between two judgments uses their values only, not their confidences.

Jueding has no retry and no fallback. typesafe-sdk owns the retries at the transport layer. Your own code must decide what to do about a low-confidence answer.

Design principles

  • Normal Python: decisions are callable objects. Results work with if, match, dictionaries, and sets.
  • Typed values: an enum decision returns an enum member, not a string to parse.
  • Visible uncertainty: Jueding always keeps the confidence and the full distribution.
  • Explicit policy: no default threshold, no retry, no fallback, no side effect.
  • Thin integration: typesafe-sdk owns HTTP, authentication, response parsing, and transport retries. Jueding adds types. Jueding never wraps the exceptions of the SDK.

Development

uv sync
uv run pdoc jueding jueding.typesafe -o site   # API docs from docstrings

License

MIT — see LICENSE.

Release files for jueding 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jueding 0.1.0
File Size Uploaded
jueding-0.1.0.tar.gz 61.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jueding 0.1.0
File Interpreter ABI Platform
jueding-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 79.7 kB

Release files / jueding-0.1.0.tar.gz

Download URL jueding-0.1.0.tar.gz
Size 61.4 kB
Tags Source
SHA-256 checksum
How to use checksums
b5f38f6bdda70e87f91599ba2f69d718d0d3e2f049fa9f5ab768d824a3e372a1
BLAKE2b-256 checksum
How to use checksums
ea2c3f13d7fd0726e6f85360f3a60adf697c06ed7c2a0b4b72d1d3639bb4bbfd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / jueding-0.1.0-py3-none-any.whl

Download URL jueding-0.1.0-py3-none-any.whl
Size 18.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6ec331e1885f42799bc7d859251fb5068ce2a3ca7a3e1478cc8b15dd0fe03575
BLAKE2b-256 checksum
How to use checksums
d58a07d350db985eba75fdcfd1290ce263dc023c29d5ba8eb53a691c3dec7c8a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page