Catalog
langchain-ai/langgraph-decision-models

langchain-ai

langgraph-decision-models

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 a routing decision. Covers langchain-typesafe Noul/Choice/Score, reading answers correctly, threshold design, and LangSmith Gateway wiring.

v1.0LATEST
NewUpdated Oct 12, 2026
  • Noul(instructions=...) — binary question, returns a probability of yes
  • Choice(instructions=..., criteria={...}) — picks one label, returns the full distribution plus confidence
  • Score(instructions=..., criteria=[...]) — grades against an ordered rubric, returns an expected value plus confidence

TypeSafeClassifier 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.


Install and wire

langchain-typesafe is alpha (0.0.1a3) and TypeSafeClassifier is marked @beta — pin it and expect churn.

uv add langchain-typesafe

Three 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:

1. TypeSafe directly (Jev). Reads TYPESAFE_API_KEY when api_key is omitted.

classifier = TypeSafeClassifier(model="jev-latest")

2. SemIf, hosted on the LangSmith Gateway. Note: LangSmith key, not a TypeSafe key.

classifier = TypeSafeClassifier( model="semif-qwen3.5-4b", api_key=os.environ["LANGSMITH_API_KEY"], base_url="https://gateway.smith.langchain.com", )

3. Jev through the Gateway (BYOK). The typesafe/ prefix routes to a

TYPESAFE_API_KEY stored in LangSmith workspace secrets. Without that secret

every typesafe/* id returns 424 Failed Dependency -- before the model name is

even validated, so a 424 does not confirm the id is real.

classifier = TypeSafeClassifier( model="typesafe/jev-1.13.0", api_key=os.environ["LANGSMITH_API_KEY"], base_url="https://gateway.smith.langchain.com", )

</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."],
    ),
    "angry": Noul(instructions="Is the customer expressing anger?"),
}

class State(TypedDict):
    text: str
    answers: ClassifierResponse
    route: str

classifier = TypeSafeClassifier(model="jev-latest")

def classify(state: State) -> dict:
    # One request, every question. They are answered independently.
    return {"answers": classifier.invoke(
        {"state": state["text"], "questions": QUESTIONS}
    )}

def route(state: State) -> str:
    a = state["answers"]
    if a.nouls["angry"].noul > 0.7:
        return "escalate"
    if a.scores["relevant"].score < 0.5:
        return "close"
    return "handle"

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.


Three traps when reading answers

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 true

2. 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.


Question wording dominates accuracy

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.


Auditing an existing agent

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?


Expectations

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.


Batching with Send

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 classifier
Files2
2 files · 7.1 KB

Select a file to preview

Overall Score

87/100

Grade

A

Excellent

Grades are signals, not a certification. Always review a skill yourself before use.

Safety

85

Quality

89

Clarity

87

Completeness

84

Summary

This skill guides developers through integrating TypeSafe decision models (Jev, SemIf) into LangGraph agents as a replacement for LLM-based routing. It covers model installation, wiring three deployment architectures, reading classifier responses correctly, threshold design, and how to audit existing agents for LLM calls that only produce routing decisions. The skill includes concrete examples, performance expectations, and a comprehensive conversion playbook for migrating from generate-then-parse patterns.

Detected Capabilities

code generation and examplesapi integration (langchain-typesafe, LangSmith Gateway)network requests (TypeSafeClassifier POSTs to gateway.smith.langchain.com)structured data parsing (ClassifierResponse)python module installation (uv add)

Trigger Keywords

Phrases that agents use to match this skill to user intent.

langgraph routing decisionreplace llm with classifiertypesafe decision modelsemantic judgment thresholdgenerate-then-parse audit

Risk Signals

INFO

Network API integration via gateway.smith.langchain.com and TypeSafeClassifier.invoke

Install and wire section, ex-classify-and-route
INFO

Reading TYPESAFE_API_KEY and LANGSMITH_API_KEY from environment

ex-wiring section, lines with os.environ
INFO

Credentials stored in LangSmith workspace secrets for 'typesafe/' routes

Install and wire section, option 3 comment

Referenced Domains

External domains referenced in skill content, detected by static analysis.

gateway.smith.langchain.comgithub.com

Use Cases

  • Replacing LLM calls in routing nodes with typed decision models to reduce latency and cost
  • Auditing an existing LangGraph agent to find where generate-then-parse patterns can be converted to classifiers
  • Designing thresholds and confidence gates for binary (Noul), multi-choice (Choice), and scored (Score) classification
  • Batching per-item classification using Send to parallelize routing decisions
  • Comparing fairness and cost of decision models vs LLM baselines on semantic routing tasks

Quality Notes

  • Excellent scope boundary: skill is narrowly focused on decision models for routing, not general LLM use
  • Clear separation of three deployment modes with concrete examples for each
  • Three explicit 'traps' section documents common misreadings that cause silent bugs (expected value vs level, confidence vs correctness, threshold transferability)
  • Real performance data provided (0.27s per item for Jev, 0.49s for SemIf vs 2.87s for Claude) with honest disclaimers about generalization
  • Comprehensive conversion playbook (references/conversion-playbook.md) provides structured six-step audit methodology with concrete signals to look for
  • Well-structured guidance on question wording with a measured real-world example showing how rephrasing moved Noul score from 0.798 to 0.005
  • Batching example with Send shows how to parallelize classification
  • Related skills section provides clear dependency graph
  • Error-handling guidance is implicit but solid: emphasis on calibration per model, threshold sweeping against labeled data, shadow deployment before cutover
Model: claude-haiku-4-5-20251001Analyzed: Oct 12, 2026

Reviews

Add this skill to your library to leave a review.

No reviews yet

Be the first to share your experience.

Use langchain-ai/langgraph-decision-models in your dev environment

Command Palette

Search for a command to run...