Integrations
LangChain
langchain-wity lets you use Wity inside LangChain. Ask typed questions in a chain, let Wity pick the model for each agent run, and stop risky tool calls before they run.
It has three parts:
|. See In a chain.Install#
pip install langchain-wity
- Needs Python 3.10 or newer.
- It's built on the Python SDK, so answers have the same fields.
- The package is called
langchain-wity, but in code you import it aslangchain_wity.
API key#
Get an API key from the keys page. Save it in your environment variables as WITY_API_KEY, and the classifier reads it by itself:
export WITY_API_KEY="wity_..."
To pass a key from somewhere else, like a secret manager, use WityClassifier(api_key=...).
Your first request#
This example reads a customer's message and asks Wity three questions about it: which team should handle it, whether it's urgent, and how much it hurts the customer. All three are answered in one request.
from langchain_wity import WityClassifier, choice, noul, scoreclassifier = WityClassifier() # reads your key from WITY_API_KEYres = classifier.invoke({# The text you want Wity to judge."state": "I was charged twice for my subscription this month. Please refund one of them today.","questions": {# Pick one of these teams."team": choice("Which team should handle this ticket?", {"billing": "Payments, charges and refunds","technical": "Bugs, errors and outages","other": "Anything else",}),# Yes or no."urgent": noul("Does the customer ask for something to happen soon?",yes="They ask for fast action",no="No time pressure",),# A level on a scale, from lowest to highest."severity": score("How much is this hurting the customer?", ["Not at all", "A little", "A lot"]),},})res.choices["team"].choice # "billing"res.choices["team"].probabilities # {"billing": 0.99993..., "technical": 0.00003..., "other": 0.00003...}res.nouls["urgent"].noul # 0.99997...: the probability of yesres.scores["severity"].score # 1.96507...: the expected level, from 0 (Not at all) to 2 (A lot)
stateis the text Wity judges.questionsnames each question, and you use that name to find its answer.- Answers are grouped by type in
res.choices,res.noulsandres.scores. They're the Python SDK's answer objects, unchanged. invoke,ainvoke,batchandabatchall work.- Keep one classifier and reuse it, so later calls reuse its connection. Call
close()when you're done.
Probabilities
In a chain#
Give the classifier its questions when you create it. Then each call takes only the state, so it fits after any step that outputs text:
from langchain_core.runnables import RunnableLambdafrom langchain_wity import WityClassifier, choice# Questions set once, so each call takes only the text.team = WityClassifier(questions={"team": choice("Which team should handle this ticket?", {"billing": "Payments, charges and refunds","technical": "Bugs, errors and outages",})},reasoning="off",)def send(res):answer = res.choices["team"]if answer.confidence < 0.7:return "Sent to a person: Wity isn't sure."return f"Sent to {answer.choice}."pipeline = RunnableLambda(lambda ticket: ticket.strip()) | team | sendpipeline.invoke(" The app crashes when I upload a photo. ") # "Sent to technical."pipeline.batch(["Please refund my last invoice.", "Login page shows error 500."])# ["Sent to billing.", "Sent to technical."]
Messages as state
State can be text, a dict, a list, a Pydantic model, a dataclass, or LangChain messages. Messages are sent as {role, content}, so Wity can judge a whole chat:
from langchain_core.messages import AIMessage, HumanMessagefrom langchain_wity import WityClassifier, noulupset = WityClassifier(questions={"upset": noul("Is the customer upset?")})chat = [HumanMessage("Hi, my order hasn't arrived."),AIMessage("Sorry about that! It should arrive tomorrow."),HumanMessage("You said that last week. This is the third time."),]upset.invoke(chat).nouls["upset"].noul # 0.99991...
Images, audio and files inside messages are sent by type only, like {"type": "image_url"}.
Model router#
ModelRouterMiddleware sends easy tasks to a cheap model and hard ones to a strong model. At the start of each agent run, Wity reads the user's latest message and picks a route.
from langchain.agents import create_agentfrom langchain_wity.middleware import ModelChoice, ModelRouterMiddlewarerouter = ModelRouterMiddleware(choices={"small": ModelChoice(model="openai:gpt-5-mini", criteria="Simple, well-scoped tasks."),"large": ModelChoice(model="openai:gpt-5", criteria="Complex tasks that need deeper reasoning."),},instructions="Choose the least costly model suited to the task.",)agent = create_agent("openai:gpt-5-mini", middleware=[router])result = agent.invoke({"messages": [{"role": "user", "content": "Design a sharding plan for a 40 TB Postgres database."}]})result["model_route"]["choice"] # "large"result["model_route"]["probabilities"] # {"small": 0.0303..., "large": 0.9696...}
- You need at least two routes. Any LangChain chat model works.
- Wity also sees up to 4 earlier user messages, so a follow-up keeps its context.
- Every model call in the run goes to the picked model.
- Wity's answer is saved in the agent state as
model_route, so you can see why a model was picked. - If Wity fails, the run stops with the error. The router never guesses a model.
Tool-risk gate#
AutoModeMiddleware checks the tools you list before they run. Wity sees the proposed call and the conversation, and answers one yes-or-no question: should this call be stopped?
from langchain.agents import create_agentfrom langchain_wity.middleware import AutoModeMiddleware# read_file, send_email and delete_file are your own tools.# Only the tools listed in the gate are checked.gate = AutoModeMiddleware(tools=[send_email, delete_file])agent = create_agent("openai:gpt-5-mini", tools=[read_file, send_email, delete_file], middleware=[gate])
- It stops calls the user didn't ask for, calls that do more than the user asked, and calls that expose private data.
- A call that does exactly what the user asked runs, even one like deleting the file they named.
- Only the user's own messages can approve a call. Text in a web page or file can't, even if it pretends to give orders.
- A stopped tool doesn't run. The agent gets an error message instead, so it can tell the user.
- If Wity fails, the tool doesn't run either.
Tuning the gate
A call is stopped when the probability of risky is at or above threshold, 0.5 by default. Try the gate on some of your own calls, safe and risky. Then set threshold between where they land:
from langchain_wity import NoulCriteria# Block only when Wity is more sure the call is risky.gate = AutoModeMiddleware(tools=[send_email], threshold=0.8)# Or say what risky and safe mean for your tools.gate = AutoModeMiddleware(tools=[delete_file],criteria=NoulCriteria(true="The call writes, deletes, publishes or changes access.",false="The call only reads public or user-provided data.",),)
Reasoning#
Wity answers most questions in one quick pass. On close calls it can think first, which usually takes 1 to 4 seconds. You choose when with reasoning:
classifier = WityClassifier(reasoning="off", max_latency_ms=2000)# The router and the gate take a classifier of their own, without questions.router = ModelRouterMiddleware(choices=..., instructions=..., classifier=WityClassifier(reasoning="off"))gate = AutoModeMiddleware(tools=[send_email], classifier=WityClassifier(reasoning="always"))
max_latency_ms caps how long one request may take, from 200 to 120000 ms. More in Reasoning & auto mode.
Options#
Everything WityClassifier takes:
auto.WITY_API_KEY.WITY_BASE_URL, then Wity's address.Errors#
Each error is both a Python SDK error and the matching LangChain error, so you can catch either. For example, WityRateLimitError is also LangChain's ModelRateLimitError.
from langchain_wity import WityRateLimitErrortry:res = classifier.invoke(ticket)except WityRateLimitError as err:print(err.status, err.headers.get("retry-after"))
- The classifier already retries. If you add LangChain's
.with_retry(), setmax_retries=0so calls aren't retried twice. - Each call shows up in LangSmith as a model call, with model
wity-1and its input tokens. - Your key never appears in errors, logs or traces.
The package is langchain-wity on PyPI. Questions and bugs: info@alphanimble.com. It's licensed Apache-2.0.
