ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Text2Sql.Net 实战:用 Semantic Kernel 让 .NET 数据库听懂人话

Text2Sql.Net 实战:用 Semantic Kernel 让 .NET 数据库听懂人话 1. 从一句中文到一条 SQLText2Sql.Net 到底解决了什么Text2Sql.Net 是一个基于 .NET 8 和 Microsoft Semantic Kernel 构建的自然语言转 SQL 框架它能让你用中文或英文直接向数据库提问自动生成可执行的 SQL 并返回结果。适合谁适合所有不想手写复杂 JOIN、又需要快速从业务库拿数的 .NET 开发者、全栈工程师和数据分析岗。我试过最典型的场景是这样的产品经理跑过来问“上个月销售额最高的前十个客户是谁”你打开 SQL 工具回忆表结构写 JOIN、写 GROUP BY、写 HAVING调半天才出结果。而用 Text2Sql.Net你只需要把这句话原封不动丢进 Blazor 页面几秒后 SQL 和结果一起出来。它的核心链路分四步第一步把数据库的表结构Schema转成向量存起来这叫 Schema 训练第二步用户提问时用语义搜索找到相关的表和列第三步把相关 Schema、Few-Shot 示例和用户问题拼成 Prompt交给 Semantic Kernel 调用大模型生成 SQL第四步执行 SQL 并把结果回显到前端。如果执行报错它还会把错误信息反馈给模型自动优化 SQL 重试最多迭代三次。这套流程听起来简单但真正落地时有几个坑Schema 注入不全导致模型“猜”列名、Prompt 里没给时间范围提示导致“上个月”被理解错、SQL 执行权限没控制好导致误操作。下面我会从零开始把最小可跑链路搭出来每一步都给可复制的配置和验证动作。2. 前置准备TaoToken 接入与 Semantic Kernel 环境搭建Text2Sql.Net 本身不绑定特定的大模型服务商它通过 Semantic Kernel 的 OpenAI 兼容接口调用模型。你需要准备两样东西一个可用的 API Key 和一个能跑 .NET 8 的环境。2.1 获取 API Key 与 Base URL访问 TaoToken 的 API Keys 管理页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite创建一个新的 Key。创建时注意选择支持 Chat Completion 和 Embedding 的模型权限因为 Text2Sql.Net 需要同时调用对话模型和向量化模型。拿到 Key 之后记下两个关键信息Base URLhttps://taotoken.net/apiAPI Keysk-开头的一串字符这两个值后面会写进appsettings.json。如果你还没注册可以先到官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite了解一下支持的模型列表确认你需要的模型比如 gpt-4o 或 claude 系列在可用范围内。2.2 环境要求.NET 8 SDKdotnet --version确认 8.0一个可连接的数据库SQL Server / MySQL / PostgreSQL / SQLite 都行本文用 SQLite 做演示零配置可选PostgreSQL pgvector 扩展生产环境推荐用于向量存储2.3 克隆项目与还原依赖git clone https://github.com/AIDotNet/Text2Sql.Net.git cd Text2Sql.Net dotnet restore项目结构里有两个关键目录src/Text2Sql.Net是核心库src/Text2Sql.Net.Web是 Blazor 前端。我们主要改的是 Web 项目的配置文件。2.4 配置 appsettings.json打开src/Text2Sql.Net.Web/appsettings.json把 OpenAI 相关的配置改成 TaoToken 的地址和你的 Key{ Text2SqlOpenAI: { Key: sk-你的TaoToken密钥, EndPoint: https://taotoken.net/api, ChatModel: gpt-4o, EmbeddingModel: text-embedding-ada-002 }, Text2SqlConnection: { DbType: Sqlite, DBConnection: DataSourcetext2sql.db, VectorConnection: text2sqlmem.db, VectorSize: 1536 } }注意EndPoint结尾不要带/v1Semantic Kernel 的 OpenAI 连接器会自动补全路径。如果你用的是其他兼容模型把ChatModel和EmbeddingModel换成对应名称即可。2.5 注册 Kernel 与插件在Program.cs里Text2Sql.Net 已经封装好了 Kernel 的注册逻辑。核心代码大致如下你不需要改但需要理解builder.Services.AddKernel() .AddOpenAIChatCompletion( modelId: config[Text2SqlOpenAI:ChatModel], apiKey: config[Text2SqlOpenAI:Key], endpoint: new Uri(config[Text2SqlOpenAI:EndPoint])) .AddOpenAITextEmbeddingGeneration( modelId: config[Text2SqlOpenAI:EmbeddingModel], apiKey: config[Text2SqlOpenAI:Key], endpoint: new Uri(config[Text2SqlOpenAI:EndPoint]));这段代码做了两件事注册对话模型用于生成 SQL注册 Embedding 模型用于 Schema 向量化。两者都走 TaoToken 的兼容接口不需要额外配置代理。3. 可复制配置Schema 注入、Prompt 模板与数据库连接这一节是整篇文章的核心。Text2Sql.Net 能不能“听懂人话”取决于三件事Schema 注入是否完整、Prompt 模板是否合理、数据库连接是否稳定。3.1 数据库连接配置在appsettings.json的Text2SqlConnection节点里DbType支持Sqlite、SqlServer、MySQL、PostgreSQL四种。生产环境建议用 PostgreSQL pgvector因为向量检索性能更好。如果你用 PostgreSQL配置改成这样{ Text2SqlConnection: { DbType: PostgreSQL, DBConnection: Hostlocalhost;Databasetext2sql;Usernamepostgres;Passwordyourpassword, VectorConnection: Hostlocalhost;Databasetext2sql_vector;Usernamepostgres;Passwordyourpassword, VectorSize: 1536 } }然后在 PostgreSQL 里启用 pgvector 扩展CREATE EXTENSION IF NOT EXISTS vector;VectorSize必须和 Embedding 模型的输出维度一致。text-embedding-ada-002是 1536 维如果你换其他模型记得同步修改。3.2 Schema 训练让模型知道表里有什么Text2Sql.Net 启动后第一件事是训练 Schema。它会读取数据库的所有表、列、主键、外键拼成自然语言描述然后调用 Embedding 模型生成向量存入向量库。在 Blazor 页面里进入/schema-training/{ConnectionId}页面点击“训练所有表”。后端会执行类似这样的逻辑public async Taskbool TrainDatabaseSchemaAsync(string connectionId) { var tables await GetDatabaseTablesAsync(connectionId); foreach (var table in tables) { var tableDescription BuildTableDescription(table); var embedding new SchemaEmbedding { ConnectionId connectionId, TableName table.TableName, Description tableDescription, EmbeddingType EmbeddingType.Table }; await textMemory.SaveInformationAsync( connectionId, id: ${connectionId}_{table.TableName}, text: JsonConvert.SerializeObject(embedding)); } return true; }BuildTableDescription会把表名、列名、数据类型、外键关系拼成一段文字。比如Orders表会变成“表 Orders订单表列OrderId int 主键CustomerId int 外键关联 Customers.CustomerIdOrderDate datetimeTotalAmount decimal。”这段文字越清晰后续语义检索越准。3.3 Prompt 模板配置Text2Sql.Net 的 Prompt 是分层拼装的系统角色 Schema 上下文 Few-Shot 示例 用户问题 输出格式要求。你可以在PromptService里找到模板也可以自定义。一个典型的 Prompt 长这样你是一个专业的 SQL Server 查询生成专家。 请根据以下数据库 Schema 和用户问题生成准确的 SQL 查询。 -- Schema 信息 表: Orders订单表 列: - OrderId: int主键非空- 订单 ID - CustomerId: int非空- 客户 ID - OrderDate: datetime非空- 订单日期 - TotalAmount: decimal(18,2) - 订单总额 外键: CustomerId → Customers.CustomerId 表: Customers客户表 列: - CustomerId: int主键非空- 客户 ID - CustomerName: nvarchar(100) - 客户名称 - Email: nvarchar(100) - 邮箱 -- 相关示例 示例 1: 问题: 查询上个月订单总额超过 1000 的客户 SQL: SELECT c.CustomerName, SUM(o.TotalAmount) as TotalSales FROM Orders o INNER JOIN Customers c ON o.CustomerId c.CustomerId WHERE o.OrderDate DATEADD(MONTH, -1, GETDATE()) GROUP BY c.CustomerName HAVING SUM(o.TotalAmount) 1000 -- 用户问题 查询最近 7 天内下单次数最多的前 10 个客户 -- 请生成 SQL只返回 SQL 语句不要任何解释关键点输出格式要求“只返回 SQL”这样后续解析不用做额外清洗。如果你发现模型经常返回带 markdown 标记的 SQL可以在 Prompt 里加一句“不要包含 markdown 代码块标记”。3.4 Few-Shot 示例配置Few-Shot 示例是提升准确率最有效的手段。Text2Sql.Net 支持在数据库里维护问答示例每次查询时用语义搜索找出最相关的三条拼进 Prompt。你可以在QAExampleService里看到检索逻辑public async TaskListQAExample GetRelevantExamplesAsync( string connectionId, string userQuestion, int limit 3, double minRelevanceScore 0.7) { var memory await _semanticService.GetTextMemory(); var collectionName $qa_examples_{connectionId}; var relevantExamples new ListQAExample(); await foreach (var result in memory.SearchAsync( collectionName, userQuestion, limit, minRelevanceScore)) { var exampleData JsonConvert .DeserializeObjectQAExampleEmbedding(result.Metadata.Text); var example await _exampleRepository .GetByIdAsync(exampleData.ExampleId); if (example ! null example.IsEnabled) { relevantExamples.Add(example); } } return relevantExamples; }建议至少准备 5-10 条覆盖常见查询模式的示例比如聚合查询、时间范围查询、多表 JOIN、模糊匹配。示例质量比数量重要一条准确的示例能顶十条模糊的。4. 验证请求三条自然语言查询的完整过程与结果对照配置完成后启动项目cd src/Text2Sql.Net.Web dotnet run访问http://localhost:5000进入数据库聊天页面。下面用三条查询验证整条链路。4.1 查询一简单聚合输入“统计每个客户的下单总金额按金额从高到低排序”系统生成的 SQLSELECT c.CustomerName, SUM(o.TotalAmount) AS TotalAmount FROM Orders o INNER JOIN Customers c ON o.CustomerId c.CustomerId GROUP BY c.CustomerName ORDER BY TotalAmount DESC返回结果示例CustomerNameTotalAmount张三15800.00李四12300.50王五9800.00验证动作检查 SQL 里是否正确使用了SUM和GROUP BYJOIN 条件是否用了外键CustomerId。如果模型把CustomerName写成了Name说明 Schema 注入不完整需要重新训练。4.2 查询二时间范围 条件过滤输入“查询最近 7 天内下单次数超过 3 次的客户邮箱”系统生成的 SQLSELECT c.Email, COUNT(*) AS OrderCount FROM Orders o INNER JOIN Customers c ON o.CustomerId c.CustomerId WHERE o.OrderDate DATEADD(DAY, -7, GETDATE()) GROUP BY c.Email HAVING COUNT(*) 3返回结果示例EmailOrderCountzhangsanexample.com5lisiexample.com4验证动作确认DATEADD(DAY, -7, GETDATE())是否正确对应“最近 7 天”。如果模型写成了OrderDate 2024-01-01这种硬编码日期说明 Prompt 里缺少当前日期提示。可以在 Prompt 里加一句“当前日期是 {DateTime.Now:yyyy-MM-dd}”。4.3 查询三多表 JOIN 模糊匹配输入“查询邮箱包含 example 的客户的所有订单显示客户名、订单日期和金额”系统生成的 SQLSELECT c.CustomerName, o.OrderDate, o.TotalAmount FROM Orders o INNER JOIN Customers c ON o.CustomerId c.CustomerId WHERE c.Email LIKE %example% ORDER BY o.OrderDate DESC返回结果示例CustomerNameOrderDateTotalAmount张三2024-06-153200.00李四2024-06-141500.00验证动作检查LIKE %example%是否正确生成。如果模型用了而不是LIKE说明 Prompt 里缺少模糊匹配的提示。可以在 Few-Shot 示例里加一条“包含 xxx”的示例。4.4 执行失败自动优化验证故意在 Prompt 里不给Customers表的Email列信息然后提问“查询客户邮箱”。模型可能生成SELECT Email FROM Customers但实际列名是CustomerEmail执行会报错。Text2Sql.Net 的反馈优化机制会捕获错误把错误信息和 Schema 一起重新发给模型public async TaskOptimizationResult OptimizeWithFeedbackAsync( string connectionId, string userMessage, string schemaJson, string originalSql) { var result new OptimizationResult(); var currentSql originalSql; int maxIterations 3; for (int i 0; i maxIterations; i) { var (queryResult, errorMessage) await _sqlExecutionService .ExecuteQueryAsync(connectionId, currentSql); if (string.IsNullOrEmpty(errorMessage)) { result.Success true; result.FinalSql currentSql; result.QueryResult queryResult; break; } currentSql await OptimizeSqlWithErrorFeedback( userMessage, currentSql, errorMessage, schemaJson); } return result; }实测下来大部分列名错误在第一次重试就能修正。如果三次都失败说明 Schema 训练数据缺失严重需要重新训练。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节列出接入过程中最容易遇到的四类报错每个都给排查路径。5.1 401 Unauthorized报错原文System.ClientModel.ClientResultException: 401 Unauthorized原因API Key 无效或过期。检查appsettings.json里的Text2SqlOpenAI:Key是否和 TaoToken 控制台里的一致。注意不要有多余空格也不要误把 Embedding Key 填到 Chat Key 位置。排查命令curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的密钥 \ -H Content-Type: application/json \ -d {model:gpt-4o,messages:[{role:user,content:hi}]}如果返回 200说明 Key 没问题问题在代码配置。如果返回 401去 TaoToken 控制台重新生成 Key。5.2 local proxy failed报错原文local proxy failed: connection refused原因EndPoint配置错误或者本地网络无法访问目标地址。检查EndPoint是否写成了https://taotoken.net/api不要带/v1也不要带尾部斜杠。如果你在 Docker 里跑确认容器能访问外网。可以用docker exec -it 容器名 curl https://taotoken.net/api测试。5.3 reading choices 相关报错报错原文Error reading choices[0].message.content: content is null原因模型返回了空内容通常是因为 Prompt 太长超出了模型上下文窗口或者模型被安全策略拦截。检查 Schema 注入是否过多建议只注入相关表不要全库注入。Text2Sql.Net 的 Schema Linking 会自动筛选相关表但如果你的数据库有 200 张表建议在训练时只训练业务相关的表或者调低minRelevanceScore让检索更精准。5.4 OAuth 相关报错报错原文OAuth token exchange failed或invalid_client原因如果你用的是 Azure OpenAI 或其他需要 OAuth 的服务配置方式和 TaoToken 的 API Key 模式不同。Text2Sql.Net 默认走 API Key 模式如果你混用了 OAuth 配置会报这个错。解决确认appsettings.json里只配置了Key和EndPoint没有多余的TenantId、ClientId等字段。如果你确实需要 OAuth参考 Semantic Kernel 的 Azure OpenAI 连接器文档单独配置。5.5 三件套检查清单无论遇到哪种报错先检查这三项配置项正确值示例常见错误Base URLhttps://taotoken.net/api多了/v1或尾部斜杠API Keysk-xxxx空格、换行、Key 过期Model IDgpt-4o模型名拼写错误、权限不足如果你用的是 Cline MCP 或 Claude Code 接入配置格式类似但字段名可能不同。Cline MCP 的配置里需要写baseUrl、apiKey、model三个字段Claude Code 的settings.json里对应anthropic.baseUrl、anthropic.apiKey、anthropic.model。Codex 的auth.json里则是api_base、api_key、model。不管哪种核心三件套不变。6. 从能跑到好用长期编码与 Agent 场景的接入建议最小链路跑通后下一步是把它变成日常工具。两个方向一是接入 Coding Plan 做长期编码辅助二是通过 MCP 协议接入 AI IDE 做 Agent 场景。6.1 接入 Coding Plan如果你每天都要写 SQL、调 Schema、优化查询建议开通 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite。它提供更稳定的调用配额和更低的延迟适合长时间挂载在开发环境里。接入方式很简单把appsettings.json里的Key换成 Coding Plan 专属 KeyEndPoint不变。然后在 Blazor 页面里把常用查询保存成快捷按钮比如“今日订单统计”“本周新增客户”“库存预警”一键触发。6.2 MCP 协议接入 AI IDEText2Sql.Net 内置了 MCP Server可以被 Cursor、Trae 等支持 MCP 的 IDE 直接调用。配置方式{ mcpServers: { text2sql: { name: Text2Sql.Net-ProductionDB, type: sse, description: 智能 Text2SQL 服务, isActive: true, url: http://localhost:5000/mcp/sse?connectionIdyour-db-id } } }配置完成后在 IDE 里可以直接用自然语言查库你: text2sql 查询最近一周的订单统计 AI: 正在调用 Text2Sql 工具... 生成的 SQL: SELECT CAST(OrderDate AS DATE) as OrderDay, COUNT(*) as OrderCount, SUM(TotalAmount) as TotalSales FROM Orders WHERE OrderDate DATEADD(DAY, -7, GETDATE()) GROUP BY CAST(OrderDate AS DATE) ORDER BY OrderDay DESC 执行结果: 返回 7 条记录MCP 的好处是上下文感知IDE 可以把当前项目信息传给 Text2Sql.Net让生成的 SQL 更贴合业务语义。比如你正在改订单模块提问“查一下这个表的最近数据”模型会自动关联到Orders表。6.3 生产环境注意事项第一SQL 安全审查不能省。Text2Sql.Net 内置了危险关键字检查但建议再加一层数据库权限控制只给查询账号SELECT权限。第二向量库定期重建。Schema 变更后旧向量会失效建议在 CI/CD 里加一个 Schema 训练步骤每次部署后自动触发增量训练。第三监控查询延迟。在ChatService外面包一层计时逻辑记录每次 SQL 生成和执行的耗时。如果发现某类查询经常超时检查是不是 Schema 注入过多导致 Prompt 太长。第四Few-Shot 示例库持续维护。每次遇到模型生成错误 SQL 的情况把正确 SQL 和问题一起存进示例库下次同类问题就能命中。这是提升准确率最划算的投入。如果你还没开始建议先从 SQLite 本地库跑通三条验证查询再逐步迁移到生产库。遇到报错先查三件套Base URL、Key、Model ID再查 Schema 训练是否完整。整套流程跑顺之后你会发现“让数据库听懂人话”这件事比想象中简单得多。
返回列表