qua_add_external_ai_model#

Registers an external generative AI model — the endpoint, format and model identity that the AI functions invoke. The model is stored as an instance-scoped connection under the internal AI Model provider and is listed by the sys.external_models view.

The registration carries no API key. A model’s key resolves at call time from a credential, by the model’s endpoint URL — see Provider API keys below.

Syntax#

qua_add_external_ai_model [ @name = ] N'name'
    , [ @api_format = ] N'api_format'
    , [ @location = ] N'location'
    , [ @model = ] N'model'
    [ , [ @parameters = ] N'parameters' ]

Arguments#

[ @name = ] N’name

The model’s registration name — the value used to bind it to a session and to drop it. sysname. The name must be unique among the instance’s connections.

[ @api_format = ] N’api_format

The provider wire format: Anthropic, OpenAI or Azure OpenAI. sysname. The value is matched case-insensitively and stored in the canonical spelling above; any other value is rejected at registration.

[ @location = ] N’location

The full HTTPS endpoint the requests are sent to. nvarchar(4000). For Azure OpenAI the deployment and api-version are part of the URL. This is also the name a credential must carry to supply this model’s API key — see Provider API keys below.

[ @model = ] N’model

The provider-side model identifier, for example claude-sonnet-5 or gpt-4o. nvarchar(128). Azure OpenAI takes the deployment from the endpoint URL; the value is still recorded for the catalog view.

[ @parameters = ] N’parameters

Optional JSON object of provider request parameters, merged into every request the model serves — for example N'{"max_tokens":1024,"temperature":0.2}'. A parameter overrides the same-named request field. The keys messages, model, system and stream are controlled by the AI function and are rejected.

The reserved sql_rest_options key is consumed by Querona itself and is never forwarded to the provider:

Option

Description

retry_after_cap_seconds

Ceiling, in seconds (0–600, default 20), applied to a wait the provider requests through the Retry-After header when Querona retries a throttled or failed call. 0 ignores Retry-After and uses the default backoff.

Remarks#

A single registered model is used by the AI functions automatically. When more than one model is registered, bind the session’s ambient model by name:

EXEC sys.sp_set_session_context N'ai_default_model', N'MyClaude';

Provider API keys#

A model’s API key is never part of its registration — it resolves at call time from a CREDENTIAL or DATABASE SCOPED CREDENTIAL whose name is the model’s @location endpoint URL, matched by the same prefix rule OPENROWSET (BULK ...) uses for direct-URL authentication (name equals the URL, or is a prefix of it ending at a / boundary). Highest precedence first:

  1. A database scoped credential in the calling database — a per-database override, for example a tenant-specific key on a shared cluster.

  2. A server credential — a cluster-wide default available to every database.

A call with no matching credential at either scope fails, naming the endpoint (never any key material).

-- once, any node: every database's Anthropic models use this key by default
CREATE CREDENTIAL [https://api.anthropic.com] WITH IDENTITY = 'anthropic', SECRET = 'sk-ant-default...';

-- a single tenant database: its models use the tenant's own key instead
USE TenantDb;
CREATE DATABASE SCOPED CREDENTIAL [https://api.anthropic.com] WITH IDENTITY = 'anthropic', SECRET = 'sk-ant-tenant...';

A credential that matches but carries no secret#

An AI call always consumes the winning credential’s secret — there is no identity-only authentication mode for an AI endpoint. When the winning credential carries no secret, the call fails with a clear error naming the credential (never any key material): “The credential ‘…’ carries no secret, but its authentication mode requires one.”

A credential legally holds no secret in two ordinary situations: it was created identity-only (SECRET is optional), or a later ALTER restated the identity and omitted SECRET, which clears the stored secret. Because a database scoped credential wins outright over the server-level default, an identity-only database scoped credential also shadows a working cluster key for its database:

-- the cluster default works everywhere:
CREATE CREDENTIAL [https://api.anthropic.com] WITH IDENTITY = 'anthropic', SECRET = 'sk-ant-default...';

USE TenantDb;
-- legal, but for an AI endpoint always a misconfiguration: no secret to send.
CREATE DATABASE SCOPED CREDENTIAL [https://api.anthropic.com] WITH IDENTITY = 'Managed Identity';

-- this database's AI calls now fail, naming the identity-only credential that
-- outranks the working cluster default; other databases are unaffected.
SELECT AI_SUMMARIZE(review_text) FROM hotel_reviews;

-- recovery is one statement, picked up by the very next call (resolution is
-- per call and never cached):
DROP DATABASE SCOPED CREDENTIAL [https://api.anthropic.com];

The same error after an ALTER on the endpoint credential means the ALTER did not restate SECRET and has cleared the key; ALTER CREDENTIAL [<endpoint>] WITH IDENTITY = '<identity>', SECRET = '<api-key>' restores it.

Example#

Registers an Anthropic and an Azure OpenAI model, creates their credentials, then summarizes with the bound model.

EXEC qua_add_external_ai_model
     @name       = N'MyClaude',
     @api_format = N'Anthropic',
     @location   = N'https://api.anthropic.com/v1/messages',
     @model      = N'claude-sonnet-5',
     @parameters = N'{"max_tokens":1024}';
CREATE CREDENTIAL [https://api.anthropic.com/v1/messages] WITH IDENTITY = 'anthropic', SECRET = '<api-key>';

EXEC qua_add_external_ai_model
     @name       = N'MyAzureGpt',
     @api_format = N'Azure OpenAI',
     @location   = N'https://myresource.cognitiveservices.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-06-01',
     @model      = N'gpt-4o';
CREATE CREDENTIAL [https://myresource.cognitiveservices.azure.com] WITH IDENTITY = 'azure', SECRET = '<api-key>';

EXEC sys.sp_set_session_context N'ai_default_model', N'MyClaude';

SELECT AI_SUMMARIZE(review_text) AS summary FROM hotel_reviews;

See Also#