Jev in LangGraph and LangChain
Use Jev as a fast router node inside a LangGraph graph, and as middleware that picks a model or checks tool calls in a LangChain agent. Full, runnable examples.
- Install
langchain-typesafeand setTYPESAFE_API_KEY. TypeSafeClassifieris a normal LangChain Runnable. Call.invoke()withstateandquestions.- In LangGraph, put Jev in a node and let a plain Python function turn its answer into a conditional edge.
- In a LangChain agent, two ready-made middlewares:
ModelRouterMiddlewarepicks the model,AutoModeMiddlewarechecks risky tool calls. - The LLM still does the writing. Jev only makes the decisions in between.
Why Jev fits a graph so well
A LangGraph graph is a set of nodes with edges between them. The hard part is usually the edge: deciding where to go next. Many teams make that decision with an extra LLM call and then parse the text.
Jev is built for exactly that step. It answers in a fraction of a second, the answer is already one of your node names, and the confidence tells you when to send the case to a human instead.
route() turns it into an edge. The LLM only runs in the node that actually has to write something.Install
pip install langchain-typesafe langgraph
export TYPESAFE_API_KEY=your_key_hereThe classifier on its own
from langchain_typesafe import Choice, Noul, Score, TypeSafeClassifier
classifier = TypeSafeClassifier()
response = classifier.invoke({
"state": "The deploy failed twice and customers are seeing 500s. Can someone look now?",
"questions": {
"urgent": Noul(instructions="Does this need attention right now?"),
"team": Choice(
instructions="Which team?",
criteria={"infra": "Servers, deploys, outages", "billing": "Charges and refunds"},
),
"severity": Score(
instructions="How severe?",
criteria=["Cosmetic.", "Degraded.", "Outage."],
),
},
})
response.nouls["urgent"].noul # 0.98
response.choices["team"].choice # "infra"
response.scores["severity"].score # 1.9state can be a string, a dict, or a list of LangChain messages, so you can pass an agent's message history straight in. Because it is a Runnable, .ainvoke() and .batch() work too, and calls show up in LangSmith traces with token usage.
Jev as a router node in LangGraph
A support graph: Jev triages the ticket, then a conditional edge sends it to the right specialist, or to a human when Jev is unsure.
from typing import TypedDict
from langchain_typesafe import Choice, Noul, TypeSafeClassifier
from langgraph.graph import END, START, StateGraph
classifier = TypeSafeClassifier()
class TicketState(TypedDict, total=False):
message: str
team: str
confidence: float
urgent: float
reply: str
def triage(state: TicketState) -> dict:
result = classifier.invoke({
"state": state["message"],
"questions": {
"team": Choice(
instructions="Which team should handle this ticket?",
criteria={
"billing": "Charges, invoices, and refunds",
"technical": "Bugs, outages, and integration failures",
},
),
"urgent": Noul(instructions="Is the customer losing money or blocked right now?"),
},
})
team = result.choices["team"]
return {
"team": team.choice,
"confidence": team.confidence,
"urgent": result.nouls["urgent"].noul,
}
def route(state: TicketState) -> str:
# plain code owns the policy
if state["confidence"] < 0.5:
return "human_review"
return state["team"]
def billing(state: TicketState) -> dict:
... # your billing agent, e.g. an LLM with billing tools
def technical(state: TicketState) -> dict:
... # your technical agent
def human_review(state: TicketState) -> dict:
... # open a ticket for a person
builder = StateGraph(TicketState)
builder.add_node("triage", triage)
builder.add_node("billing", billing)
builder.add_node("technical", technical)
builder.add_node("human_review", human_review)
builder.add_edge(START, "triage")
builder.add_conditional_edges("triage", route, ["billing", "technical", "human_review"])
for node in ["billing", "technical", "human_review"]:
builder.add_edge(node, END)
graph = builder.compile()
graph.invoke({"message": "I was charged twice this month."})A few things worth noticing:
- The Choice options are node names. No mapping table, no string cleanup.
- The threshold lives in
route(). You can read it, test it, and change it without touching a prompt. urgentis stored in state. Any later node can use it, for example to page someone on call.
Run the graph with a clear billing message and a vague one like "it is broken again". The first should land in billing. The second should have lower confidence and may go to human_review.
Other places to add a Jev node
The same pattern works anywhere a graph has to decide something:
- Loop exit. After each agent step, a Noul: "Has the user's request been fully answered?" Stop when it is high.
- Retrieval filter. Score each retrieved passage for relevance, drop the low ones before the LLM sees them.
- Tool check. Before a tool node runs, a Noul: "Could this action delete or change data?" Route risky calls to an approval node.
Middleware for LangChain agents
If you use LangChain's create_agent instead of building a graph by hand, there are two ready-made middlewares. They are experimental, so install the extra:
pip install "langchain-typesafe[experimental]"Pick the cheapest model that can do the job
ModelRouterMiddleware asks Jev which model fits each request, then calls that one.
from langchain.agents import create_agent
from langchain_typesafe.experimental.middleware import ModelChoice, ModelRouterMiddleware
router = ModelRouterMiddleware(
choices={
"fast": ModelChoice(
model="anthropic:claude-haiku-4-5",
criteria="Direct lookups, extraction, and localized changes.",
),
"powerful": ModelChoice(
model="anthropic:claude-opus-5-5",
criteria="Architecture and high-stakes decisions.",
),
},
instructions="Choose the least costly model that can complete the task.",
)
agent = create_agent("anthropic:claude-haiku-4-5", middleware=[router])Check tool calls before they run
AutoModeMiddleware scores each call to the listed tools for risk before it executes. This is the same idea as the "auto mode" in coding agents like Claude Code, Codex, and Cursor.
from langchain.agents import create_agent
from langchain_typesafe.experimental.middleware import AutoModeMiddleware
guardrail = AutoModeMiddleware(tools=["bash"])
agent = create_agent("anthropic:claude-haiku-4-5", middleware=[guardrail])Both middlewares live under langchain_typesafe.experimental. Pin the package version, and expect names or options to change between releases.
JavaScript
Everything on this page is the Python package. On TypeScript, the most direct route today is the Vercel AI SDK or the TypeSafe JS SDK from Your first call. A Jev call inside a LangGraph.js node looks the same as the Python version above.
Where this page's facts come from▾
LangChain's TypeSafe integration docs and the LangChain blog post Building a harness with Jev. The support graph is our own example, built on the standard LangGraph StateGraph API. Model names in the middleware examples are placeholders, swap in whichever models you use.