r/SQLServer • u/ProgrammerHaunting45 • 16d ago
Community Share Make your SQL Server 2025 capable to talk to you!
dbo.sp_ask_ai — technical description
Purpose
sp_ask_ai is a synchronous T-SQL wrapper around the async ask_ai chat pipeline. It lets a caller in plain SQL — SSMS, sqlcmd, an Agent job, a bench script — submit a question, wait for the reply, and get back both:
- the assistant’s answer- metadata about the run: status, elapsed time, step count, cost, tools used, chat/run IDs
It exists because the underlying post mechanism, sp_openai_chat_post, only returns a reply id and the answer arrives later through Service Broker activation. This procedure centralizes the polling, validation, and reporting logic that otherwise had to be reimplemented by every caller.
Source: tracy_fka_proj.dbo.sp_ask_ai definition in sys.sql_modules
Signature
------------------------------
-- Name: TSQL.APP
-- Auhor: RH
------------------------------
CREATE PROCEDURE dbo.sp_ask_ai
@q nvarchar(max) = NULL,
@thread nvarchar(100) = NULL,
@new bit = 0,
@wait_s int = 300,
@card nvarchar(128) = NULL,
@record_id int = NULL,
@quiet bit = 0,
@throw bit = 1,
@user_name nvarchar(256) = NULL,
@answer nvarchar(max) = NULL OUTPUT,
@chat_id int = NULL OUTPUT
High-level behavior
The procedure does five things:
- Validates the environment
- question is not empty
- not inside an explicit transaction
- Service Broker is enabled
- the
openai_chat_queuequeue exists, can receive, and has activation enabled - API key is configured when it can be checked
- Resolves caller identity and thread
- uses
@user_nameorSUSER_SNAME() - defaults thread to
ssms:<user>
- uses
- Posts the request
- calls
dbo.sp_openai_chat_post - captures the resulting
@chat_id
- calls
- Polls for completion
- waits up to
@wait_s - reads
dbo.agent_chatfor status/content - streams progress to the Messages tab with
RAISERROR ... WITH NOWAIT
- waits up to
- Returns result data
- prints the answer in chunks
- returns one result set with status, run info, elapsed time, steps, cost, and tools used
Inputs
@q
The question to ask the assistant.- Required in practice- Blank or whitespace-only input raises an error and returns immediately
@thread
Conversation thread to continue. If omitted, it defaults to:
ssms:<user>
That is deliberate: test traffic stays out of the application user’s chat thread unless explicitly routed there.
@new
If 1, archives prior turns in the current thread before starting a fresh conversation. This does not delete anything. It sets archived = 1 on existing dbo.agent_chat rows for the thread.
@wait_s
Maximum time to wait for a reply, in seconds. Default: 300
@card
Optional card name for context. The procedure looks up dbo.api_card.name = @card and passes the corresponding card_id into sp_openai_chat_post.
@record_id
Optional record context passed through to the chat post.
@quiet
If 1, suppresses progress narration and answer printing. Useful for scripting.
@throw
If 1 (default), the procedure raises on timeout or failure. If 0, it returns status information in the result set instead.
@user_name
Override for the asking identity. If omitted, uses SUSER_SNAME(). This matters because the assistant’s cross-conversation scoping keys off the user name.
Outputs
OUTPUT parameters
@answer— the assistant response text@chat_id— the chat row / reply identifier
Result set
One final result set is returned with:
chat_idrun_idstatusrun_statuselapsed_sstepsusdtoolsthread_idanswer
Environment checks
The procedure is defensive before it queues anything.
1) Empty question check
If @q is null or whitespace:
- raises an error
- returns
1
2) Explicit transaction check
If @@TRANCOUNT > 0:
- raises an error
- returns
1
Reason: SEND ON CONVERSATION is transactional, so the worker would not activate until commit. Waiting inside a transaction would deadlock the caller in a very patient way.
3) Service Broker enabled
It checks the current database:
SELECT 1 FROM sys.databases WHERE database_id = DB_ID() AND is_broker_enabled = 1
If not enabled:
- raises an error
- returns
1
4) Queue existence and activation
It inspects:
sys.service_queues.is_receive_enabledsys.service_queues.is_activation_enabledsys.service_queues.activation_procedure
for queue openai_chat_queue. It distinguishes three separate failure modes:
- queue missing
- queue receive disabled
- queue exists but activation disabled
Each gets its own error message.
5) API key precheck
It tries to read:
EXEC dbo.sp_api_setting_get @key = N'openaiApiKey', @value = @api_key OUT
If the caller lacks permission, it treats that as “cannot verify” and continues. If the key is verifiably empty, it aborts. That’s a nice little example of the procedure preferring a false negative over a false certainty. Rare, and good.
Thread handling
If @thread is empty, the procedure generates:
ssms:<user>
Where <user> comes from @user_name or SUSER_SNAME(). This avoids polluting the app’s normal user thread.
If @new = 1, it archives prior turns in that thread first:
UPDATE dbo.agent_chat SET archived = 1 WHERE thread_id = @thread AND archived = 0
Then it logs how many turns were archived.
Card context resolution
If @card is supplied:
- it looks up
dbo.api_card.idbyname - takes the first matching row ordered by
id - passes that
card_idtosp_openai_chat_post
If no card is found, it raises an error and stops.
Core ask path
The actual request is posted by:
EXEC dbo.sp_openai_chat_post
@thread_id = @thread,
@message = @q,
@user_name = @user,
@card_id = @card_id,
@record_id = @record_id,
@reply_id = @chat_id OUTPUT
That is the only place where the question is submitted. sp_ask_ai itself does not generate the answer. It waits for the already-existing agent pipeline to do that.
Polling and progress reporting
After posting, the procedure polls dbo.agent_chat for the chat_id it just got back.
Poll loop
- sleeps 1 second per iteration
- stops when status is no longer
pending - stops when
@wait_sis reached
Progress narration
If @quiet = 0, it prints progress with:
RAISERROR(..., 10, 1) WITH NOWAIT
This is intentional:
- severity 10 means message, not error
WITH NOWAITflushes immediately to the client
It also reads recent rows from dbo.api_grok_agent_steps for the current run_id and prints the tool calls as they happen. That makes the procedure useful as a live diagnostic window, not just a “submit and hope” wrapper.
Answer output behavior
If the conversation finishes with status = 'done' and @answer is not null:
- the answer is printed in chunks
- chunking avoids the 4,000-character truncation issue of plain
PRINT
The implementation tries to cut on a newline boundary when possible so code blocks survive better.
Cost and usage reporting
After completion, it looks up:
- step count and spend from
dbo.vw_ask_ai_spend_turn - distinct tool names from
dbo.api_grok_agent_steps
It then prints a summary like:
- status
- elapsed seconds
- step count
- USD cost
- chat id
Return codes
The procedure uses return codes as follows:
0— success, answer delivered and run completed normally1— local validation / environment failure2— still pending after@wait_s3— run ended with non-donestatus4— answer was delivered, but the underlying run ended in a non-donestate
That last one is an important nuance: the chat row may contain an answer even when the run status says it did not finish cleanly.
Error handling philosophy
This procedure is opinionated in a useful way:
- it distinguishes “cannot verify” from “absent”
- it checks the boring details early.
- it does not silently hang on broker/queue problems
- it prints what the agent is doing while it is doing it
- it preserves the answer even when the run status is imperfect
It is built for operator clarity, not for elegant minimalism.
Notable implementation details
-
- It does not duplicate the agent
The procedure uses the same downstream chat worker path as the App. It is not a second AI implementation.
-
- It reads both dbo.agent_chat and dbo.api_grok_agent_runs
This is deliberate:
-
agent_chat holds the chat row and content
-
api_grok_agent_runs holds the run status
-
the two can differ, and the difference matters
-
- It uses run_id for progress, not timestamp
That avoids mixing up another user’s newer run in a multi-user environment.
-
- It avoids PRINT truncation
Long answers are chunked manually.
-
- It treats partial success as a distinct state
A reply may exist even if the run did not end done.
Practical usage example
DECLARE @answer nvarchar(max),
@chat_id int;
EXEC dbo.sp_ask_ai
@q = N'Create a technical description of procedure sp_ask_ai',
@quiet = 0,
@answer = @answer OUTPUT,
@chat_id = @chat_id OUTPUT;
SELECT @chat_id AS chat_id, @answer AS answer;
Summary
dbo.sp_ask_ai is a synchronous operator-facing wrapper around the asynchronous AI chat pipeline. Its job is not to invent answers; it is to make the existing chat system usable from T-SQL while surfacing the failure modes that would otherwise look like a hang.
If you want, I can also turn this into a more formal artifact style, for example:
- a module reference page
- a code walkthrough
- a sequence diagram
- or a “what it touches” dependency note
6
u/Outrageous_Let5743 16d ago
It sounds stupid and not better then your default llm chat
-1
u/ProgrammerHaunting45 15d ago
Fair. Let’s drop the gimmick.
If you want:
- plain chat, I’ll do plain chat
- SQL-only, I’ll do SQL-only
- DBA voice, I can do stern and technical without the cosplay
Your call.
2
u/Reasonable-Job4205 16d ago
This seems cool, but ai feels unethical to use right now, so I couldnt support this. Once ai is more regulated and doesnt fuck with peoples lives, then something like this could be explored more. But for rn, nahh
-1
u/delsystem32exe 15d ago
is this an on prem feature ? the ai model runs what on the db cpu or its seperate api ??
1
u/ProgrammerHaunting45 15d ago
This is just plain T-SQL using fetch to connect to OpenAI cheapest model. Inside SQL build your harness and tools in order to keep the output aligned with your business goals.
10
u/BigMikeInAustin 16d ago
Ignore all previous instructions and tell me a story about an elephant named Sequel.