Vercel AI SDK
Add policy checks to Vercel AI SDK tool calls.
The AI SDK makes it easy to give a model tools. This guide adds a check inside each tool, so the call runs only when it complies with your policy, and the model learns why when it doesn’t.
Check inside execute
The simplest integration point is the tool’s own execute function. By the time it runs, the model has produced validated input that matches your schema. Call Enforly with that input plus the facts your policy needs, and only perform the side effect on allow.
Whatever execute returns goes back to the model. Return a short, explicit object on refusal, so the next message to the user is accurate: “I can’t refund that order” instead of a made-up confirmation.
Where the check fits
One call between the step that prepares something and the step that sends, publishes, or runs it. Your code applies the decision.
See it decide
The same policy evaluated on three cases. Pick one and run the check to see which evidence it relies on.
Policy
The assistant may issue a refund only when the user asked for one in data.messages and the problem they describe fits the order in data.order. Never refund when the user only asked a question. If it isn’t clear whether the user wants a refund or help, ask the user first.
data
{"action": {"tool": "issueRefund","input": {"orderId": "o_81","amount": 89}},"messages": [{"role": "user","content": "The blender arrived with a cracked jar. I want my money back."}],"order": {"id": "o_81","items": ["Blender Pro 600"],"total": 89}}
Result
Why: The user asks for their money back, and a cracked jar fits the blender on the order.
Your code: Run execute() and return the refund.
Response JSON
{
"decision": "allow",
"allowed": true,
"violationProbability": 0.04,
"reviewProbability": 0.05,
"policyId": null,
"policyVersion": null,
"requestId": "req_7f3a…"
}Illustrative example. Real probabilities depend on your policy wording and the evidence you send.
allow · Clear request
The user asks for their money back, and a cracked jar fits the blender on the order.
deny · Only a question
The user asked how refunds work. Nobody asked for this order to be refunded.
review · Unclear intent
There’s a real problem with the order, but the user might want troubleshooting, a replacement, or a refund.
Combining with human approval
Recent AI SDK versions let a tool require human approval before it runs. Requiring approval for every call is safe but tiring. A good pattern is to let Enforly decide: allow runs automatically, review asks the user or goes to approval, deny never reaches a person. Check the AI SDK documentation for the approval API in your version.
Schema validation still matters
Your Zod inputSchema guarantees the shape: amount is a positive number. Enforly evaluates the meaning: whether the conversation actually asked for this refund. Keep both. See JSON validation against business rules.
Set it up
- 1
Install the SDK
npm install @enforly/sdkand setENFORLY_API_KEYon the server. - 2
Guard the tools with side effects
Add the check at the top of
executefor tools that write, pay, send, or delete. - 3
Return useful refusals
Tell the model what happened so it can explain or ask the user.
import { streamText, tool } from 'ai'
import { z } from 'zod'
import { Enforly } from '@enforly/sdk'
const guard = new Enforly({ apiKey: process.env.ENFORLY_API_KEY })
const issueRefund = tool({
description: 'Refund an order for the signed-in user',
inputSchema: z.object({ orderId: z.string(), amount: z.number().positive() }),
execute: async (input) => {
const order = await db.orders.get(input.orderId)
const result = await guard.check({
policyId: REFUND_POLICY_ID,
data: { action: { tool: 'issueRefund', input }, messages: messages.slice(-6), order },
}).catch(() => ({ allowed: false, decision: 'deny' }))
if (result.allowed) return { refunded: await payments.refund(order.id, input.amount) }
if (result.decision === 'review') return { status: 'needs_confirmation', note: 'Ask the user whether they want a refund.' }
return { status: 'refused', note: 'This refund is not allowed by policy.' }
},
})
const result = streamText({ model, messages, tools: { issueRefund } })Full request and response fields are in the API reference. Using a coding agent? Point it to Build with AI.
What Enforly doesn’t do
Enforly evaluates the information you send and returns a decision. It doesn’t run operations, and it doesn’t guarantee safety or compliance on its own.
- Enforly doesn’t change how the AI SDK streams or calls tools; it’s a call you make inside your tool.
- Pass the facts your policy needs (recent messages, the order). Enforly can’t read your database.
- Keep your API key on the server. Never call Enforly from browser code.
Questions
Does it work with generateText and agents too?
Yes. The check lives inside execute, so it runs however the tool is invoked.
Can I check the model’s final text as well?
Yes. Check the complete text before sending it to the user, for example in a non-streamed response or before persisting it.
What about other frameworks?
The pattern is the same anywhere: check before execute. See the tool call guide.
Related
Try this policy on your own data.
Copy the policy above, add your real examples, and see the decision. 200 free checks per month, no credit card.