Jueding
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 question — instructions 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-sdkowns 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)
| File | Size | Uploaded | |
|---|---|---|---|
| jueding-0.1.0.tar.gz | 61.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|