現(xiàn)輕量 Text2SQL:讓自然語言直接查詢數(shù)據(jù)庫)
過去半年我一直在折騰一個很現(xiàn)實(shí)的問題怎么讓不會寫 SQL 的人也能直接對著數(shù)據(jù)庫問問題。最終落地的是一個用 DeepSeek 做大模型推理、SQLite 做數(shù)據(jù)存儲的輕量 Text2SQL 查詢助手——輸入一句中文比如“上個月銷售額最高的三個商品”它自動生成 SQL、在 SQLite 引擎里執(zhí)行再把結(jié)果用表格形式返回。整個過程不用寫一行 SQL也不用部署重型數(shù)據(jù)庫服務(wù)。這個項(xiàng)目是典型的 LLM 落地形態(tài)不需要訓(xùn)練、不需要私有化部署大模型只需要把數(shù)據(jù)庫結(jié)構(gòu)和查詢約束交代給模型它就能輸出可執(zhí)行 SQL。整套代碼加起來不到 200 行特別適合第一次認(rèn)真做 Text2SQL 的人拿來當(dāng)模板也適合想驗(yàn)證大模型在生成式查詢場景里到底穩(wěn)不穩(wěn)的開發(fā)者做一輪實(shí)測。下面我把從零搭建的完整過程講清楚包括提示詞設(shè)計、SQL 安全校驗(yàn)、錯誤糾錯循環(huán)以及我實(shí)際踩過的坑。1. 為什么選 Text2SQL 作為 LLM 落地項(xiàng)目整體思路是什么1.1 一句話描述項(xiàng)目形態(tài)這個查詢助手的核心鏈路可以概括為用戶輸入自然語言問題 → 系統(tǒng)把 SQLite 的 schema 注入提示詞 → 調(diào)用 DeepSeek 生成 SQL → 程序提取并校驗(yàn) SQL → 在 SQLite 中執(zhí)行 → 返回查詢結(jié)果。如果生成的 SQL 報錯程序會把錯誤信息回傳給模型讓它基于錯誤修正后重新生成。聽起來很順但真正實(shí)施起來有幾個難點(diǎn)模型可能生成不存在的字段名可能寫出 SQLite 不支持的語法甚至可能生成插入、刪除這類危險語句。所以這個項(xiàng)目表面上是在調(diào) API本質(zhì)上是在做三件事讓模型“看懂”庫結(jié)構(gòu)、讓輸出“規(guī)規(guī)矩矩”變成純 SQL、讓執(zhí)行過程“萬無一失”不會破壞數(shù)據(jù)。1.2 為什么選 DeepSeek SQLite 這套組合先聊模型選型。Text2SQL 對模型的要求是中文理解能力強(qiáng)、SQL 語法知識扎實(shí)、輸出穩(wěn)定。DeepSeek 的開放接口兼容 OpenAI 調(diào)用格式代碼上接入成本很低而且在實(shí)際測試中它對中文業(yè)務(wù)問題的理解明顯比通用模型更貼近中文語境生成的 SQL 也習(xí)慣用注釋和 LIMIT 這類安全寫法。再聊數(shù)據(jù)庫選型。SQLite 是單文件數(shù)據(jù)庫不需要安裝服務(wù)端一個.db文件就能承載一張完整的業(yè)務(wù)表結(jié)構(gòu)。對于個人工具、內(nèi)部小助手這類場景它簡直是絕配輕、免維護(hù)、隨時可以復(fù)制走。相比 MySQL、PostgreSQLSQLite 還能用只讀 URI 模式打開天然適合給 LLM 生成的查詢語句做執(zhí)行沙箱。這套組合的另一個優(yōu)勢是成本極低。單次查詢請求通常只消耗幾千 token跑幾百次測試的成本也就在幾塊錢量級可以放心反復(fù)實(shí)驗(yàn)。相比接一個獨(dú)立的數(shù)據(jù)庫服務(wù)開發(fā)階段省掉的不只是部署還有連接配置、賬號權(quán)限、網(wǎng)絡(luò)安全這一堆事。1.3 整體流程設(shè)計一次查詢請求是怎么走通的我把它拆成五個環(huán)節(jié)取 schema、拼提示詞、調(diào)模型、執(zhí)行 SQL、反饋糾錯。前兩個環(huán)節(jié)是準(zhǔn)備中間一個是核心后兩個是保障。取 schema 這一步很多人會偷懶覺得寫死幾張表名就行了。實(shí)際上模型能不能生成正確 SQL很大程度上取決于它能不能看到完整字段名和類型。我選擇通過sqlite_master系統(tǒng)表自動讀取建表語句這樣數(shù)據(jù)庫結(jié)構(gòu)一變提示詞也會自動跟上。拼提示詞是最考功底的一步。要把數(shù)據(jù)庫結(jié)構(gòu)、業(yè)務(wù)字段說明、輸出格式限制、安全約束全部塞進(jìn)一段上下文里。提示詞不是越長越好而是要讓模型明確三件事回答范圍是什么、輸出格式是什么、禁區(qū)是什么。調(diào)模型和執(zhí)行 SQL 之間隔著一道安全檢查。LLM 生成的內(nèi)容本質(zhì)上不可信不能直接丟給數(shù)據(jù)庫執(zhí)行。我加了兩層防護(hù)關(guān)鍵字黑名單和只讀打開連接。前者擋掉語義層面的危險語句后者從數(shù)據(jù)庫層面保證即使漏網(wǎng)也改不了任何數(shù)據(jù)。整個設(shè)計里最讓我得意的是糾錯循環(huán)。第一次生成的 SQL 很可能會因?yàn)樽侄蚊村e、函數(shù)用錯而執(zhí)行失敗。傳統(tǒng)的做法是讓用戶重新問一遍但我在執(zhí)行出錯時會把 SQLite 返回的錯誤信息拼回對話上下文讓模型直接改寫。這樣一次失敗的查詢往往能在第二次、第三次嘗試內(nèi)自我修復(fù)用戶體驗(yàn)好很多。2. 動手前準(zhǔn)備示例庫、API 接入與 schema 注入2.1 建一個能說明問題的小型 SQLite 示例庫為了測試多表關(guān)聯(lián)和聚合查詢我建立了一個非常接近真實(shí)業(yè)務(wù)的三表結(jié)構(gòu)用戶表、商品表、訂單表。訂單表通過外鍵關(guān)聯(lián)用戶和商品這樣可以很方便地演示 JOIN、GROUP BY、子查詢這類常見需求。建表 SQL 如下DROP TABLE IF EXISTS users; CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, city TEXT, created_at TEXT NOT NULL ); DROP TABLE IF EXISTS products; CREATE TABLE products ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, category TEXT, price REAL, stock INTEGER ); DROP TABLE IF EXISTS orders; CREATE TABLE orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, product_id INTEGER NOT NULL, quantity INTEGER NOT NULL, order_time TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (product_id) REFERENCES products(id) );有一點(diǎn)需要注意SQLite 本身對字段類型非常寬容所以時間字段我統(tǒng)一存成YYYY-MM-DD HH:MM:SS格式的字符串。這樣模型在做時間范圍查詢時可以用strftime或字符串比較不容易出錯。插入數(shù)據(jù)也不必太復(fù)雜每個表放 20 條左右就夠測試了。記得讓訂單分布在不同的月份和城市因?yàn)椤吧蟼€月”這類帶時間條件的查詢是 Text2SQL 測評的重災(zāi)區(qū)數(shù)據(jù)設(shè)計時就要故意制造可查詢的時間跨度。2.2 DeepSeek API 的接入方式DeepSeek 提供了 OpenAI 兼容的接口所以我直接用openaiPython 庫來調(diào)用避免另外封裝一套請求邏輯。所有需要登錄的密鑰都通過環(huán)境變量讀取絕不硬編碼在腳本里。import os from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) def ask_deepseek(messages, temperature0.0): resp client.chat.completions.create( modeldeepseek-chat, messagesmessages, temperaturetemperature, ) return resp.choices[0].message.content把溫度設(shè)置成 0 是我反復(fù)測試后確定的。Text2SQL 不是創(chuàng)意寫作它需要確定性輸出。溫度一旦調(diào)高模型可能用不同的語法表達(dá)同一個查詢這會給后續(xù)糾錯增加不必要的變量。如果你不想引入 openai 庫直接用requests調(diào)官方接口也是可行的但openai庫在消息構(gòu)造、超時處理、錯誤信息這些方面已經(jīng)封裝得很好我建議直接用它。2.3 schema 自動注入機(jī)制模型不知道你的庫長什么樣所以每次查詢前都要把結(jié)構(gòu)信息喂給它。手動寫一段固定的 schema 描述很容易但數(shù)據(jù)庫一變就維護(hù)不過來了。我的做法是從 SQLite 系統(tǒng)表讀取真實(shí)建表語句再組裝成一段純文本。import sqlite3 def get_schema(db_path): conn sqlite3.connect(db_path) rows conn.execute( SELECT sql FROM sqlite_master WHERE typetable AND sql IS NOT NULL ).fetchall() conn.close() return \n\n.join(r[0] for r in rows)這樣返回的 schema 是原始的 CREATE TABLE 語句包含字段名、類型、外鍵約束。模型對這種結(jié)構(gòu)化文本的吸收能力很強(qiáng)幾乎不需要再做格式化。唯一要補(bǔ)充的是業(yè)務(wù)說明比如“price 單位是元”“order_time 是下單時間”。我會把這些注釋追加到 schema 末尾而不是直接改建表語句保持?jǐn)?shù)據(jù)庫文件干凈。實(shí)際測試下來schema 加上一句“請基于上面的表結(jié)構(gòu)生成 SQLite 方言的 SQL”就能明顯壓低模型使用錯誤表名和字段名的概率。這一步做好后面所有環(huán)節(jié)都會省心很多。3. 核心代碼逐段拆解提示詞、安全校驗(yàn)與糾錯循環(huán)3.1 提示詞模板是如何讓模型穩(wěn)定輸出 SQL 的提示詞設(shè)計是這個項(xiàng)目最核心的環(huán)節(jié)。一開始我寫得很簡陋就一句“把下面的話轉(zhuǎn)成 SQL”結(jié)果模型輸出五花八門有的帶解釋有的用 MySQL 語法有的直接拒絕回答。后來我總結(jié)出一套有效的模板核心是四項(xiàng)約束。SYSTEM_PROMPT 你是一個精通 SQLite 的數(shù)據(jù)庫查詢助手。 請根據(jù)數(shù)據(jù)庫 schema 和用戶問題生成可直接執(zhí)行的 SQL 查詢。 要求 1. 只輸出 SQL 語句本身不要輸出任何解釋、代碼塊標(biāo)記。 2. 只允許 SELECT 查詢禁止 INSERT、UPDATE、DELETE、DROP 等語句。 3. 使用 SQLite 語法不要使用其他數(shù)據(jù)庫的專用函數(shù)。 4. 查詢結(jié)果默認(rèn)加上 LIMIT 50。 5. 如果問題與數(shù)據(jù)庫無關(guān)或無法用提供的 schema 回答輸出 SQL_ERR: 無法回答。 def build_messages(user_question, schema, error_hintNone): user_content f數(shù)據(jù)庫 schema:\n{schema}\n\n用戶問題: {user_question} messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_content}, ] if error_hint: messages.append({ role: assistant, content: error_hint[sql] }) messages.append({ role: user, content: f你生成的 SQL 執(zhí)行報錯{error_hint[error]}\\n請修正后重寫。 }) return messages第一條約束解決了“模型話多”的問題。你可能會覺得讓模型輸出解釋更友好但對程序來說解析一段可能有代碼標(biāo)記和廢話的文本遠(yuǎn)比解析純 SQL 麻煩。我寧可讓模型輸出裸 SQL再由程序負(fù)責(zé)展示結(jié)果。第二條約束是安全底線。模型本身沒有惡意但它可能因?yàn)橛脩魡栴}描述不當(dāng)而生成危險語句。直接在提示詞里聲明禁區(qū)能從源頭過濾一大半風(fēng)險。第三、四條約束屬于方言限定。SQLite 的日期函數(shù)、字符串處理方式和 MySQL、PostgreSQL 差別很大提前聲明可以避免模型想當(dāng)然地寫出DATE_FORMAT這類函數(shù)。3.2 SQL 安全校驗(yàn)為什么 LLM 的輸出不能直接執(zhí)行即使提示詞說了“只允許 SELECT”我也在代碼里加了獨(dú)立校驗(yàn)。原因很簡單提示詞是對模型的軟約束不是硬保證。模型可能因?yàn)樯舷挛奶L而忽略限制也可能被一種叫“注入攻擊”的手段繞過。我的校驗(yàn)分為三層。第一層去掉首尾空白、將 SQL 轉(zhuǎn)成小寫后檢查是否包含insert、update、delete、drop、alter、attach、pragma這些關(guān)鍵詞。第二層檢查第一個非空語句是否以select或with開頭。第三層用只讀 URI 模式連接 SQLite從數(shù)據(jù)庫層保證任何寫操作都會拋出錯誤。import os import re BANNED_KEYWORDS [insert, update, delete, drop, alter, attach, pragma] def extract_sql(text): blocks re.findall(r(?:sql)?\\s*(.*?), text, re.S) if blocks: return blocks[0].strip() if text.startswith(SQL_ERR): return None return text.strip() def check_sql(sql): if not sql: return False, 空 SQL low sql.lower() for kw in BANNED_KEYWORDS: if re.search(r\\b kw r\\b, low): return False, f包含危險關(guān)鍵詞 {kw} if not re.match(r^(select|with)\\b, low): return False, 必須以 SELECT 或 WITH 開頭 return True, def execute_readonly(db_path, sql): uri ffile:{os.path.abspath(db_path)}?modero conn sqlite3.connect(uri, uriTrue, timeout10) try: cur conn.execute(sql) cols [d[0] for d in cur.description] if cur.description else [] rows cur.fetchall() return cols, rows finally: conn.close()我用了一個很實(shí)用的技巧如果 SQL 里包含注釋或者多余空白正則也能提取干凈。另外execute_readonly返回的不僅包含結(jié)果行還包含列名。這樣在命令行里展示時可以直接拿列名做表頭省得再查一次PRAGMA table_info。3.3 帶糾錯的主流程一次查詢失敗怎么辦第一次生成的 SQL 執(zhí)行失敗太常見了。我的處理方法是把 SQL 和錯誤信息一起回傳給模型讓它看到自己寫的代碼和數(shù)據(jù)庫的真實(shí)反應(yīng)然后要求它修正。這相當(dāng)于給模型一個“現(xiàn)場調(diào)試”的機(jī)會。def text2sql(db_path, question, max_retries2): schema get_schema(db_path) messages build_messages(question, schema) for attempt in range(max_retries 1): raw ask_deepseek(messages) sql extract_sql(raw) if sql is None: return {error: 模型無法生成 SQL, raw: raw} ok, msg check_sql(sql) if not ok: messages build_messages(question, schema, {sql: sql, error: msg}) continue try: cols, rows execute_readonly(db_path, sql) return {sql: sql, columns: cols, rows: rows} except sqlite3.Error as e: messages build_messages(question, schema, {sql: sql, error: str(e)}) return {error: 重試次數(shù)用盡, sql: sql}這里有個細(xì)節(jié)值得多說兩句重試時不是簡單地把錯誤信息追加到原消息尾部而是完整重建 message 列表并在其中模擬一段“助手生成了 SQL用戶反饋了報錯”的對話。這樣做的好處是模型能明確看到自己的上一次輸出而不是在越來越長的上下文里迷失。實(shí)測中絕大多數(shù)語法錯誤和字段名錯誤都能在第一次糾錯內(nèi)解決。3.4 一個可以直接跑的命令行主循環(huán)把上面的函數(shù)組合起來就是一個完整的命令行查詢助手。它支持exit退出每次輸入問題都會打印 SQL 和查詢結(jié)果。def main(): db_path shop.db print(Text2SQL 查詢助手已啟動輸入 exit 退出。) while True: question input(\\n問題: ).strip() if question.lower() in (exit, quit): break if not question: continue result text2sql(db_path, question) if error in result: print(錯誤:, result[error]) continue print(\\nSQL:, result[sql]) if result[columns]: print(\\t.join(result[columns])) for row in result[rows]: print(\\t.join(str(c) for c in row)) if __name__ __main__: main()整個腳本文件大概 150 行。你如果只想快速驗(yàn)證效果把這段代碼存成text2sql_assistant.py再設(shè)置好環(huán)境變量就能啟動。別急著加界面、加日志、加權(quán)限控制輕量才是這個項(xiàng)目的定位。等驗(yàn)證完模型效果再去擴(kuò)展 UI 和配套能力也不遲。4. 實(shí)測效果與幾個值得注意的點(diǎn)4.1 一組典型查詢的輸入與輸出對比我把腳本跑起來后連續(xù)問了十多個問題包括單表過濾、聚合、多表關(guān)聯(lián)、時間范圍、排序取前幾。下面是幾組有代表性的結(jié)果自然語言問題生成的 SQL節(jié)選執(zhí)行結(jié)果每個城市的用戶數(shù)量是多少SELECT city, COUNT(*) AS cnt FROM users GROUP BY city ORDER BY cnt DESC正常返回上個月銷量最高的三個商品SELECT p.name, SUM(o.quantity) AS total FROM orders o JOIN products p ON o.product_id p.id WHERE strftime(%Y-%m, o.order_time) strftime(%Y-%m, now, -1 month) GROUP BY p.name ORDER BY total DESC LIMIT 3正常返回哪些商品價格超過100元但庫存不足20件SELECT name, price, stock FROM products WHERE price 100 AND stock 20正常返回找出購買了商品最多的前5個用戶子查詢 JOIN自動處理聚合和排序正常返回昨天每個類別的銷售額strftime(%Y-%m-%d, o.order_time) strftime(%Y-%m-%d, now, -1 day)結(jié)合 JOIN正常返回讓我意外的是模型對“上個月”“昨天”這類相對時間理解得相當(dāng)準(zhǔn)直接用了strftime(%Y-%m, now, -1 month)這種寫法而不是我預(yù)想中的死日期。這說明只要 schema 里字段命名清晰模型完全可以把自然語言的時間表達(dá)映射成 SQLite 方言。4.2 實(shí)測中的效果邊界模型不是萬能的。我試過一個問題“誰的訂單金額最高”。這個描述有歧義可以指單個訂單金額最高也可以指累計消費(fèi)金額最高。模型默認(rèn)選擇了累計求和但這未必是提問者想要的。這類模糊查詢沒有標(biāo)準(zhǔn)答案需要在提示詞里追加“如果問題不明確請列出多種解釋”的規(guī)則或者讓用戶補(bǔ)充條件。另一個邊界是復(fù)雜嵌套查詢。比如“找出購買過所有超過100元商品的用戶”這種帶全稱量詞的語義模型偶爾會生成錯誤的邏輯結(jié)構(gòu)。我建議遇到這種問題時把問題拆成多個步驟查詢而不是期望模型一步到位。4.3 成本與性能控制成本方面單次查詢包含 schema 和 SQL 結(jié)果大約消耗 1000 到 2000 token。一次完整的糾錯循環(huán)可能到 4000 token。就算連續(xù)測試 500 個問題成本也很低完全可以接受。性能方面真正的瓶頸不在模型調(diào)用而在每次請求都要重新獲取 schema。雖然這條語句執(zhí)行很快但反復(fù)讀取也不優(yōu)雅。我后來加了一個簡單的模塊級緩存_schema_cache {} def get_schema_cached(db_path): if db_path not in _schema_cache: _schema_cache[db_path] get_schema(db_path) return _schema_cache[db_path]如果你的數(shù)據(jù)庫結(jié)構(gòu)很少變動這個緩存非常管用能省掉每次讀取和拼接的開銷。同時建議給 OpenAI 客戶端設(shè)置一個合理的timeout和max_retries避免網(wǎng)絡(luò)抖動時整個命令行卡死。5. 常見問題排查與避坑記錄5.1 問題速查表癥狀原因分析解決方案模型輸出帶解釋文字SQL 提取失敗提示詞約束不足在提示詞里強(qiáng)調(diào)“只輸出 SQL 本身”并用正則提取代碼塊生成的 SQL 在 SQLite 中報語法錯誤模型慣性寫了 MySQL/PostgreSQL 語法提示詞里明確“使用 SQLite 語法”利用糾錯循環(huán)回傳錯誤查詢結(jié)果明顯錯誤但不是 SQL 報錯字段語義理解偏差在 schema 后補(bǔ)充業(yè)務(wù)注釋或把問題改得更具體查出來的數(shù)據(jù)為空時間格式或過濾條件不匹配檢查數(shù)據(jù)里的時間字段是否與strftime格式化結(jié)果一致模型把“價格超過100元”理解成“價格低于100”否定詞或多條件組合理解偏差換一種更直接的說法或拆成兩個查詢對比5.2 我實(shí)際踩過的幾個坑第一個坑是時間字段的格式。我最初插入數(shù)據(jù)時用的是2024-3-5這樣不補(bǔ)零的格式導(dǎo)致strftime(%Y-%m, order_time)匹配不到數(shù)據(jù)。排查了很久才發(fā)現(xiàn)是數(shù)據(jù)格式問題。建議統(tǒng)一用YYYY-MM-DD HH:MM:SS否則模型生成的時間條件經(jīng)常對不上。第二個坑是模型把ORDER BY和LIMIT的先后順序?qū)懛?。這不是大問題SQLite 會直接報語法錯誤糾錯循環(huán)能自動修復(fù)。但如果你的場景對延遲敏感可以在提示詞里加一個 few-shot 示例讓模型看一眼正確寫法。第三個坑是字段名大小寫不一致。SQLite 對字段名大小寫不敏感但模型在生成 SQL 時可能一會兒用OrderTime一會兒用order_time。最好的做法是建表時統(tǒng)一使用小寫蛇形命名并在 schema 里保持完全一致減少模型的猜測空間。第四個坑和安全性有關(guān)。我在做校驗(yàn)時只檢查了首個非空關(guān)鍵詞結(jié)果有一次模型生成了WITH x AS (...) DELETE FROM users這種以 WITH 開頭的危險語句第一層校驗(yàn)沒能攔下來。幸好只讀連接兜了底。后來我把關(guān)鍵詞檢查從“是否包含 DELETE”改成了“是否包含以 DELETE 開頭的任何語法結(jié)構(gòu)”并額外檢查了with語句內(nèi)部是否出現(xiàn)寫操作關(guān)鍵詞。5.3 幾個讓效果更穩(wěn)的進(jìn)階小技巧給模型喂 few-shot 樣例是我認(rèn)為性價比最高的優(yōu)化手段。在系統(tǒng)提示詞里加兩組“問題 → SQL”的示例一組是簡單條件查詢一組是 JOIN GROUP BY 聚合查詢。模型只需多讀幾十個 token 的上下文就能少犯很多類型錯誤。第二個技巧是給 schema 加“使用說明”。比如字段里有個price我是在 schema 后面追加一行“price單位是元查詢價格時直接比較數(shù)值即可”。這類業(yè)務(wù)說明模型是從建表語句里猜不出來的但它對查詢語義的準(zhǔn)確度影響極大。第三個技巧是限制最大行數(shù)。除了讓模型默認(rèn)加 LIMIT 50我在執(zhí)行層也會強(qiáng)行限制返回行數(shù)。比如在fetchall后再截斷到 200 行防止因?yàn)槟P蜕蒘ELECT *導(dǎo)致終端被大量數(shù)據(jù)淹沒。第四個技巧是記錄失敗案例并形成小型回歸測試集。每次模型生成錯誤我就把那條自然語言問題、錯誤 SQL、糾正后的 SQL 存到一個 JSON 文件里。后續(xù)修改提示詞時直接用這套測試集重放一看就知道改動是變好還是變差。這個方法讓整個項(xiàng)目從“試出來能用”變成了“可持續(xù)迭代”。結(jié)尾一點(diǎn)個人體會這套查詢助手做到現(xiàn)在最大的收獲不是“能跑”而是我真正理解了 LLM 應(yīng)用里“約束”的價值。模型的能力邊界固然重要但提示詞的結(jié)構(gòu)化、SQL 的校驗(yàn)層、錯誤反饋的閉環(huán)這些工程細(xì)節(jié)才是決定一個工具能不能被真實(shí)使用的關(guān)鍵。我個人的建議是不要一上來就追求復(fù)雜架構(gòu)。先用 SQLite 做底層、用一個模型接口做推理把一條最核心的查詢鏈路跑通再逐步加入緩存、界面、多輪對話。很多看似“簡陋”的原型反而能幫你最清楚地看到模型在哪里犯傻、工程在哪里彌補(bǔ)、產(chǎn)品在哪里加分。后續(xù)如果把這個助手接上 Grafana 或者做成 Web 服務(wù)也只是在這套骨架上加肉而已。最后分享一個使用習(xí)慣我把這個腳本掛在了本地終端別名里平時查任何開發(fā)庫的數(shù)據(jù)都先打一句自然語言試試。查成功了就省一段自己手寫 SQL 的時間查失敗了模型的錯誤往往比搜索引擎的答案更接近問題本身。這套“讓 AI 替你寫第一版 SQL你只做校驗(yàn)和修改”的工作流我已經(jīng)離不開了。