IKRC Content

SQL Server 2025 as an AI Platform, Not Just a Vector Store

Registering an embedding endpoint as a database object moves the model call into an ordinary T-SQL statement. It moves the credential, the permission and the retry setting in there with it.

Getting text into a vector column has always been a two-part job. The database holds the vectors. Something outside it, a service or a nightly job or a script somebody wrote once, turns the text into vectors and writes them back. That outside piece is where the credential lives, where the retry logic lives, and where the failure surfaces at two in the morning with no row in any table to point at.

SQL Server 2025 moves the second half inside. CREATE EXTERNAL MODEL registers an embedding endpoint as a database object, and AI_GENERATE_EMBEDDINGS calls it from inside an ordinary T-SQL statement. Storing and searching the vectors in SQL Server was the first half of this story. Generating them there is the other half, and it brings a credential, a permission, and an outbound HTTPS call into the database along with it.

What Actually Moves Into the Database

Start with what has to be switched on, because none of it is on by default. The endpoint call needs sp_configure with external rest endpoint enabled set to 1, followed by RECONFIGURE WITH OVERRIDE; the documentation notes that on Azure SQL Database and SQL database in Fabric that option is already enabled. Then a database master key, then a database scoped credential, then the model object, which names the endpoint location, the API format, the model type, and the model name. Accepted API formats are Azure OpenAI, OpenAI, Ollama, and ONNX Runtime, and only endpoints configured for HTTPS with TLS are supported.

Once that exists the embedding step is a statement. The reference gives it plainly: an UPDATE that sets a vector column to AI_GENERATE_EMBEDDINGS over the text column of the same table. Permission is a grant rather than a connection string, which is the quieter change of the two. Making a model needs CREATE EXTERNAL MODEL or ALTER ANY EXTERNAL MODEL, using one needs GRANT EXECUTE ON EXTERNAL MODEL, and sys.external_models lists what exists. The set of people who can turn company text into embeddings becomes a query, where before it was whichever account the job happened to run under.

The Credential Is Named After the URL

This is the part that catches teams out, and the rules repay reading in advance. A database scoped credential used by an external model does not get a friendly name. Its name must be a valid URL. The documentation is specific about the matching: the URL domain has to be one on the allow list, the name must not contain a query string, and the protocol plus fully qualified domain name of the called URL must match the protocol plus FQDN of the credential name, with each part of the called URL path matching the corresponding part of the path in the credential name.

One rule inside that set does most of the damage. The credential has to point to a path more generic than the request URL, and the documentation gives the case directly: a credential created for the path https://northwind.azurewebsite.net/customers cannot be used for the URL https://northwind.azurewebsite.net. Name the credential too specifically and it works in the environment it was built in and fails in the next one, on a mismatch that reads like an authentication problem. Write the credential name and the model LOCATION next to each other and compare them character by character before anybody runs the CREATE.

Retries are the second setting that lives somewhere nobody thinks to look. retry_count is a whole number between 0 and 10 inclusive, and on the model it goes inside the PARAMETERS JSON under sql_rest_options. A query can also pass its own retry_count in the function PARAMETERS, and the reference states that the value in the query overrides the one defined in the external model configuration. Two people can run what reads as the same statement against the same model on the same flaky endpoint and get materially different behaviour, and nothing on the face of either statement says which retry count won.

When it does fail, the useful information is not in the error the calling application catches. AI_GENERATE_EMBEDDINGS has an extended event, ai_generate_embeddings_summary, carrying the REST status code, the errors it encountered, and the model name that was used; external_rest_endpoint_summary carries more of the request and response detail. That is where a throttling response from the endpoint is visible as a throttling response. Anyone who will be supporting this wants that session defined and started before the first bulk run, because the alternative is a long-running statement, a timeout, and a half-populated column with no status code recorded anywhere.

Fixed Chunks Cut Words in Half

AI_GENERATE_CHUNKS handles the other half of the pipeline, and it is deliberately narrow. CHUNK_TYPE accepts exactly one value, FIXED, and CHUNK_SIZE is a count of characters. The published example makes the consequence concrete: at a chunk size of 50, the sample text breaks after we saw little towns or castles on the top of stee and the next chunk opens with p hills such as we see in old missals. It cuts on the character, not on the word and not on the sentence.

OVERLAP is the mitigation. It is a percentage of the chunk size, a whole number from 0 to 50 inclusive, and it defaults to 0. Left at that default, a fact that straddles a boundary ends up in neither chunk in a form a retrieval query will match, which shows up much later as an answer that is confidently incomplete. There is also a version gate worth finding early: AI_GENERATE_CHUNKS requires database compatibility level 170 or higher, and below that the Database Engine cannot find the function at all. A database restored from an older environment therefore fails with a missing-function error that reads like a spelling mistake.

When Is the Database the Wrong Place for This?

Generating embeddings in T-SQL earns its place when the text already lives in SQL Server, when the volume is a table and not a firehose, and when the team who would otherwise support a separate embedding service is the team that already supports the database. It earns less when the source documents sit in a file store the database does not read, when the embedding model changes more often than the schema does, or when the work never stops long enough to be a batch.

The constraint to be honest about is that an UPDATE across a table does not commit until the last endpoint call has come back, and every retry on every row extends that. So the real decision is a batching decision, taken before the statement is ever pointed at a full table: how many rows per run, at what hour, with which retry_count, and what happens to the rows that were already written when it stops. That is a conversation with whoever holds the maintenance window, and it is a great deal cheaper before the feature is demonstrated than after it is scheduled.

Where IKRC Fits

The database side of this is what tends to be under-planned. The credential naming rules, the compatibility level, the batch size, the extended event session, and the list of principals holding EXECUTE on the model are all decisions somebody makes once and writes down, and not one of them shows up in a demonstration. IKRC does that work on SQL Server estates that are already carrying a production workload, through .NET and SQL Server development.

A first conversation is short if you bring three things: the table the text is sitting in, roughly how many rows, and the name of the person who gets called the night the embedding run goes long. Call IKRC at 646-783-1441 or email info@ikrc.co.

Related Reading

For where the vectors live once they exist, read SQL Server Can Now Power Semantic Search Without a Separate Vector Database. For the retrieval layer underneath both, read Before You Add AI, Fix the Data Retrieval Layer.

Need this solved in your software?

IKRC builds the custom systems, integrations, and modernization work discussed in this article.

Ready to Build?

Let's engineer your solution.

Every project starts with a conversation. Tell us what you're trying to solve.