Articles / LLMs & Agents
Typed Intent Routing in Python with Jev and TypeSafe
Route requests to deterministic handlers, an LLM, or human review with Jev's typed Choice and Noul answers in Python.

An agent that sends every message to the same general-purpose model has no explicit routing policy. A request to cancel a subscription, a request for a factual answer, and an ambiguous complaint all enter the same prompt. That makes it difficult to test which path ran and unsafe to automate high-impact work.
Typed routing splits that job in two. Code owns the available handlers and their permissions. A narrow model judgment selects one of those handlers and exposes uncertainty for policy to handle. Jev, TypeSafe's System One model, is designed for that second part: it returns structured decisions rather than an open-ended response.
This article builds a small Python router. The function below is an integration boundary: once its caller has supplied a verified user ID, redacted text, typesafe-sdk, and TYPESAFE_API_KEY, calling it performs the SDK request.
Start with routes that code can enforce
Do not make a model invent route names. Define a closed set whose consequences are clear:
| Route | What code may do |
|---|---|
help_center | Search read-only documentation |
account_status | Fetch the authenticated user's status |
cancellation_request | Collect intent, then require a separate confirmation flow |
human_review | Create a support queue item without making an account change |
The cancellation route is deliberately not a cancellation operation. Classification can be wrong, and a typed result is an interface guarantee, not evidence that the model understood the user's authority or intent. Verify authentication, display the consequence, and obtain confirmation in ordinary application code.
Ask one decision and one independent clarification question
The current TypeSafe Python SDK quickstart installs as typesafe-sdk and exposes TypeSafeClient, Choice, Noul, and Score. A Choice selects from defined criteria; a Noul returns the probability of yes. The following complete example asks both questions over the same state.
from typesafe_sdk import Choice, Noul, TypeSafeClient
def classify_request(redacted_message: str, verified_user_id: str) -> tuple[str, float, float]:
state = {
"message": redacted_message,
"user": {"id": verified_user_id},
}
questions = {
"route": Choice(
instructions=(
"Choose the next support workflow. Choose human_review when "
"the message is ambiguous or needs a person."
),
criteria={
"help_center": "Question answerable from public help articles.",
"account_status": "Asks for the user's current account state.",
"cancellation_request": "Wants to cancel or close an account.",
"human_review": "Ambiguous, sensitive, or outside these workflows.",
},
),
"needs_clarification": Noul(
instructions=(
"Does this request contain multiple unresolved intents or lack "
"the detail needed to choose a safe workflow?"
)
),
}
with TypeSafeClient() as client:
result = client.system_one(state=state, questions=questions)
route = result.choices["route"]
clarification = result.nouls["needs_clarification"]
return route.choice, route.confidence, clarification.noul
route.choice is the selected label and route.confidence measures how concentrated that choice is among the alternatives. The Noul value is a probability from 0 to 1 that the request needs clarification. They answer different questions, so do not compare them as if both were a universal safety score. The confidence guide makes the same distinction: confidence summarizes certainty in a Choice or Score distribution, while a Noul reports probability of its yes outcome.
Redaction happens before this function. Do not upload a raw message in order to ask whether it contains secrets. The caller must authenticate the session, remove credentials and payment data according to its policy, then pass only the minimum text necessary for routing. The verified_user_id name documents a precondition; the SDK cannot verify it.
Put thresholds and side effects in Python
Thresholds belong to the application because their cost depends on the action. A low-confidence help search can be harmless. A low-confidence cancellation request should never trigger a state change. Here is a policy layer for the output above:
def choose_handler(route: str, confidence: float, clarification_probability: float) -> str:
if clarification_probability >= 0.6:
return "ask_clarifying_question"
if confidence < 0.80:
return "human_review"
if route == "cancellation_request":
return "show_cancellation_confirmation"
return route
The numeric values are starting hypotheses, not model guarantees. Pick them with representative labeled messages and the actual cost of a wrong route. Record the selected route, confidence, policy outcome, and a privacy-safe evaluation label. Do not record raw secrets merely to debug the classifier.
Test the workflow, not just the classifier
Build a small versioned table with pre-redacted request text, expected route, expected policy result, and whether it needs clarification. Include multilingual phrasing, typos, near-misses, and messages with multiple intents. A route can be correct while the resulting handler is broken, so test both layers.
For example, "I want to delete my account, but first send my invoice" should often reach review instead of forcing a single route. The right behavior can be an escalation, not a more confident prediction. Measure route agreement by class, the share escalated, and the rate of unsafe automatic outcomes. Anthropic's agent-evaluation guide similarly recommends testing tasks and graders against the behavior the system must produce, rather than trusting a single aggregate score.
Where this fits in an agent
Use routing before an agent decides which bounded tool set it may see. A read-only retrieval question can proceed to RAG. An account question can receive a narrowly scoped account lookup. An ambiguous request receives a person or a clarification, not a larger prompt with more tools. The technique complements the tool loop described in AI agents with LangGraph for data analysis: LangGraph can coordinate a workflow, while typed routing makes the initial branch observable and testable.
Jev does not replace authorization, input validation, audit logs, or a confirmation screen. It is useful when ordinary code cannot reliably interpret natural language but code can reliably enforce what each interpretation is allowed to do.
About the author
Rodrigo Arenas is a software architect and machine learning engineer in Medellín, Colombia. He builds products, platforms, and open-source software for AI, Data Engineering, and Machine Learning: creator of Ciaren, sklearn-genetic-opt, and PyWorkforce.
Are you building an AI or data platform?
I design and build critical systems end to end: architecture, data, models, and product. If that is the scale you are working at, let us talk.