langgraph-jev
langgraph-jev is an open-source LangGraph/LangChain integration for
Jev, TypeSafe's typed probabilistic
decision API. JevNode wraps it as a decision node you drop into an agent
graph -- it's not an LLM and not an LLM wrapper.
Jev evaluates typed questions against state and returns typed answers with probabilities and confidence attached. There's no text generation and nothing to parse -- your code branches on, sorts by, and routes with the result directly.
For example, a work_ticket graph might run an "understand" step over a
customer request, hand the structured output to a JevNode for typed
decisions (work type, priority, whether it needs an engineer), and then route
to a coding, support, or docs worker based on those decisions. See
examples/work_ticket.py for the full version of this.
Installation
pip install langgraph-jev
Set your API key (never commit it):
export TYPESAFE_API_KEY=...
The examples in examples/ also support a project-local .env file (already
covered by .gitignore) via optional python-dotenv support:
pip install "langgraph-jev[examples]"
echo "TYPESAFE_API_KEY=..." > .env
python examples/basic.py
Quick start
from langgraph_jev import JevNode, choice
jev = JevNode(
questions={
"route": choice(["coding", "support", "research"]),
}
)
Running examples/basic.py against a real state produces typed decisions
with confidence scores, not free text:
Resource cleanup
JevClient (and anything built on it, like JevNode/JevRunnable) opens
underlying HTTP clients on construction. Close it when you're done, or use
it as a context manager:
from langgraph_jev import JevClient, choice
with JevClient() as client:
result = client.decide(state={"title": "..."}, questions={"route": choice(["a", "b"])})
# or, in async code:
async with JevClient() as client:
result = await client.adecide(state={"title": "..."}, questions={"route": choice(["a", "b"])})
If you construct a long-lived JevNode/JevRunnable (e.g. one per graph,
reused across requests), there's no need to close it per-call -- call
client.close()/await client.aclose() once when your application shuts
down.
LangGraph
JevNode is directly callable by LangGraph -- it receives graph state and
returns a state update (not a replacement of the whole state):
graph.add_node("jev", jev)
By default the entire graph state is passed to Jev as state. Use
state_key/output_key to scope input and output:
jev = JevNode(
state_key="structured_request",
output_key="decision",
questions={...},
)
Confidence routing
Choice and Score answers include a confidence score derived from the
response's probability distribution (see
Confidence). Configure thresholds and
a policy for what happens below them:
jev = JevNode(
questions={
"work_type": choice(["bug", "support", "configuration", "documentation"]),
"priority": choice(["critical", "high", "normal", "low"]),
},
thresholds={"work_type": 0.85, "priority": 0.90},
low_confidence="human_review", # "allow" (default) | "human_review" | "error"
)
def route_after_jev(state):
if state["decision"].requires_review:
return "human_review"
return "continue"
Low-confidence decisions are never silently discarded -- the full decision and its confidence are always available on the result.
Routing helper
from langgraph_jev import route_by_decision
def route_work(state):
return route_by_decision(
state["decision"],
field="work_type",
routes={
"bug": "coding_agent",
"support": "support_agent",
"documentation": "documentation_agent",
},
fallback="human_review",
)
Jev makes the probabilistic decision; your application code decides what to do with it. Routing stays deterministic.
WorkTicket example
Jev doesn't generate the WorkTicket itself -- it provides the decisions,
and your application code builds the typed contract from them:
decision = state["decision"]
ticket = WorkTicket(
id="WT-1234",
type=decision["work_type"].value,
priority=decision["priority"].value,
needs_engineer=decision["needs_engineer"].value,
title=request["title"],
description=request["description"],
context={"customerTier": request.get("customerTier")},
acceptance_criteria=["Identify root cause", "Add regression coverage if appropriate"],
)
See examples/work_ticket.py for the full example: an understand step feeds
Jev, Jev's decisions build a WorkTicket, and the ticket routes to a coding,
support, or docs worker.
LangChain Runnable
from langgraph_jev import JevRunnable, choice
jev = JevRunnable(questions={"route": choice(["coding", "support", "research"])})
result = jev.invoke({"title": "Customer cannot login"})
Supports both invoke() and ainvoke().
Examples
| File | Demonstrates |
|---|---|
examples/basic.py |
Minimal JevClient usage |
examples/langgraph_basic.py |
A minimal LangGraph graph with a JevNode |
examples/confidence_routing.py |
Thresholds and low_confidence="human_review" |
examples/work_ticket.py |
Building a WorkTicket from Jev's decisions |
examples/multi_agent.py |
Full graph: understand -> Jev -> ticket -> routed workers |
Public API
from langgraph_jev import (
JevClient,
JevNode,
JevRunnable,
choice,
boolean,
score,
route_by_decision,
)
That's the full public API. Errors (JevError and its subclasses
JevConfigurationError, JevAPIError, JevTimeoutError,
JevValidationError) and the result types (JevDecision, JevResult) are
also exported for type annotations and except clauses.
Development
pip install -e ".[dev]"
pytest
ruff check .
ruff format .
mypy src
Tests use a fake/mock Jev transport and never require a real
TYPESAFE_API_KEY. CI runs ruff check, ruff format --check, mypy, and
pytest on Python 3.11-3.13.
License
MIT
Metadata
Release files for langgraph-jev 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 | |
|---|---|---|---|
| langgraph_jev-0.1.0.tar.gz | 149.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| langgraph_jev-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 164.6 kB
Release files / langgraph_jev-0.1.0.tar.gz
| Download URL | langgraph_jev-0.1.0.tar.gz |
|---|---|
| Size | 149.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2c56fd2083404aa33f496cfb02f732cc6d0da12899f2ce15d93928146cac02d7
|
|
BLAKE2b-256 checksum How to use checksums |
e58f93778f93b99523cbb01e956e6c8f2a4393a44ce6bef9bdb67a4250e00dcb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|
Release files / langgraph_jev-0.1.0-py3-none-any.whl
| Download URL | langgraph_jev-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9afb1192a0f389893173700c142c131181f153bc8e151c2c5ad7796df0100589
|
|
BLAKE2b-256 checksum How to use checksums |
3c52bb3d399b5d6474d877a6cccce697fd65d858b2b3ea5a4bed351cbfd790e5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|