@@ -876,6 +876,7 @@ CREATE TABLE #ai_configuration
876876 AI_Database_Scoped_Credential_Name NVARCHAR (500 ),
877877 AI_System_Prompt_Override NVARCHAR (4000 ),
878878 AI_Parameters NVARCHAR (4000 ),
879+ Payload_Template_Override NVARCHAR (4000 ),
879880 Timeout_Seconds TINYINT ,
880881 DefaultModel BIT DEFAULT 0 );
881882
@@ -885,17 +886,19 @@ DECLARE
885886 @AIConfigTableName NVARCHAR (258 ) = CASE WHEN @AIConfig IS NULL THEN NULL ELSE PARSENAME (@AIConfig, 1 ) END ,
886887 @AISystemPrompt NVARCHAR (4000 ),
887888 @AIParameters NVARCHAR (4000 ),
889+ @AIPayloadTemplate NVARCHAR (MAX ),
888890 @AITimeoutSeconds TINYINT ,
889891 @AIAdviceText NVARCHAR (MAX );
890892
891893
892894IF @AIConfig IS NOT NULL
893895BEGIN
894896 RAISERROR (N ' Reading values from AI Configuration Table' , 0 , 1 ) WITH NOWAIT ;
895- SET @config_sql = N ' INSERT INTO #ai_configuration SELECT Id, AI_Model, AI_URL, AI_Database_Scoped_Credential_Name, AI_System_Prompt_Override, AI_Parameters, Timeout_Seconds, DefaultModel FROM '
897+ SET @config_sql = N' INSERT INTO #ai_configuration (Id, AI_Model, AI_URL, AI_Database_Scoped_Credential_Name, AI_System_Prompt_Override, AI_Parameters, Payload_Template_Override, Timeout_Seconds, DefaultModel)
898+ SELECT Id, AI_Model, AI_URL, AI_Database_Scoped_Credential_Name, AI_System_Prompt_Override, AI_Parameters, Payload_Template_Override, Timeout_Seconds, DefaultModel FROM '
896899 + CASE WHEN @AIConfigDatabaseName IS NOT NULL THEN (QUOTENAME (@AIConfigDatabaseName) + N ' .' ) ELSE N ' ' END
897900 + CASE WHEN @AIConfigSchemaName IS NOT NULL THEN (QUOTENAME (@AIConfigSchemaName) + N ' .' ) ELSE N ' ' END
898- + QUOTENAME (@AIConfigTableName) + N ' WHERE DefaultModel = 1 OR AI_Model = @AIModel ; ' ;
901+ + QUOTENAME (@AIConfigTableName) + N ' WHERE (@AIModel IS NULL AND DefaultModel = 1) OR @AIModel IN (AI_Model, Nickname) ; ' ;
899902 EXEC sp_executesql @config_sql, N ' @AIModel NVARCHAR(100)' , @AIModel;
900903END ;
901904
@@ -906,6 +909,9 @@ IF @AI > 0
906909
907910 SELECT @ExpertMode = 1 , @KeepCRLF = 1 ;
908911
912+ IF @Debug = 2
913+ SELECT N ' ai_configuration' AS TableLabel, * FROM #ai_configuration;
914+
909915 IF @AI = 1 AND NOT EXISTS (SELECT * FROM sys .all_objects WHERE name = ' sp_invoke_external_rest_endpoint' )
910916 BEGIN
911917 /* If someone was ambitious and they wanted to code a drop-in replacement for that stored proc,
@@ -916,38 +922,62 @@ IF @AI > 0
916922 END
917923
918924 IF @AIModel IS NULL
925+ /* Check the config table */
919926 SELECT TOP 1 @AIModel = AI_Model, @AIURL = AI_URL,
920927 @AICredential = AI_Database_Scoped_Credential_Name,
921928 @AISystemPrompt = AI_System_Prompt_Override,
922929 @AIParameters = AI_Parameters,
923- @AITimeoutSeconds = COALESCE (Timeout_Seconds, 30 )
930+ @AITimeoutSeconds = COALESCE (Timeout_Seconds, 30 ),
931+ @AIPayloadTemplate = Payload_Template_Override
924932 FROM #ai_configuration
925933 WHERE DefaultModel = 1
926934 ORDER BY Id;
927935 ELSE
928- SELECT TOP 1 @AIURL = COALESCE (@AIURL, AI_URL),
936+ SELECT TOP 1 @AIModel = AI_Model,
937+ @AIURL = COALESCE (@AIURL, AI_URL),
929938 @AICredential = COALESCE (@AICredential, AI_Database_Scoped_Credential_Name),
930939 @AISystemPrompt = AI_System_Prompt_Override,
931940 @AIParameters = AI_Parameters,
932- @AITimeoutSeconds = COALESCE (Timeout_Seconds, 30 )
941+ @AITimeoutSeconds = COALESCE (Timeout_Seconds, 30 ),
942+ @AIPayloadTemplate = Payload_Template_Override
933943 FROM #ai_configuration
934- WHERE AI_Model = @AIModel
935944 ORDER BY Id;
936945
946+ IF @AIModel IS NULL
947+ SET @AIModel = N ' gpt-4.1-mini' ;
948+
937949 IF @AIURL IS NULL OR @AIURL NOT LIKE N ' http%'
938950 SET @AIURL = N ' https://api.openai.com/v1/chat/completions' ;
939951
940952 IF @AICredential IS NULL OR @AICredential NOT LIKE ' http%'
941953 SET @AICredential = N ' https://api.openai.com' ;
942954
955+ IF @AITimeoutSeconds IS NULL OR @AITimeoutSeconds < 1 OR @AITimeoutSeconds > 230
956+ SET @AITimeoutSeconds = 30 ;
957+
943958 IF @AISystemPrompt IS NULL OR @AISystemPrompt = N ' '
944959 SET @AISystemPrompt = N ' You are a very senior database developer working with Microsoft SQL Server and Azure SQL DB. You focus on real-world, actionable advice that will make a big difference, quickly. You value everyone'' s time, and while you are friendly and courteous, you do not waste time with pleasantries or emoji because you work in a fast-paced corporate environment.
945960
946961 You have a query that isn' ' t performing to end user expectations. You have been tasked with making serious improvements to it, quickly. You are not allowed to change server-level settings or make frivolous suggestions like updating statistics. Instead, you need to focus on query changes or index changes.
947962
948963 Do not offer followup options: the customer can only contact you once, so include all necessary information, tasks, and scripts in your initial reply. Render your output in Markdown, as it will be shown in plain text to the customer.' ;
949964
950- IF @Debug IN (1 ,2 ) OR (@AI = 1 AND (@AIModel IS NULL OR @AIURL IS NULL OR @AISystemPrompt IS NULL OR @AICredential IS NULL ))
965+ IF @AIPayloadTemplate IS NULL
966+ SET @AIPayloadTemplate = N' {
967+ "model": "@AIModel",
968+ "messages": [
969+ {
970+ "role": "system",
971+ "content": "@AISystemPrompt"
972+ },
973+ {
974+ "role": "user",
975+ "content": "@CurrentAIPrompt"
976+ }
977+ ]
978+ }' ;
979+
980+ IF @Debug = 2 OR (@AI = 1 AND (@AIModel IS NULL OR @AIURL IS NULL OR @AISystemPrompt IS NULL OR @AICredential IS NULL OR @AIPayloadTemplate IS NULL ))
951981 BEGIN
952982 PRINT N ' @AIModel: ' ;
953983 PRINT @AIModel;
@@ -961,9 +991,11 @@ IF @AI > 0
961991 PRINT @AITimeoutSeconds;
962992 PRINT N ' @AISystemPrompt: ' ;
963993 PRINT @AISystemPrompt;
994+ PRINT N ' @AIPayloadTemplate: ' ;
995+ PRINT @AIPayloadTemplate;
964996 END ;
965997
966- IF @AI = 1 AND (@AIModel IS NULL OR @AIURL IS NULL OR @AISystemPrompt IS NULL OR @AICredential IS NULL )
998+ IF @AI = 1 AND (@AIModel IS NULL OR @AIURL IS NULL OR @AISystemPrompt IS NULL OR @AICredential IS NULL OR @AIPayloadTemplate IS NULL )
967999 BEGIN
9681000 RAISERROR (' @AI is set to 1, but not all of the necessary configuration is included.' ,12 ,1 );
9691001 RETURN ;
@@ -5175,30 +5207,15 @@ Thank you.'
51755207 SET @AIResponseJSON = NULL ;
51765208 SET @AIResponse = NULL ;
51775209
5178- /* Build the JSON payload for the API call. Escape special characters in the prompt for JSON: */
5179- SET @CurrentAIPrompt = REPLACE (@CurrentAIPrompt, ' \' , ' \\' );
5180- SET @CurrentAIPrompt = REPLACE (@CurrentAIPrompt, ' "' , ' \"' );
5181- SET @CurrentAIPrompt = REPLACE (@CurrentAIPrompt, CHAR (13 ), ' \r' );
5182- SET @CurrentAIPrompt = REPLACE (@CurrentAIPrompt, CHAR (10 ), ' \n' );
5183- SET @CurrentAIPrompt = REPLACE (@CurrentAIPrompt, CHAR (9 ), ' \t' );
5184-
5185- /* Build payload based on API type (OpenAI-compatible format works for most providers) */
5186- SET @AIPayload = N' {
5187- "model": "' + @AIModel + N' ",
5188- "messages": [
5189- {
5190- "role": "system",
5191- "content": "' + REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (@AISystemPrompt, ' \' , ' \\' ), ' "' , ' \"' ), CHAR (13 ), ' \r' ), CHAR (10 ), ' \n' ), CHAR (9 ), ' \t' ) + N' "
5192- },
5193- {
5194- "role": "user",
5195- "content": "' + @CurrentAIPrompt + N' "
5196- }
5197- ]
5198- }' ;
5210+ /* Build payload using the template. */
5211+ SET @AIPayload = REPLACE (@AIPayloadTemplate, N ' @AIModel' , @AIModel);
5212+ SET @AIPayload = REPLACE (@AIPayload, N ' @AISystemPrompt' , REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (@AISystemPrompt, ' \' , ' \\' ), ' "' , ' \"' ), CHAR (13 ), ' \r' ), CHAR (10 ), ' \n' ), CHAR (9 ), ' \t' ));
5213+ SET @AIPayload = REPLACE (@AIPayload, N ' @CurrentAIPrompt' , REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (@CurrentAIPrompt, ' \' , ' \\' ), ' "' , ' \"' ), CHAR (13 ), ' \r' ), CHAR (10 ), ' \n' ), CHAR (9 ), ' \t' ));
5214+ -- SET @AIPayload = REPLACE(@AIPayload, N'@CurrentAIPrompt', @CurrentAIPrompt);
51995215
52005216 IF @Debug = 2
52015217 BEGIN
5218+ SELECT @AIPayload AS AIPayload;
52025219 RAISERROR (' AI Payload (first 4000 chars):' , 0 , 1 ) WITH NOWAIT ;
52035220 PRINT LEFT (@AIPayload, 4000 );
52045221 END ;
@@ -5225,22 +5242,22 @@ Thank you.'
52255242 OpenAI format: {"choices":[{"message":{"content":"..."}}]} */
52265243 IF @AIResponseJSON IS NOT NULL
52275244 BEGIN
5245+ /* Try OpenAI ChatGPT chat completion by default: */
52285246 SET @AIAdviceText = (SELECT c .Content
52295247 FROM OPENJSON (@AIResponseJSON, ' $.result.choices' )
52305248 WITH (
52315249 Content nvarchar (max ) ' $.message.content'
52325250 ) AS c);
52335251
5234- IF @Debug = 2
5235- BEGIN
5236- SELECT ' @AIResponseJSON parsed with OPENJSON:' AS Label, c .Content
5237- FROM OPENJSON (@AIResponseJSON, ' $.response.result.choices' )
5238- WITH (
5239- Content nvarchar (max ) ' $.message.content'
5240- ) AS c;
5241- END ;
5252+ /* No data? How about Google Gemini: */
5253+ IF @AIAdviceText IS NULL
5254+ SET @AIAdviceText = (SELECT TOP 1 p.[text]
5255+ FROM OPENJSON (@AIResponseJSON, ' $.result.candidates' ) AS cand
5256+ CROSS APPLY OPENJSON (cand .value , ' $.content.parts' )
5257+ WITH ([text] nvarchar (max ) ' $.text' ) AS p);
52425258
5243- /* If we couldn't parse it, check for error codes */
5259+
5260+ /* If we still couldn't parse it, check for error codes */
52445261 IF @AIAdviceText IS NULL
52455262 BEGIN
52465263 DECLARE @ErrorMessage NVARCHAR (MAX );
@@ -5512,11 +5529,14 @@ BEGIN
55125529 QueryPlan AS [Query Plan],
55135530 missing_indexes AS [Missing Indexes],
55145531 implicit_conversion_info AS [Implicit Conversion Info],
5515- cached_execution_parameters AS [Cached Execution Parameters],
5532+ cached_execution_parameters AS [Cached Execution Parameters], '
5533+ + CASE WHEN @AI = 2 THEN N'
55165534 [AI Prompt] = (
5517- SELECT (@AISystemPrompt + NCHAR(13) + NCHAR(10) + NCHAR(13) + NCHAR(10) + ai_prompt) AS [text()] FOR XML PATH('' ai_prompt'' ), TYPE),
5535+ SELECT (@AISystemPrompt + NCHAR(13) + NCHAR(10) + NCHAR(13) + NCHAR(10) + ai_prompt) AS [text()] FOR XML PATH('' ai_prompt'' ), TYPE),' ELSE N ' ' END
5536+ + CASE WHEN @AI = 1 THEN N'
55185537 [AI Advice] = CASE WHEN ai_advice IS NULL THEN NULL ELSE (
5519- SELECT ai_advice AS [text()] FOR XML PATH('' ai_advice'' ), TYPE) END, ' + @nl;
5538+ SELECT ai_advice AS [text()] FOR XML PATH('' ai_advice'' ), TYPE) END, ' ELSE N ' ' END
5539+ + @nl;
55205540
55215541 IF @ExpertMode = 2 /* Opserver */
55225542 BEGIN
@@ -5645,9 +5665,13 @@ BEGIN
56455665 QueryPlanHash AS [Query Plan Hash],
56465666 StatementStartOffset,
56475667 StatementEndOffset,
5648- PlanGenerationNum,
5668+ PlanGenerationNum, '
5669+ + CASE WHEN @AI <> 2 THEN N'
5670+ [AI Prompt] = (
5671+ SELECT (@AISystemPrompt + NCHAR(13) + NCHAR(10) + NCHAR(13) + NCHAR(10) + ai_prompt) AS [text()] FOR XML PATH('' ai_prompt'' ), TYPE),' ELSE N ' ' END
5672+ + CASE WHEN @AI = 1 THEN N'
56495673 [AI Raw Response] = CASE WHEN ai_raw_response IS NULL THEN NULL ELSE (
5650- SELECT ai_raw_response AS [text()] FOR XML PATH('' ai_raw_response'' ), TYPE) END,
5674+ SELECT ai_raw_response AS [text()] FOR XML PATH('' ai_raw_response'' ), TYPE) END, ' ELSE N ' ' END + N '
56515675 [Remove Plan Handle From Cache],
56525676 [Remove SQL Handle From Cache]' ;
56535677END ;
0 commit comments