Vercel AI SDK Integration
Enforce Control Zero policies in applications built with the Vercel AI SDK.
Overview
The Vercel AI SDK provides a unified interface for building AI-powered applications in TypeScript/JavaScript. Control Zero integrates through middleware that intercepts model calls and tool invocations before they execute.
Installation
npm install @controlzero/sdk ai
Setup
import { Client } from '@controlzero/sdk';
import { generateText, streamText, tool, gateway } from 'ai';
const cz = new Client({
policy: {
rules: [
{ allow: 'llm:generate', reason: 'LLM calls are permitted' },
{ allow: 'search_web:*', reason: 'Web search is permitted' },
{ deny: 'read_database:*', reason: 'Database access is blocked' },
],
},
});
Basic Usage
Enforce Before Generation
Pass raiseOnDeny: true so a deny decision throws and blocks the AI call.
Without it, guard() returns the decision but does not stop execution.
async function generate(prompt: string, model: string = 'openai/gpt-5.4') {
// Enforce policy before the AI call
await cz.guard('llm', {
method: 'generate',
args: { model },
raiseOnDeny: true,
});
const result = await generateText({
model: gateway(model),
prompt,
});
return result.text;
}
Enforce Tool Calls
AI SDK v6 tool definitions use inputSchema, not parameters.
import { z } from 'zod';
async function runAgent(prompt: string) {
// Enforce the LLM call
await cz.guard('llm', {
method: 'generate',
args: { model: 'openai/gpt-5.4' },
raiseOnDeny: true,
});
const result = await generateText({
model: gateway('openai/gpt-5.4'),
prompt,
tools: {
searchWeb: tool({
description: 'Search the web for information',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => {
await cz.guard('search_web', { method: 'execute', raiseOnDeny: true });
return `Results for: ${query}`;
},
}),
readDatabase: tool({
description: 'Query the database',
inputSchema: z.object({ sql: z.string() }),
execute: async ({ sql }) => {
await cz.guard('read_database', { method: 'execute', raiseOnDeny: true });
return `DB results for: ${sql}`;
},
}),
},
});
return result.text;
}
Streaming with Enforcement
async function streamResponse(prompt: string) {
await cz.guard('llm', {
method: 'generate',
args: { model: 'openai/gpt-5.4' },
raiseOnDeny: true,
});
const result = streamText({
model: gateway('openai/gpt-5.4'),
prompt,
});
return result.toUIMessageStreamResponse();
}
Next.js Route Handler Example
// app/api/chat/route.ts
import { Client } from '@controlzero/sdk';
import { streamText, gateway } from 'ai';
const cz = new Client({ policyFile: './controlzero.yaml' });
export async function POST(req: Request) {
const { messages } = await req.json();
// Enforce before streaming. raiseOnDeny throws on deny and returns 500
// unless you catch and convert.
await cz.guard('llm', {
method: 'generate',
args: { model: 'openai/gpt-5.4' },
raiseOnDeny: true,
});
const result = streamText({
model: gateway('openai/gpt-5.4'),
messages,
});
return result.toUIMessageStreamResponse();
}
First-party middleware: vercelAiMiddleware
For teams who want one-line governance across every LLM call (instead of a
guard() per route), the Node SDK ships a middleware factory that plugs
directly into the AI SDK's wrapLanguageModel API.
npm install @controlzero/sdk ai
import { Client } from '@controlzero/sdk';
import { vercelAiMiddleware } from '@controlzero/sdk/integrations/vercelAi';
import { generateText, wrapLanguageModel, gateway } from 'ai';
const cz = new Client({ policyFile: './controlzero.yaml' });
// Wrap any Vercel AI SDK language model. Every generate/stream call is
// now policy-checked before going to the provider, and audited after.
const governedModel = wrapLanguageModel({
model: gateway('openai/gpt-5.4'),
middleware: vercelAiMiddleware(cz, { agentId: 'chat-backend' }),
});
const result = await generateText({
model: governedModel,
prompt: "Summarize today's board deck.",
});
What the middleware does on every call:
- Extracts the model + provider from the AI SDK request.
- Calls
cz.guard('llm', { method: 'generate', args: { model }, context: { tags: { provider } }, raiseOnDeny: true }). - On allow, passes the request through; on deny, throws before the provider is hit.
- After the call, records tokens, latency, and decision metadata to the audit trail.
Use this when you want governance to be invisible to feature code and enforced consistently across every AI SDK call path.
Example Policy
{
"name": "vercel-ai-policy",
"rules": [
{
"effect": "allow",
"action": "llm:generate",
"resource": "model/gpt-5.4"
},
{
"effect": "allow",
"action": "tool:call",
"resource": "tool/search_web"
},
{
"effect": "deny",
"action": "tool:call",
"resource": "tool/read_database"
}
]
}
Python: ControlZeroVercelAIMiddleware
If you are building a Python backend that calls Vercel AI SDK endpoints (or any OpenAI-compatible endpoint from Python), use the first-party Python middleware.
from controlzero import Client
from controlzero.integrations.vercel_ai import ControlZeroVercelAIMiddleware
cz = Client(api_key="cz_live_your_key_here")
middleware = ControlZeroVercelAIMiddleware(cz, agent_id="my-python-backend")
# Before calling the AI endpoint
result = middleware.before_call(model="gpt-5.4", action="chat")
if not result["allowed"]:
raise PermissionError(f"Blocked: {result['decision'].reason}")
# Call the model...
response = call_your_ai_endpoint(...)
# After the call -- logs tokens and latency to the audit trail
middleware.after_call(
model="gpt-5.4",
action="chat",
input_tokens=response.usage.input_tokens,
output_tokens=response.usage.output_tokens,
duration_ms=elapsed_ms,
)
Or wrap an async generate function end-to-end:
import asyncio
async def my_generate(prompt: str) -> str:
# ... your model call here
return response_text
governed_generate = middleware.wrap_generate(my_generate)
# Every call is now governed
result = await governed_generate("Summarize this document")
Install: pip install controlzero (no extra packages needed).
Next Steps
- See the Node.js SDK for the full API reference.
- Explore the Customer Support Guide for a complete chat application example.