23 skills · 76 min
Skills
Skill 15 of 23
INVOKE THIS SKILL when routing a LangGraph agent with a decision model (TypeSafe Jev, SemIf) instead of an LLM, or when auditing an existing agent for LLM calls that only produce…
4 minutes · 839 words · 8 sections
Install
npx skills add langchain-ai/langchain-skills --skill langgraph-decision-modelsnpx skills add langchain-ai/langchain-skills/plugin marketplace add langchain-ai/langchain-skillsThe first command installs just this skill, by the name in its SKILL.md; the second installs the whole repository.
Noul(instructions=...) — binary question, returns a probability of yesChoice(instructions=..., criteria={...}) — picks one label, returns the full distribution plus confidenceScore(instructions=..., criteria=[...]) — grades against an ordered rubric, returns an expected value plus confidenceTypeSafeClassifier is a LangChain Runnable[ClassifierRequest, ClassifierResponse], so it drops into a node like any other runnable. Up to 32 questions share one request and are answered independently — your code combines them.
Reach for one when a node generates text only so you can parse a decision out of it: routing, triage, filtering, guardrails, or per-item classification over a batch.
Do not reach for one when the node’s output is the product (summaries, drafts, code) or when the judgment needs multi-step reasoning. A decision model classifies; it does not think.
langchain-typesafe is alpha (0.0.1a3) and TypeSafeClassifier is marked @beta — pin it and expect churn.
uv add langchain-typesafeThree ways to reach a model. The classifier POSTs to {base_url}/v1/systemone with Authorization: Bearer {api_key}, so switching providers is constructor arguments only:
classifier = TypeSafeClassifier(model=”jev-latest”)
classifier = TypeSafeClassifier( model=”semif-qwen3.5-4b”, api_key=os.environ[“LANGSMITH_API_KEY”], base_url=”https://gateway.smith.langchain.com (opens in a new tab)“, )
typesafe/ prefix routes to atypesafe/* id returns 424 Failed Dependency – before the model name isclassifier = TypeSafeClassifier( model=”typesafe/jev-1.13.0”, api_key=os.environ[“LANGSMITH_API_KEY”], base_url=”https://gateway.smith.langchain.com (opens in a new tab)“, )
</python>
</ex-wiring>
---
## The conversion pattern
Ask every question about a page/item in **one** request, put the typed response in state, and let a plain function route on it. The router is ordinary Python — testable without touching a network.
<ex-classify-and-route>
<python>
```python
from typing import TypedDict
from langchain_typesafe import ClassifierResponse, Noul, Score, TypeSafeClassifier
from langgraph.graph import StateGraph, START, END
QUESTIONS = {
"relevant": Score(
instructions="How relevant is this ticket to a billing problem?",
criteria=["Unrelated.", "Possibly related.", "Directly about billing."],
Reading answers: response.nouls[id].noul, response.choices[id].choice, response.scores[id].score. Each view is keyed by your question id; response.answers holds them all.
These cause silent misrouting, not exceptions.
1. Score.score is an expected value, not a level. It is a probability-weighted average over the rubric and is routinely fractional. score == 0 almost never fires — a “not responsive” item lands at 0.07, not 0. Always compare against a band.
if a.scores["relevant"].score < 0.5: # correct
if a.scores["relevant"].score == 0: # WRONG -- nearly never true2. Confidence measures distribution shape, not correctness. On a Score, confidence reports how concentrated the rubric distribution is. An item sitting cleanly between two levels scores low confidence even when the model is entirely clear about it. A blanket confidence < X -> escalate rule therefore escalates items the model already decided. Gate on confidence only inside the ambiguous middle:
if score < NOT_RELEVANT: # decisive -- trust it
return "close"
if score < RELEVANT or confidence < MIN_CONF: # ambiguous -- escalate
return "human_review"
return "handle"3. Thresholds do not transfer between models. Calibration is part of the model. The same policy over the same items routes differently on Jev vs SemIf vs an LLM adapter. Re-tune thresholds whenever you change models, and pin the model id.
A loose question produces confident wrong answers, and no threshold fixes it. Use criteria to say what each outcome means, including what should not count.
In a measured case, “Is this a confidential communication with a lawyer?” scored a routine finance memo at 0.798. Rewriting it to name the actual test — written by or to a lawyer, with an explicit carve-out for finance and accounting content — moved the same page to 0.005 while a genuinely privileged page held at 0.991.
Noul(
instructions=(
"Was this written by or to a lawyer, or does it convey a lawyer's legal "
"advice? Answer no for ordinary business or accounting discussion, even "
"when the subject is litigation-sensitive."
),
criteria=NoulCriteria(
true="A named attorney is author or recipient, or it relays legal advice.",
false="Business or accounting content with no attorney involved.",
),
)Before blaming the model, rewrite the question and re-measure.
To find where a decision model fits, look for these in the codebase — see references/conversion-playbook.md for the full walkthrough.
| Signal | What to look for |
|---|---|
| Generate-then-parse | An LLM call whose output is immediately regex’d, json.loads‘d, or string-matched into a branch |
| Prompted classifiers | Prompts containing “respond with one of”, “answer yes or no”, “rate from 1 to 5” |
| Sampling for cost | Comments or configs that check only the first N items because checking all is too expensive |
| Brittle rules | Keyword lists or regexes standing in for semantic judgment |
| Re-reading context | The same document re-sent to a model for each separate question |
The last two matter most: cheap semantic judgments change what you can build, not just the bill. If evaluating every item became affordable, what would you stop sampling?
Measured on a 24-item batch, identical LangGraph graph and routing policy, only the classifier swapped:
| per item | tokens (6 items) | notes | |
|---|---|---|---|
| Jev 1.13.0 | 0.27s | 3,648 in / 318 out | reports usage |
| SemIf 4B | 0.49s | not reported | hosted on the Gateway |
| Claude Sonnet 5 | 2.87s | 7,930 in / 864 out | via structured output |
Routing agreed on 4–5 of 6 items across engines; disagreements clustered on genuinely borderline items. Treat these as shape, not benchmarks — measure on your own workload.
If you compare against an LLM baseline, use method="json_schema" so the comparison is fair. LangChain’s with_structured_output defaults to method="function_calling", which injects a tool schema into every request — 556/35 tokens versus 228/12 for the native output_config.format path on the same one-field probe.
Classification is per-item and independent, so fan out with Send and let each item route on its own.
def fan_out(state): return [Send(“classify_item”, {“text”: t}) for t in state[“items”]]
builder.add_conditional_edges(START, fan_out, [“classify_item”])
</python>
</ex-fan-out>
Fan-out hides latency, so it flatters slow classifiers most: in the run above, Sonnet gained 8x from concurrency and Jev only 1.4x — yet Jev still finished first. Compare throughput, not the speedup multiple.
---
## Related skills
- **langgraph-fundamentals** — StateGraph, `Send`, `Command`, conditional edges
- **langgraph-human-in-the-loop** — `interrupt()` for the escalation branch above
- **langchain-middleware** — structured output when you need an LLM, not a classifierINVOKE THIS SKILL when routing a LangGraph agent with a decision model (TypeSafe Jev, SemIf) instead of an LLM, or when auditing an existing agent for LLM calls that only produce a routing decision. Covers langchain-typesafe Noul/Choice/Score, reading answers correctly, threshold design, and LangSmith Gateway wiring.
The verbatim description from this skill’s front matter — the string an agent matches on to decide whether to load it.
main, last pushed 25 September 2026.SKILL.md, not by matching a directory convention. One layout observed: config/skills/*/SKILL.md.h1 and no skipped levels:.claude-plugin/marketplace.json by LangChain, declaring 1 plugin. It is read for editorial metadata only — never as the skill index, which is always the repository tree..md1 file · 6 KB
Everything this skill ships beside its prose. All of it is set here, as a subchapter of skill 15.
Documentation the agent loads on demand, rather than up front.