Bring your own model
Model providers
AskDB doesn’t host inference. You wire a model in, AskDB calls it, and the model’s response goes back through your provider — billed to your account.
Two ways to wire a model
Section titled “Two ways to wire a model”| What you do | Best for | |
|---|---|---|
| In your config | Set ai.provider in askdb.config.ts. The CLI, Studio and the HTTP API resolve the model for you. In your own code, createAskDb({ config }) from @askdb/client does the same: schema, model and dialect come from config. No model code. | CLI workflows, Studio, the bundled HTTP service, and embeds where askdb.config.ts owns provider and model selection |
| In your code | Pass any Vercel AI SDK LanguageModel to ask(). Full control. | Embedding @askdb/core in your own service |
Same providers, same keys — pick per surface. You can use both in one project (for example: Studio uses your config, your embedding service constructs the model directly).
Which should I use?
Section titled “Which should I use?”ask()’s actual contract is model: LanguageModel — a plain Vercel AI SDK model object. Every path below ends up building one of those; @askdb/ai and @askdb/client are a convenience layer on top, not a separate mechanism. Neither path is more “correct.” Pick based on who owns provider configuration:
- Use
@askdb/client(built on@askdb/ai) when you wantaskdb.config.tsto drive provider/model selection — one config file, no provider code, and the CLI/Studio/your app all resolve the same way. This is the right default when you’re embedding AskDB as a standalone service and don’t already have a provider-config system. - Construct a
LanguageModeldirectly with the AI SDK when your app already owns provider/config resolution (env vars, a secrets manager, feature flags), so provider config doesn’t live in two systems.
Either way you get a plain AI SDK LanguageModel, so you can reuse it for calls AskDB doesn’t make, such as a generateObject() call for chart planning next to ask(). On the config path, build it with @askdb/ai’s registry, as One config driving both shows.
If you’re an agent implementing this integration: check whether the host app already constructs AI SDK models elsewhere before reaching for @askdb/ai — duplicating provider config across two systems is the failure mode to avoid.
In your config (CLI, Studio, HTTP API)
Section titled “In your config (CLI, Studio, HTTP API)”Set ai.provider in askdb.config.ts and supply the matching env var. The CLI, Studio, and the HTTP service pass the config’s ai.language section to @askdb/ai’s registry, which builds the model.
import { defineConfig, env } from "@askdb/config";
export default defineConfig({ ai: { provider: "openai", providerConfig: { openai: { apiKey: env("OPENAI_API_KEY") }, }, language: { model: "gpt-4o-mini" }, // optional: each provider has a default model }, // ...});For another built-in provider, change provider and key its connection by the same id: anthropic, google, azure or gateway. Azure’s connection also takes resourceName, and its model is your deployment name. The gateway ships with ai, so it needs no @ai-sdk/* package, and its model ids are <upstream>/<model>. Anthropic has no embeddings API: for RAG, set ai.embedding.provider to another provider.
ai.providerConfig holds only the connection (key, endpoint, resource), and its reference lists each provider’s fields. The model goes in ai.language, whose reference lists each provider’s default model, and the embedding model for RAG in ai.embedding, which can use the same provider or another one. Environment variables lists the names askdb init writes, and A provider that isn’t built in covers any other provider.
In your code (embedding)
Section titled “In your code (embedding)”When you embed @askdb/core directly, pass any Vercel AI SDK LanguageModel to ask(). AskDB calls the model with the assembled prompt and parses the SQL it returns. No proxy. No middleman.
import { ask } from "@askdb/core";
const { sql } = await ask({ question, schema, dialect: "postgres", model, // any LanguageModel from the Vercel AI SDK});Install the provider package and construct the model. ai is a peer dependency of @askdb/core (AI SDK 6 or 7 are both supported), so it is installed alongside the provider package and AskDB uses whichever version your app pins:
npm install ai @ai-sdk/openaipnpm add ai @ai-sdk/openaiyarn add ai @ai-sdk/openaiimport { openai } from "@ai-sdk/openai";
// Reads OPENAI_API_KEY from the environmentconst model = openai("gpt-4o-mini");npm install ai @ai-sdk/anthropicpnpm add ai @ai-sdk/anthropicyarn add ai @ai-sdk/anthropicimport { anthropic } from "@ai-sdk/anthropic";
const model = anthropic("claude-sonnet-4-6");npm install ai @ai-sdk/googlepnpm add ai @ai-sdk/googleyarn add ai @ai-sdk/googleimport { google } from "@ai-sdk/google";
// Reads GOOGLE_GENERATIVE_AI_API_KEY from the environmentconst model = google("gemini-2.0-flash");npm install ai @ai-sdk/azurepnpm add ai @ai-sdk/azureyarn add ai @ai-sdk/azureimport { createAzure } from "@ai-sdk/azure";
const azure = createAzure({ resourceName: process.env.AZURE_RESOURCE_NAME, apiKey: process.env.AZURE_API_KEY,});
const model = azure("your-deployment-name");For self-hosted or alternative providers that expose the OpenAI chat-completions shape (vLLM, llama.cpp server, OpenRouter, Together, LM Studio, Ollama, …):
npm install ai @ai-sdk/openai-compatiblepnpm add ai @ai-sdk/openai-compatibleyarn add ai @ai-sdk/openai-compatibleimport { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const local = createOpenAICompatible({ name: "local", baseURL: "http://localhost:11434/v1", apiKey: "ignored-by-local-server",});
const model = local("llama3.1");npm install aipnpm add aiyarn add aiimport { gateway } from "ai";
// Reads AI_GATEWAY_API_KEY from the environmentconst model = gateway("openai/gpt-4o-mini");One config driving both
Section titled “One config driving both”If you embed AskDB but want to reuse the same askdb.config.ts instead of hardcoding a provider, use @askdb/client. createAskDb resolves the schema, model, and dialect from your config — the same resolution the CLI and the bundled HTTP API use. Install the AI SDK package for the provider your config selects (@ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/google, or @ai-sdk/azure; the AI Gateway needs none):
npm install @askdb/client @askdb/core @askdb/config ai @ai-sdk/openaipnpm add @askdb/client @askdb/core @askdb/config ai @ai-sdk/openaiyarn add @askdb/client @askdb/core @askdb/config ai @ai-sdk/openaiimport { createAskDb } from "@askdb/client";import { bootstrapAskDbEnv, getAskDbRuntimeConfig } from "@askdb/config";
bootstrapAskDbEnv({ cwd: process.cwd() });
const askdb = createAskDb({ config: getAskDbRuntimeConfig(), // ai.provider picks one of @askdb/ai's built-in providers schema: { path: "./my-app.schema" }, // or set host.schemaPath in config and omit this});
// Only the question — schema, model, and dialect come from config.const { sql } = await askdb.ask("Which customers signed up last week?");Every built-in provider is registered, and each loads its @ai-sdk/* package only when it first builds a model. If that package isn’t installed, the call fails with Provider '<name>' requires the optional peer dependency @ai-sdk/<name>. Install it: npm i @ai-sdk/<name>. To restrict the set, pass providers: ["openai"]. To add a provider that isn’t built in, pass an AiProviderAdapter object in providers (see A provider that isn’t built in).
Need the model object yourself, to pass to ask() directly or to share with your app’s other AI calls? Build it once from the same config with @askdb/ai’s registry. It returns a plain AI SDK LanguageModel, so one model, from one configuration, serves AskDB and your own generateText() or generateObject() calls:
import { bootstrapAskDbEnv, getAskDbRuntimeConfig } from "@askdb/config";import { createAiRegistry } from "@askdb/ai";import { ask } from "@askdb/core";import { generateText } from "ai";
bootstrapAskDbEnv({ cwd: process.cwd() });const rt = getAskDbRuntimeConfig();const ai = createAiRegistry(); // every built-in provider; or createAiRegistry(["openai"])const model = await ai.createLanguageModelFromSection(rt.ai.language); // undefined without an API keyif (!model) throw new Error(ai.keyMissingMessage("my-app"));
// The same model for AskDB and for your own AI SDK calls:const { sql } = await ask({ question, schema, dialect: "postgres", model });const { text } = await generateText({ model, prompt: `Explain this SQL in one sentence: ${sql}` });rt.ai.language is the config’s typed ai.language section: its provider, its connection’s settings and its model. The registry maps it to the provider’s settings, and rt.ai.embedding goes to createEmbeddingModelFromSection the same way.
@askdb/ai declares ai as a peer dependency and the @ai-sdk/* provider packages as optional peers. Add ai and the provider package you use to your own package.json so you control their versions.
Without a config file
Section titled “Without a config file”To use @askdb/ai’s built-in providers in an app that has no askdb.config.*, pass the registry an env map, such as process.env, instead of a config section:
import { createAiRegistry } from "@askdb/ai";
const ai = createAiRegistry();const model = await ai.createLanguageModelFromEnv(process.env); // undefined without an API keyASKDB_AI_PROVIDER picks the provider, openai when it’s unset. @askdb/ai takes its settings only from the map you pass. Its one read of process.env is a check: with no base URL configured, it refuses an OPENAI_BASE_URL or ANTHROPIC_BASE_URL with a username or password, which the provider SDK would read itself. The @askdb/ai reference lists the variables it reads from the map, in order. A config section and an env map that hold the same settings give the same model.
A provider that isn’t built in
Section titled “A provider that isn’t built in”The built-in providers are openai, azure (with the aliases azure-openai and foundry), google, anthropic and gateway. For another one, you have two options.
An endpoint that speaks OpenAI’s API, such as vLLM, OpenRouter, Together or Ollama: use the built-in openai provider and set baseUrl on its connection to the endpoint. The CLI, Studio and the HTTP API can use it, since openai is built in.
Any other provider: ai.provider, and a section’s provider, take any id. Key the provider’s connection by that id in ai.providerConfig (apiKey and baseUrl), and put its model in ai.language.model:
import { defineConfig, env } from "@askdb/config";
export default defineConfig({ ai: { provider: "mistral", // any id providerConfig: { mistral: { apiKey: env("MISTRAL_API_KEY") }, }, language: { model: "mistral-large-latest" }, }, // ...});AskDB then hands the connection and the model to the adapter registered under that id, so you need your own code to register one. The CLI, Studio and the HTTP API register only the built-in providers, so they can’t use a custom id. This adapter builds Mistral models with @ai-sdk/mistral:
import { createAiRegistry, resolveBaseConfig, type AiProviderAdapter } from "@askdb/ai";import { createMistral } from "@ai-sdk/mistral";
const MISTRAL_ENV = { apiKeyVars: ["MISTRAL_API_KEY"], modelVars: ["MISTRAL_MODEL"], defaultModel: "mistral-large-latest" };
export const mistralAdapter: AiProviderAdapter = { provider: "mistral", configHint: "For Mistral, set MISTRAL_API_KEY.", resolveConfig: (env, options) => resolveBaseConfig("mistral", env, MISTRAL_ENV, options), resolveConfigFromSection(section, { usage }) { const apiKey = section.connection.apiKey ?? section.connection.apiKeySecondary; if (!apiKey) return undefined; const model = section.model ?? (usage === "language" ? MISTRAL_ENV.defaultModel : undefined); if (!model) throw new Error("mistral: set ai.embedding.model"); return { provider: "mistral", apiKey, model, ...(section.connection.baseUrl ? { baseURL: section.connection.baseUrl } : {}) }; }, createLanguageModel: (config) => createMistral({ apiKey: config.apiKey, baseURL: config.baseURL })(config.model), createEmbeddingModel: (config) => createMistral({ apiKey: config.apiKey, baseURL: config.baseURL }).embedding(config.model),};
const ai = createAiRegistry(["openai", mistralAdapter]);Pass it to createAskDb({ providers: ["openai", mistralAdapter] }) the same way: list built-in names next to it to keep those. AiProviderAdapter lists every field.
Reasoning/latency effort
Section titled “Reasoning/latency effort”Set ai.language.reasoning in askdb.config.ts (see the config reference) for the config-driven path. Skip it and current behavior is unchanged — no providerOptions are sent, and the provider/model uses its own default.
AskDB maps the portable value to each provider’s own setting with the @askdb/ai registry’s resolveProviderOptions() method:
| Provider | What AskDB sends |
|---|---|
| OpenAI, Azure | providerOptions.openai.reasoningEffort. gpt-6 and later accept low through max but not minimal, so they get low for minimal. |
providerOptions.google.thinkingConfig.thinkingLevel on Gemini 3.x, and thinkingConfig.thinkingBudget on Gemini 2.5. | |
| Anthropic | thinking: adaptive thinking plus effort on Claude Sonnet 4.6, Opus 4.6 and newer, and the 5.x models, with low for minimal; a budgetTokens budget on Claude 3.7 Sonnet and the 4.0–4.5 models. |
| Vercel AI Gateway | The mapping of the model’s upstream (openai/…, google/… or anthropic/…) for the id after the prefix, which the gateway passes on. A model from another upstream gets none. |
A model that doesn’t support reasoning tuning, such as gpt-4o-mini, gpt-5-chat-latest, gemini-2.0-flash or claude-3-5-haiku, never receives these options, even when reasoning is set.
With @askdb/client, createAskDb resolves ai.language.reasoning.nlToSql/effort automatically — nothing else to wire. Override per call, or set a client-level default:
const askdb = createAskDb({ config: getAskDbRuntimeConfig(), reasoningEffort: "medium", // client-level default, overrides ai.language.reasoning from config});
const { sql } = await askdb.ask("Which customers signed up last week?", { reasoningEffort: "low", // per-call override, overrides the client default});Programmatically, resolve the portable effort to provider providerOptions with @askdb/ai’s registry, then forward it to ask()’s deps.providerOptions — @askdb/core forwards it verbatim to generateText and never interprets it:
import { createAiRegistry } from "@askdb/ai";import { ask } from "@askdb/core";
const ai = createAiRegistry(["openai"]);const config = ai.resolveAiConfigFromSection(rt.ai.language)!; // e.g. { provider: "openai", model: "o4-mini", ... }const model = await ai.createLanguageModel(config);
// Provider-portable: "minimal" | "low" | "medium" | "high". To use the config's// ai.language.reasoning instead: resolveReasoningEffortFromSection(rt.ai.language.reasoning, "nlToSql").// Returns undefined (nothing sent) when the model doesn't support reasoning tuning.const providerOptions = ai.resolveProviderOptions(config, { reasoningEffort: "low" });
const { sql } = await ask({ question, schema, dialect: "postgres", model, deps: { providerOptions },});Key management
Section titled “Key management”Your model API key lives in your environment, not in the schema artifact and not in AskDB. The CLI loads .env for local work; in production, supply the key the same way you supply your database URL.
AskDB has no API of its own to authenticate — it’s a library. The only credentials it touches are the ones you pass to your model provider through the AI SDK.
Switching models
Section titled “Switching models”Models can be swapped without changing any other AskDB code. A common pattern: cheaper model for low-stakes analytics, stronger model for complex multi-join queries.
import { openai } from "@ai-sdk/openai";
const cheapModel = openai("gpt-4o-mini");const strongModel = openai("gpt-4o");
const model = question.length > 200 ? strongModel : cheapModel;
const { sql } = await ask({ question, schema, dialect: "postgres", model });You’re billed by your model provider for every ask() call. Cost scales with:
- Schema size (larger artifacts mean more tokens in the prompt) — use RAG for large schemas to send only the relevant slice. You build the index with
@askdb/ragand pass a retriever toask(); AskDB chunks, embeds and retrieves. - Question length.
- Model tier.
AskDB itself is Apache 2.0-licensed and free.
Read next
Section titled “Read next”© 2026 Yahya Gilany

