Création d’un générateur de requêtes T-SQL à l’aide de Microsoft.Extensions.AI

Obtenez des informations plus intelligentes sur les données à partir de vos propres tables : le package Microsoft.Extensions.AI nous permet de créer des requêtes IA sur des bases de données MS-SQL.
Dans cet article, je vais démontrer une solution efficace pour créer des requêtes T-SQL à l’aide du package Microsoft.Extensions.AI NuGet basé sur les tables et les colonnes des bases de données MS-SQL, transformant la structure de la table en un paramètre système permettant à l’IA de créer des sélections basées sur celle-ci. Et cela donne quelques idées sur ce qu’il faut demander.

Image créée avec l’IA
Comment ça marche
L’application lit les serveurs de base de données Microsoft locaux sur votre ordinateur local, vous permettant d’en sélectionner un dans la liste.
Les tables sont répertoriées sur un ListView, vous permettant de choisir les tables que vous souhaitez utiliser dans la sélection. Sous le tableau se trouve une liste des colonnes disponibles qui seront utilisées par l’IA pour créer la requête.
Après avoir sélectionné les tables souhaitées et en cliquant sur l’invite, une requête est automatiquement adressée à l’IA avec l’invite : « Dites-moi 5 choses auxquelles vous pouvez répondre en fonction de ces données. »
Ainsi, vous aurez quelques idées sur quoi écrire une invite dans les données de requête AI.
Ci-dessous l’utilisation de base, en sélectionnant seulement quelques tableaux :

Image de l’application principale
Dans l’image ci-dessus, nous pouvons voir les suggestions de l’IA concernant les tableaux/colonnes sélectionnés.
Une analyse complète
Vous trouverez ci-dessous le résultat de l’invite « Générer une requête SQL qui démontre les revenus par région ».

En appuyant sur le bouton « Afficher la grille », une nouvelle fenêtre s’ouvrira avec une grille avec les données :

Après cela, il est possible de sélectionner les données et de les formater dans Microsoft Excel :
| Région | Revenu total |
|---|---|
| Allemagne | 5.939.763,50$ |
| France | 9.136.704,47$ |
| Nord-est | 9.269.741,31$ |
| Royaume-Uni | 9.506.447,59 $ |
| Central | 10.568.959,19$ |
| Sud-est | 10.570.580,42$ |
| Australie | 12.197.515,53$ |
| Nord-Ouest | 20.802.600,78$ |
| Canada | 21.501.812,46$ |
| Sud-Ouest | 31.213.459,58$ |
Astuce : appuyer sur F10 sur ListView entraînera l’activation ou la désactivation de tous les éléments.
Le code source
La logique du code est assez simple. Nous chargeons toutes les bases de données dans une DropDownList à partir de localhost et déclenchons leur chargement dans un ListView.
1. private void LoadDBs()
2. {
3. string connectionString = "Server=localhost;Integrated Security=true;TrustServerCertificate=True;";
4. string query = "SELECT name FROM sys.databases";
5.
6. using var connection = new SqlConnection(connectionString);
7.
8. using var command = new SqlCommand(query, connection);
9. connection.Open();
10. using var reader = command.ExecuteReader();
11.
12. while (reader.Read())
13. {
14. this.radDropDownList1.Items.Add(reader["name"].ToString());
15. }
16.
17. this.radDropDownList1.SelectedIndexChanged += RadDropDownList1_SelectedIndexChanged;
18. }
19.
20. private void RadDropDownList1_SelectedIndexChanged(object sender, Telerik.WinControls.UI.Data.PositionChangedEventArgs e)
21. {
22. string selectedDatabase = this.radDropDownList1.SelectedItem.Text;
23. string connectionString = $"Server=localhost;Database={selectedDatabase};Integrated Security=true;TrustServerCertificate=True;";
24. string query = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_SCHEMA, TABLE_NAME";
25.
26. using var connection = new SqlConnection(connectionString);
27. using var command = new SqlCommand(query, connection);
28. connection.Open();
29. using var reader = command.ExecuteReader();
30.
31. this.radListView1.Items.Clear();
32. while (reader.Read())
33. {
34. var schema = reader["TABLE_SCHEMA"].ToString();
35. var tableName = reader["TABLE_NAME"].ToString();
36. var recordCount = GetTableRecordCount(connectionString, schema + "." + tableName);
37. var item = new ListViewDataItem();
38. item[0] = schema;
39. item[1] = tableName;
40. item[2] = recordCount.ToString();
41. this.radListView1.Items.Add(item);
42. }
43. this.radListView1.SelectedIndex = 0;
44. }
Lorsque l’utilisateur vérifie une table, il déclenche un événement pour charger toutes les colonnes dans un autre ListView (cela aide l’utilisateur à choisir une colonne pour la requête), remplit la propriété SystemPrompt avec les informations de colonne pour l’IA et réinitialise le Suggestion drapeau.
1. private void RadListView1_ItemCheckedChanged(object sender, ListViewItemEventArgs e)
2. {
3. SystemPrompt = GetSystemPrompt();
4. Suggestion = false;
5. }
6.
7. private string GetSystemPrompt()
8. {
9. this.radLabel2.Text = "";
10.
11. var sb = new StringBuilder();
12.
13. sb.AppendLine("You are a data analyst and will generate T-SQL queries from this structure, response based on the database strucuture that you have access that are:");
14.
15.
16. this.radListView2.Items.Clear();
17.
18. foreach (ListViewDataItem item in this.radListView1.CheckedItems)
19. {
20. string schema = item[0].ToString();
21. string tableName = item[1].ToString();
22. string connectionString = $"Server=localhost;Database={this.radDropDownList1.SelectedItem.Text};Integrated Security=true;TrustServerCertificate=True;";
23. string query = $@"
24. SELECT
25. TABLE_SCHEMA as SchemaName,
26. '{tableName}' AS TableName,
27. COLUMN_NAME AS ColumnName,
28. DATA_TYPE AS DataType,
29. CHARACTER_MAXIMUM_LENGTH AS Size
30. FROM INFORMATION_SCHEMA.COLUMNS
31. WHERE TABLE_SCHEMA = '{schema}' AND TABLE_NAME = '{tableName}' ";
32.
33. using var connection = new SqlConnection(connectionString);
34. using var command = new SqlCommand(query, connection);
35. connection.Open();
36. using var reader = command.ExecuteReader();
37.
38.
39. while (reader.Read())
40. {
41. var col = new ListViewDataItem();
42. col[0] = reader["TableName"].ToString();
43. col[1] = reader["ColumnName"].ToString();
44. col[2] = reader["DataType"].ToString();
45.
46. this.radListView2.Items.Add(col);
47.
48. sb.AppendLine($"{reader["SchemaName"]}.{reader["TableName"]}, {reader["ColumnName"]}, {reader["DataType"]}, {reader["Size"]}");
49. }
50.
51. }
52.
53. if (this.radListView2.Items.Count > 0)
54. this.radListView2.SelectedIndex = 0;
55.
56. string result = sb.ToString();
57. return sb.ToString();
58. }
Lorsque l’utilisateur entre en focus sur le radTextBox1 pour écrire l’invite, l’application appelle automatiquement l’IA GetResponse avec une invite pour générer des suggestions sur ce que les tables et colonnes sélectionnées peuvent être faites. Le Suggestion var est un contrôle permettant de ne pas générer d’autres réponses sans aucun changement dans la sélection.
1. private async void radTextBox1_Enter(object sender, EventArgs e)
2. {
3. const string Prompt = "Tell me 5 things you can answer based on these data.";
4. if (!Suggestion && this.radListView2.Items.Count > 0)
5. {
6. Suggestion = true;
7. this.radTextBox2.Text = Prompt;
8.
9. var result = await AI.GetResponse(false, SystemPrompt, Prompt);
10.
11. this.radTextBox2.Text = Prompt + "\r\n" + result.Item2.Replace("\n", "\r\n").Replace("**", "");
12. }
13. }
Dans le bouton « Générer », nous déclenchons l’événement pour obtenir le résultat :
1. private async void radButton1_ClickAsync(object sender, EventArgs e)
2. {
3. if (string.IsNullOrEmpty(SystemPrompt))
4. {
5. this.radLabel2.Text = "Please select a table";
6. return;
7. }
8.
9. var result = await AI.GetResponse(this.radCheckBox1.Checked, SystemPrompt, this.radTextBox1.Text);
10.
11. this.radTextBox2.Text = result.Item1.Replace("\n", "\r\n");
12. this.radButton2.Visible = true;
13. }
Désormais, nous utilisons Microsoft.Extensions.AI qui intègre également la logique de l’API OpenAI et de l’API Azure AI. En utilisant l’interface IChatClient, il est possible d’utiliser OpenAI, AzureOpenAI ou Ollama AI (non implémenté dans cet exemple). Nous définissons l’interface client et, à partir de la ligne 39, le code est le même pour tous les grands modèles linguistiques (LLM). Le Microsoft.Extensions.AI prendra bientôt en charge d’autres LLM.
Vous devrez ajouter les informations de votre compte pour les clés OPENAI_API_KEY et AZURE_OPENAI_ENDPOINT (et autres).
1. using Azure;
2. using Microsoft.Extensions.AI;
3. using OpenAI;
4. using System;
5. using System.Collections.Generic;
6. using System.Linq;
7. using System.Threading.Tasks;
8.
9. namespace TelerikWinFormsApp1;
10.
11. class AI
12. {
13. public static async Task<(string, string)> GetResponse(bool useAzure, string systemPrompt, string prompt)
14. {
15. IChatClient client;
16.
17. if (useAzure)
18. {
19. var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? "";
20. var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY") ?? "";
21. string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "";
22.
23. var aoaiClient = new OpenAIClient(
24. new AzureKeyCredential(key),
25. new OpenAIClientOptions
26. {
27. Endpoint = new Uri(endpoint)
28. }
29. );
30.
31. var chatClient = aoaiClient.GetChatClient(deploymentName);
32. client = chatClient.AsIChatClient();
33. }
34. else
35. {
36. var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? "";
37. var chatClient = new OpenAI.Chat.ChatClient("gpt-4o-mini", apiKey);
38. client = chatClient.AsIChatClient();
39. }
40.
41. var messages = new List<ChatMessage>
42. {
43. new(ChatRole.System, systemPrompt),
44. new(ChatRole.User, prompt)
45. };
46.
47. var response = await client.GetResponseAsync(messages);
48. string responseText = response?.Text ?? string.Empty;
49.
50. string[] responseParts = responseText.Split(new[] { "\n\n" }, StringSplitOptions.None);
51. var explanationText = responseParts.FirstOrDefault(part => !part.Contains("```sql", StringComparison.OrdinalIgnoreCase)) ?? string.Empty;
52.
53. var sqlQuery = string.Join("\r\n", responseParts);
54. var sql = Tools.ClearSql(sqlQuery);
55.
56. if (string.IsNullOrEmpty(sql))
57. {
58. explanationText = sqlQuery;
59. }
60.
61. return (sql, explanationText);
62. }
63. }
Le ClearSql La fonction obtient le code SQL de la réponse en utilisant RegEx :
1. public static string ClearSql(string queryTSql)
2. {
3. var match = Regex.Match(queryTSql, @"```sql\s*(.*?)\s*```", RegexOptions.Singleline);
4. if (!match.Success) return "";
5. string tsql = match.Groups[1].Value;
6. return tsql;
7. }
Le secret de cette application est de transmettre une invite système. Nous faisons cela à : new ChatMessage(ChatRole.System, systemPrompt),.
Le systemPromptcomme on le voit dans l’autre code ci-dessus, est :
"You are a data analyst and will generate T-SQL queries from this structure, response based on the database structure that you have access that are:"
Et la structure est la ChatRole.System.
ChatRole.System est le moteur qui prépare l’invite utilisateur à répondre correctement à l’invite utilisateur. Si vous écrivez ce code pour une base de données spécifique, vous pouvez ajouter plus d’informations ici pour aider l’IA à comprendre le contexte et les relations entre les données.
Conclusion
Cette application peut être améliorée pour aider les développeurs et les analystes de données à créer des requêtes avec l’aide de l’IA. Lors de mes tests avec mes bases de données, l’IA renvoie parfois des résultats incomplets, mais l’ajustement de l’invite renvoie une requête correcte.
Microsoft Extensions AI est une API puissante pour éviter de créer un code spécifique pour implémenter l’API AI et nous aurons bientôt des implémentations pour Claude AI, Gemini et d’autres IA du marché mondial.
Le code source complet est disponible sur mon GitHub https://github.com/jssmotta/MsExtensionsAI25/. Vous aurez besoin d’une licence Telerik pour utiliser cet exemple.
Source link
