Loading...
Loading...
One API and one credential for frontier and open-source LLMs, built into your Neon branch and powered by Databricks. Use when a user wants to call an LLM, add AI/chat/an agent to their app, route between model providers (OpenAI, Anthropic, Google/Gemini, Meta, Alibaba, DeepSeek), or avoid juggling separate provider API keys and accounts — especially when they already use Neon and want AI requests to branch with their project. Works with the OpenAI SDK, Anthropic SDK, google-genai, the Vercel AI SDK, and Mastra by changing only the base URL. Triggers include "call an LLM", "add AI to my app", "chat completion", "model routing", "LLM proxy/gateway", "one API for all models", "use Claude/GPT/Gemini", "AI SDK", "Mastra agent", "Neon AI Gateway", and "log/rate-limit AI calls".
npx skill4agent add neondatabase/agent-skills neon-ai-gatewayneonneonnpx skills add neondatabase/agent-skills --skill neonus-east-2modelclaude-sonnet-4-6gpt-5-minigemini-2-5-flashus-east-2us-east-2neon config applydeployneon checkoutpreview.aiGatewayneon config planneon env pull*-proGET /v1/modelsneon env pullapplydeploycheckouthttps://console.neon.tech/app/projects/<project-id>/branches/<branch-id>/ai-gateway/v1/models/v1/modelsneon.tsneonneon.tspreview.aiGateway// neon.ts
import { defineConfig } from "@neon/config/v1";
export default defineConfig({
preview: {
aiGateway: true,
},
});neon deploy # provisions the gateway on the linked branchneon.tspreview.aiGatewayneon.tsneonneon config status # print the branch's live config (is the gateway on?)
neon config plan # dry-run diff of what apply would change
neon config apply # enable the gateway on the branch (neon deploy is an alias)neon.tsneon checkoutneon deployconfig applydeploylinkcheckout.env.localenv pullpreview.aiGatewayneon env pull.env.env.localneon-env run -- <cmd>| Variable | Meaning |
|---|---|
| Gateway bearer token (a Neon credential, |
| Bare branch gateway host ( |
Neon injects only these two vars — it does not set/OPENAI_API_KEY. TheOPENAI_BASE_URLand Mastra's@neon/ai-sdk-providerreadneon/<model>directly (zero config); for the plain OpenAI SDK /NEON_AI_GATEWAY_*, build the client's@ai-sdk/openai+apiKeyfrom them (shown below), or set your ownbaseURLby hand (OPENAI_*leaves user-set vars untouched).env pull
NEON_AI_GATEWAY_BASE_URL@neon/ai-sdk-provider/v1/v1/chat/completions/openai/v1gpt-5-…-codexgpt-5-5-pro@ai-sdk/openai/openai/v1/responses/anthropic/v1/anthropic/v1/messages/gemini/v1beta/...generateContent/gemini/v1beta/models/<model>:generateContent${NEON_AI_GATEWAY_BASE_URL}/v1${NEON_AI_GATEWAY_BASE_URL}/openai/v1neon.tsparseEnv@neon/envenv.aiGatewayapiKeybaseUrlgenerateTextstreamText@neon/ai-sdk-providerNEON_AI_GATEWAY_BASE_URLNEON_AI_GATEWAY_TOKENimport { neon } from "@neon/ai-sdk-provider";
import { streamText } from "ai";
const result = streamText({
model: neon("gpt-5-mini"), // or claude-sonnet-4-6, gemini-2-5-flash, ...
messages,
tools: {
image_generation: neon.tools.imageGeneration({
outputFormat: "jpeg",
size: "1024x1024",
}),
},
});
return result.toUIMessageStreamResponse();generateTextimport { neon } from "@neon/ai-sdk-provider";
import { generateText } from "ai";
const { text } = await generateText({
model: neon("claude-haiku-4-5"), // or gpt-5-3-codex, gemini-2-5-flash, ...
prompt: "Summarize Postgres for me.",
});Preferover the bare@neon/ai-sdk-provider@ai-sdk/openai: Neon injects onlyopenai(), notNEON_AI_GATEWAY_*, soOPENAI_*won't pick up the gateway from the env on its own. If you do useopenai(), configure it explicitly with@ai-sdk/openai${process.env.NEON_AI_GATEWAY_BASE_URL}/openai/v1createOpenAI({ apiKey: process.env.NEON_AI_GATEWAY_TOKEN, baseURL:.})
toolsstopWhenimport { neon } from "@neon/ai-sdk-provider";
import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";
const { text } = await generateText({
model: neon("claude-sonnet-4-6"),
prompt: "How many open todos do I have, and what's the oldest one?",
tools: {
listTodos: tool({
description: "List the user's open todos.",
inputSchema: z.object({}), // AI SDK v5+: `inputSchema`, not `parameters`
execute: async () => db.select().from(todos),
}),
},
stopWhen: stepCountIs(5), // let the model call tools, then summarize
});neon-functionsreferences/ai-sdk.md@mastra/coreneon/<model>NEON_AI_GATEWAY_BASE_URLNEON_AI_GATEWAY_TOKENneon deploypreview.aiGatewayparseEnvenv.postgres.databaseUrl@mastra/pgimport { Agent } from "@mastra/core/agent";
import { parseEnv } from "@neon/env";
import config from "../neon";
const env = parseEnv(config);
export const personalAssistant = new Agent({
id: "personal-assistant",
name: "personal-assistant",
instructions:
"You are a warm, concise personal assistant with long-term memory.",
model: "neon/claude-haiku-4-5",
memory, // your Mastra memory store, e.g. @mastra/pg on env.postgres.databaseUrl
});NEON_AI_GATEWAY_*OPENAI_*apiKeybaseURL/openai/v1import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.NEON_AI_GATEWAY_TOKEN,
baseURL: `${process.env.NEON_AI_GATEWAY_BASE_URL}/openai/v1`,
});
const res = await client.responses.create({
model: "gpt-5-mini", // swap to claude-sonnet-4-6, gemini-2-5-flash, ...
input: "What is Neon?",
});baseURL/v1const client = new OpenAI({
apiKey: process.env.NEON_AI_GATEWAY_TOKEN,
baseURL: `${process.env.NEON_AI_GATEWAY_BASE_URL}/v1`,
});
const res = await client.chat.completions.create({
model: "claude-sonnet-4-6",
messages: [{ role: "user", content: "What is Neon?" }],
});${NEON_AI_GATEWAY_BASE_URL}/anthropic/v1/messages${NEON_AI_GATEWAY_BASE_URL}/gemini/v1beta/models/...modelclaude-sonnet-4-6gpt-5-minigemini-2-5-flashneon/v1/models/v1curl "$NEON_AI_GATEWAY_BASE_URL/v1/models" \
-H "Authorization: Bearer $NEON_AI_GATEWAY_TOKEN"GET ${NEON_AI_GATEWAY_BASE_URL}/v1/modelsGET ${NEON_AI_GATEWAY_BASE_URL}/openai/v1/models/v1neon.tspreview.aiGatewayneon.tsneon deployneon config applyneon linkneon checkoutNEON_AI_GATEWAY_TOKENNEON_AI_GATEWAY_BASE_URL.env.localneon env pull.env.env.localneon-env run -- <cmd>neon.tspreview.aiGatewaynt_live_...NEON_AI_GATEWAY_BASE_URL{
"object": "list",
"data": [
{
"id": "claude-sonnet-4-6", // catalog model ID — use directly in the `model` field
"canonical_slug": "claude-sonnet-4-6",
"name": "Claude Sonnet 4.6", // human-readable display name
"object": "model",
"owned_by": "anthropic", // anthropic | openai | google | meta | alibaba | databricks
"created": 0,
"enabled": true,
"context_length": null,
"architecture": {
"modality": "text->text",
"input_modalities": ["text"],
"output_modalities": ["text"],
"tokenizer": "Claude", // Claude | Gemini | GPT | "" (empty for open-source)
"instruct_type": null
},
"top_provider": {
"is_moderated": false,
"context_length": null,
"max_completion_tokens": null
},
"pricing": null,
"per_request_limits": null
}
// ... one entry per model in the branch's catalog
]
}Note:,context_length, andpricingare currentlyper_request_limitsandnulliscreatedfor every entry — for context windows, pricing, and capabilities use the models.dev catalog above. Use0when you need the live, branch-scoped list of servable model IDs (e.g. to populate a model picker or validate a/v1/modelsbefore a request).model
.mdAccept: text/markdown