Skip to content

Commit 04100a6

Browse files
authored
Merge pull request #3762 from BrentOzarULTD/3669_sp_BlitzCache_AI_2
#3669 sp_BlitzCache AI pt 2
2 parents aea51c9 + 7b81395 commit 04100a6

File tree

1 file changed

+67
-43
lines changed

1 file changed

+67
-43
lines changed

sp_BlitzCache.sql

Lines changed: 67 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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

892894
IF @AIConfig IS NOT NULL
893895
BEGIN
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;
900903
END;
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]';
56535677
END;

0 commit comments

Comments
 (0)