隨著技術的發展,人工智慧在各個領域的套用已經不再是新鮮事。在資料庫查詢領域能夠自然地將人類語言轉換為SQL語句將為不懂技術的人士提供極大的便捷,同時也能大幅提高專業開發者的工作效率。今天,我帶大家深入了解一個非常有趣的計畫—— Nl2Sql ,這個計畫是基於.NET平台和 Semantic Kernel 的工具,它可以 將自然語言轉換為SQL查詢語句 。
https://github.com/microsoft/kernel-memory/tree/NL2SQL/examples/200-dotnet-nl2sql
GPT-4帶來的新突破
GPT-4的出現,使得基於自然語言處理的技術跨越了一個新的門檻,尤其是在自然語言轉換成SQL語句的能力上有了顯著提升。 Nl2Sql 工具就是利用GPT-4和 Semantic Kernel 的強大功能,為我們提供了一個實驗和測試平台,能夠基於自然語言表達生成SQL查詢語句。
計畫結構與樣例資訊
在開源的Nl2Sql計畫中,我們可以看到以下幾個組成部份:
nl2sql.config
- 包含了設定說明、數據模式和語意提示。nl2sql.console
- 控制台套用,用於將自然語言目標轉換成SQL查詢。nl2sql.library
- 支持庫,同樣用於自然語言到SQL的轉換。nl2sql.harness
- 開發偵錯工具,用於即時逆向工程化資料庫模式。nl2sql.sln
- Visual Studio解決方案檔。
執行樣例的第一步是進行初始設定和配置。
核心方法解析
我們來看一看 Nl2Sql 功能的實作關鍵。該程式采用.NET的開發框架,透過一系列方法來完成從輸入的自然語言到輸出的SQL查詢的轉換。這個過程涉及到自然語言理解、資料庫模式解析和查詢生成等環節。
privateasync Task ExecuteConsoleAsync(CancellationToken stoppingToken)
{
var schemaNames = SchemaDefinitions.GetNames().ToArray();
await SchemaProvider.InitializeAsync(
this._memory,
schemaNames.Select(s => Path.Combine(Repo.RootConfigFolder, "schema", $"{s}.json"))).ConfigureAwait(false);
this.WriteIntroduction(schemaNames);
while (!stoppingToken.IsCancellationRequested)
{
var objective = await ReadInputAsync().ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(objective))
{
continue;
}
var result =
awaitthis._queryGenerator.SolveObjectiveAsync(objective).ConfigureAwait(false);
await ProcessQueryAsync(result).ConfigureAwait(false);
}
this.WriteLine();
// Capture console input with cancellation detection
async Task<string?> ReadInputAsync()
{
this.Write(SystemColor, "# ");
var inputTask = Console.In.ReadLineAsync(stoppingToken).AsTask();
var objective = await inputTask.ConfigureAwait(false);
// Null response occurs when blocking input is cancelled (CTRL+C)
if (null == objective)
{
this.WriteLine();
this.WriteLine(FocusColor, "Cancellation detected...");
// Yield to sync stoppingToken state
await Task.Delay(TimeSpan.FromMilliseconds(300), stoppingToken).ConfigureAwait(false);
}
elseif (string.IsNullOrWhiteSpace(objective))
{
this.WriteLine(FocusColor, $"Please provide a query related to the defined schemas.{Environment.NewLine}");
}
else
{
this.ClearLine(previous: true);
this.WriteLine(QueryColor, $"# {objective}");
}
return objective;
}
// Display query result and (optionally) execute.
async Task ProcessQueryAsync(SqlQueryResult? result)
{
if (result == null)
{
this.WriteLine(FocusColor, $"Unable to translate request into a query.{Environment.NewLine}");
return;
}
this.WriteLine(SystemColor, $"{Environment.NewLine}SCHEMA:");
this.WriteLine(QueryColor, result.Schema);
this.WriteLine(SystemColor, $"{Environment.NewLine}QUERY:");
this.WriteLine(QueryColor, result.Query);
if (!this.Confirm($"{Environment.NewLine}Execute?"))
{
this.WriteLine();
this.WriteLine();
return;
}
await Task.Delay(300, stoppingToken).ConfigureAwait(false); // Human feedback window
this.ClearLine();
this.Write(SystemColor, "Executing...");
await ProcessDataAsync(
result.Schema,
result.Query,
reader =>
{
this.ClearLine();
this.WriteData(reader);
}).ConfigureAwait(false);
}
// Execute query and display the resulting data-set.
async Task ProcessDataAsync(string schema, string query, Action<IDataReader> callback)
{
try
{
usingvar connection = awaitthis._sqlProvider.ConnectAsync(schema).ConfigureAwait(false);
usingvar command = connection.CreateCommand();
#pragmawarning disable CA2100 // Review SQL queries for security vulnerabilities
command.CommandText = query;
#pragmawarning restore CA2100 // Review SQL queries for security vulnerabilities
usingvar reader = await command.ExecuteReaderAsync(stoppingToken).ConfigureAwait(false);
callback.Invoke(reader);
}
#pragmawarning disable CA1031 // Do not catch general exception types
catch (Exception exception)
#pragmawarning restore CA1031 // Do not catch general exception types
{
this.ClearLine();
this.WriteLine(FocusColor, exception.Message);
}
}
}
程式碼結構非常清晰,主要包含數據讀取、SQL命令執行、命令列輸入處理和數據展示等功能。整個轉換過程使用了異步編程模式,保證了良好的使用者互動體驗和程式效能。
使用者互動設計
在使用者互動方面,該程式展現了良好的設計。使用者可以在控制台中輸入自然語言描述的目標,程式會解析這一描述並生成對應的SQL查詢。如果成功生成,還會提供是否執行查詢的選項,在使用者確認後,能夠將查詢結果以格式化的方式顯示在控制台上。
數據展示和互動
在數據展示方面,開發人員精心設計了一整套顯示系統,能夠將查詢結果分頁顯示,同時考慮到了視窗寬度和數據欄位寬度的問題,確保了數據顯示的友好性和閱讀性。
privatevoidWriteData(IDataReader reader)
{
int maxPage = Console.WindowHeight - 10;
var widths = GetWidths().ToArray();
var isColumnTruncation = widths.Length < reader.FieldCount;
var rowFormatter = string.Join('│', widths.Select((width, index) => width == -1 ? $"{{{index}}}" : $"{{{index},-{width}}}"));
if (isColumnTruncation)
{
rowFormatter = string.Concat(rowFormatter, isColumnTruncation ? $"│{{{widths.Length}}}" : string.Empty);
}
WriteRow(GetColumns());
WriteSeparator(widths);
bool showData;
do
{
int count = 0;
while (reader.Read() && count < maxPage)
{
WriteRow(GetValues());
count++;
}
if (count >= maxPage)
{
showData = this.Confirm($"...More?");
this.ClearLine();
if (!showData)
{
this.WriteLine();
}
}
else
{
showData = false;
this.WriteLine();
}
} while (showData);
voidWriteRow(IEnumerable<string> fields)
{
fields = TrimValues(fields).Concat(isColumnTruncation ? new[] { "..." } : Array.Empty<string>());
this.WriteLine(SystemColor, rowFormatter, fields.ToArray());
}
IEnumerable<string> TrimValues(IEnumerable<string> fields)
{
int index = 0;
int totalWidth = 0;
foreach (var field in fields)
{
if (index >= widths.Length)
{
yieldbreak;
}
var width = widths[index];
++index;
if (width == -1)
{
var remainingWidth = Console.WindowWidth - totalWidth;
yield return TrimValue(field, remainingWidth);
yieldbreak;
}
totalWidth += width + 1;
yield return TrimValue(field, width);
}
}
stringTrimValue(string? value, int width)
{
value ??= string.Empty;
if (value.Length <= width)
{
returnvalue;
}
returnstring.Concat(value.AsSpan(0, width - 4), "...");
}
voidWriteSeparator(int[] widths)
{
int totalWidth = 0;
for (int index = 0; index < widths.Length; index++)
{
if (index > 0)
{
this.Write(SystemColor, "┼");
}
var width = widths[index];
this.Write(SystemColor, newstring('─', width == -1 ? Console.WindowWidth - totalWidth : width));
totalWidth += width + 1;
}
if (isColumnTruncation)
{
this.Write(SystemColor, "┼───");
}
this.WriteLine();
}
IEnumerable<int> GetWidths()
{
if (reader.FieldCount == 1)
{
yieldreturn-1;
yieldbreak;
}
int totalWidth = 0;
for (int index = 0; index < reader.FieldCount; ++index)
{
if (index == reader.FieldCount - 1)
{
// Last field gets remaining width
yieldreturn-1;
yieldbreak;
}
var width = GetWidth(reader.GetFieldType(index));
if (totalWidth + width > Console.WindowWidth - 11)
{
yieldbreak;
}
totalWidth += width;
yieldreturn width;
}
}
staticintGetWidth(Type type)
{
if (!s_typeWidths.TryGetValue(type, outvar width))
{
return16; // Default width
}
return width;
}
IEnumerable<string> GetColumns()
{
for (int index = 0; index < reader.FieldCount; ++index)
{
var label = reader.GetName(index);
yieldreturnstring.IsNullOrWhiteSpace(label) ? $"#{index + 1}" : label;
}
}
IEnumerable<string> GetValues()
{
for (int index = 0; index < reader.FieldCount; ++index)
{
yieldreturn reader.GetValue(index)?.ToString() ?? string.Empty;
}
}
}
整個程式對於輸入的處理非常人性化,如果使用者輸入了無關的或格式不正確的指令,程式會給出相應的提示,引導使用者重新輸入。
結語
作為一名.NET方向的技術博主,我認為 Nl2Sql 計畫不僅展示了現代AI技術的強大能力,也體現了.NET生態在人工智慧套用中的活躍度,並且推動了開發者之間的技術互助和分享。Nl2Sql證明了在未來,我們可以期待更多的自然語言處理工具來幫助我們更好地與數據互動。
此篇博文的目的在於向您介紹 Nl2Sql 計畫的設計理念、計畫結構、核心功能以及使用者互動體驗。但要註意的是,該計畫是否適用於特定的用例,仍然需要結合具體的業務場景和預期來評估。在整個數據檢索和處理生態系中, Nl2Sql 展示的僅僅是其中的一環,但絕對是非常值得我們關註的那部份。
希望今天的分享能夠激發您的興趣,同時也希望能夠在這個領域看到更多.NET開發者的參與和貢獻。讓我們一同期待在人工智慧和自然語言處理領域能夠取得更多技術突破,為我們的開發和生活帶來更多便捷!