Skip to content

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.

What you doBest for
In your configSet ai.provider in askdb.config.ts. The CLI, Studio, and the HTTP API resolve the model for you. No model code.CLI workflows, Studio, the bundled HTTP service
In your codePass any Vercel AI SDK LanguageModel to ask(). Full control.Embedding @askdb/core in your own service
With @askdb/clientCall createAskDb({ config, providers }) then askdb.ask("question"). Schema, model, and dialect come from config.The shortest config-driven embed — one schema, one model

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).

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/ai-* + @askdb/client when you want askdb.config.ts to 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 LanguageModel directly with the AI SDK when your app already owns provider/config resolution (env vars, a secrets manager, feature flags), or when you need to reuse the same model instance for calls AskDB doesn’t make — for example, a separate generateObject() call for chart planning that sits next to ask() in your service. @askdb/ai’s registry only resolves models for AskDB’s own config shape, so if you need the model object for other LLM calls too, building it with the AI SDK directly is usually less code, not more.

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.

Set ai.provider in askdb.config.ts and supply the matching env var. The CLI, Studio, and the HTTP service call createLanguageModelFromEnv internally.

askdb.config.ts
export default {
ai: {
provider: "openai",
providerConfig: {
openai: {
apiKey: env("OPENAI_API_KEY"),
},
},
},
// ...
};

Default model: gpt-4o-mini. To use a different one, set model in providerConfig.openai (the scaffolded config reads it from OPENAI_MODEL).

See the Configuration reference for the full env-var table and the custom-provider escape hatch.

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:

Terminal window
npm install ai @ai-sdk/openai
import { openai } from "@ai-sdk/openai";
// Reads OPENAI_API_KEY from the environment
const model = openai("gpt-4o-mini");

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 and the adapters you pass — the same resolution the CLI and the bundled HTTP API use.

Terminal window
npm install @askdb/client @askdb/config @askdb/ai-openai @askdb/ai-anthropic
import { createAskDb } from "@askdb/client";
import { bootstrapAskDbEnv, getAskDbRuntimeConfig } from "@askdb/config";
import { openaiProvider } from "@askdb/ai-openai";
import { anthropicProvider } from "@askdb/ai-anthropic";
bootstrapAskDbEnv({ cwd: process.cwd() });
const askdb = createAskDb({
config: getAskDbRuntimeConfig(),
providers: [openaiProvider, anthropicProvider], // whichever ai.provider the config selects
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?");

Need the model object yourself (to pass to ask() directly, or to share with other code)? Build it from the same config with @askdb/ai’s registry — the one advanced case where you import @askdb/ai directly:

import { bootstrapAskDbEnv, getAskDbRuntimeConfig } from "@askdb/config";
import { createAiRegistry } from "@askdb/ai";
import { openaiProvider } from "@askdb/ai-openai";
bootstrapAskDbEnv({ cwd: process.cwd() });
const rt = getAskDbRuntimeConfig();
const ai = createAiRegistry([openaiProvider]);
const model = await ai.createLanguageModelFromEnv(rt.ai.aiEnv);

Adapters declare ai and @askdb/ai as peer dependencies — add ai to your own package.json when you want to pin its version.

Set ai.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.

With @askdb/client, createAskDb resolves ai.reasoning.nlToSql/effort automatically — nothing else to wire. Override per call, or set a client-level default:

const askdb = createAskDb({
config: getAskDbRuntimeConfig(),
providers: [openaiProvider],
reasoningEffort: "medium", // client-level default, overrides ai.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 { openaiProvider } from "@askdb/ai-openai";
import { ask } from "@askdb/core";
const ai = createAiRegistry([openaiProvider]);
const config = ai.resolveAiConfig(rt.ai.aiEnv)!; // e.g. { provider: "openai", model: "o4-mini", ... }
const model = await ai.createLanguageModel(config);
// Provider-portable: "minimal" | "low" | "medium" | "high".
// 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 },
});

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.

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. AskDB handles the chunking, embedding, and retrieval; you configure the store and it takes care of the rest.
  • Question length.
  • Model tier.

AskDB itself is Apache 2.0-licensed and free.