Building a Jev-Powered Model Router with LangChain and TypeSafe AI-Jev

Jev doesn't just classify text — it decides which AI model should handle your prompt. Here's how I built a working model router using LangChain's TypeSafe integration that sends simple questions to a fast cheap model and complex ones to a powerful one, all in under 50 lines of Python.

After getting Jev installed and running my first phishing classification test, the next step was building something more architecturally interesting — a model router. The idea comes straight from LangChain’s Sydney in the TypeSafe integration video: use Jev’s speed to make routing decisions instead of wasting a powerful LLM on a question that doesn’t need it.

What a model router does

In any agent system you’re making a tradeoff. Fast cheap models (like claude-haiku) are great for simple tasks but struggle with complexity. Powerful models (like claude-sonnet) handle hard reasoning but cost more and run slower. A router sits in front of both and decides which to use based on the incoming prompt — and Jev is nearly instant at making that call.

The environment

Working in ~/jev_lab on Debian with a dedicated jev-env conda environment. The key package is langchain-typesafe, which provides the TypeSafeClassifier class along with Choice and Score question types.

One thing to know going in: the LangChain TypeSafe integration is in beta and the API doesn’t match the video exactly. The Choice class takes criteria as a dictionary of label→description pairs, not a simple options list. The Score class takes criteria as an ordered list of strings, not a dict. Took a few iterations to get the schema right — checking help(Choice) and help(Score) directly in the terminal was the fastest way to find the correct field formats.

The script

python

from langchain_typesafe import TypeSafeClassifier, Choice, Score

classifier = TypeSafeClassifier()

def route_prompt(prompt: str) -> str:
    state = f"User prompt: {prompt}"
    
    questions = {
        "complexity": Choice(
            instructions="How complex is this task?",
            criteria={
                "simple": "Straightforward tasks requiring little reasoning.",
                "complex": "Tasks requiring deep reasoning or specialized knowledge."
            }
        ),
        "depth": Score(
            instructions="How much reasoning depth does this require?",
            criteria=[
                "No reasoning needed, answer is immediate.",
                "Some reasoning required.",
                "Deep multi-step reasoning required."
            ]
        )
    }
    
    response = classifier.invoke({"state": state, "questions": questions})
    complexity = response.choices["complexity"].choice
    depth_score = response.scores["depth"].score
    
    if complexity == "simple" or depth_score < 1.0:
        model = "claude-haiku-4-5"
    else:
        model = "claude-sonnet-4-6"
    
    print(f"Prompt: {prompt[:60]}...")
    print(f"Complexity: {complexity}")
    print(f"Depth score: {depth_score:.2f}")
    print(f"Routed to: {model}")
    return model

if __name__ == "__main__":
    test_prompts = [
        "What is 2 + 2?",
        "Explain the difference between TCP and UDP",
        "Design a multi-stage intrusion detection pipeline"
    ]
    for prompt in test_prompts:
        route_prompt(prompt)

The results

Prompt: What is 2 + 2?...
Complexity: simple
Depth score: 0.00
Routed to: claude-haiku-4-5

Prompt: Explain the difference between TCP and UDP...
Complexity: simple
Depth score: 0.43
Routed to: claude-haiku-4-5

Prompt: Design a multi-stage intrusion detection pipeline...
Complexity: complex
Depth score: 1.96
Routed to: claude-sonnet-4-6

Jev correctly identified the intrusion detection pipeline as complex (depth score 1.96 out of 2) and routed it to the more powerful model. The simpler questions scored 0.00 and 0.43 and stayed on the fast model. Two questions answered in parallel, nearly instantly, for a fraction of a cent.

What’s next

Module 2: auto mode — using Jev to evaluate whether a tool call is risky before it executes. The use case maps directly to security work: before an agent deletes a file, drops a table, or makes an irreversible change, Jev checks it first.