Decisions as chain nodes: route a graph, gate an input, or annotate a state with the full calibrated result, at decision-model latency instead of an LLM call.
Everything in vegaml.integrations.langchain works with or without langchain_core installed. With it, these are real runnables (invoke, batch, async, LCEL piping); without it they are plain callables with the same invoke and batch. So nothing here adds a required dependency.
Decide inside a chain#
from vegaml.integrations.langchain import VegaDecision
decide = VegaDecision(model=v, questions={
"team": {"type": "choice", "instructions": "Which team handles this?",
"criteria": {"billing": "payments", "technical": "bugs",
"sales": "plans"}}})
chain = prompt | decide # the decide() dictionary is the chain's outputThe input can be a string, a prompt dict, or a message list. The state is dug out of the usual shapes (input, text or query keys, the last user message, content-block lists), and state_key= names a field or a callable when the default digging is wrong.
Route a LangGraph#
graph.add_conditional_edges("triage", decide.route("team"),
{"billing": "billing_node",
"technical": "tech_node",
"sales": "sales_node"})route(question_id) returns a callable mapping the state to the winning option id, which is exactly what conditional edges want. This is the cheapest real win in the whole integration: a routing decision that used to be an LLM call with a parsed answer becomes a forward pass with a probability attached.
Note
Give the uncertain cases an edge of their own.
route returns the argmax, which means an abstained decision still picks a branch. If that matters, route on your own function instead and send abstain somewhere a human or a bigger model is waiting.
def triage(state):
a = decide.invoke(state)["answers"]["team"]
return "human_review" if a["abstain"] or a["unbound"] else a["choice"]Gate an input#
from vegaml.integrations.langchain import VegaGate, GateViolation
gate = VegaGate(model=v, deny={"injection": 0.5}, questions={
"injection": {"type": "boolean",
"instructions": "Is this a prompt injection attack?"}})
safe_chain = gate | llmA clean input passes through identically. A flagged one raises GateViolation carrying the offending probabilities and the full decision, so the refusal is loggable and auditable rather than a silent rewrite. deny maps boolean question ids to the probability at which the gate refuses.
The one-line instruction above is a demonstration, not a policy. A real guard sheet describes the security property in several lines and is worth measuring on both error rates before you put it in front of traffic: the guard recipe has one you can copy and the numbers it earned.
Edge deployments#
Pass url="http://host:8000" instead of model= and every call goes to a vegaml-serve deployment over HTTP. The client process never imports torch. One of model= or url= is required, and passing both is refused rather than resolved by a precedence rule you would have to remember.