據(jù)庫知識包:從DDL到OKF的工程實踐)
第二次讓 Agent 直接連數(shù)據(jù)庫做查詢它把訂單表和客戶表的關聯(lián)字段猜錯了當天晚上我收到一排告警。那一刻我才想明白把一堆CREATE TABLE丟給 Agent跟把一本沒有目錄、沒有注釋的字典丟給新來的實習生本質(zhì)沒有區(qū)別。后來我花了兩周時間寫了一個 Python 編譯器把 DDL、業(yè)務術(shù)語、字段枚舉、關系強弱這些信息統(tǒng)一編譯成一份Agent 就緒的數(shù)據(jù)庫 OKF 知識包。這套東西不解決“怎么寫 SQL”它解決的是“怎么讓 Agent 在寫 SQL 之前真的讀懂數(shù)據(jù)庫”。這篇文章就把這個項目的完整思路拆開講為什么要從 DDL 里編譯知識包而不是直接讓 Agent 問元數(shù)據(jù)接口、知識包內(nèi)部結(jié)構(gòu)怎么設計、編譯器流水線怎么搭、Agent 側(cè)怎么消費以及我踩過的那些低級的坑。適合正在搭 Agent 數(shù)據(jù)助手、或者準備把數(shù)據(jù)庫能力開放給大模型工具鏈的人參考。1. 為什么 Agent 接數(shù)據(jù)庫之前先要有一份 OKF 知識包先說一個最直接的體感。讓 Agent 調(diào)用數(shù)據(jù)庫工具時大多數(shù)實現(xiàn)就是把表名、列名、類型、主鍵外鍵這些元數(shù)據(jù)拼進 prompt或者讓 Agent 自己SHOW TABLES之后去猜。我的項目前三天就是這么干的效果非常糟糕。1.1 表結(jié)構(gòu)信息與業(yè)務語義之間的斷層數(shù)據(jù)庫表結(jié)構(gòu)天然缺三塊東西第一塊是字段的業(yè)務含義。customer_id到底是下單客戶還是收貨聯(lián)系人status字段是 0 表示正常還是 1 表示正常這類信息在 DDL 里往往只有一個字段名再看一眼類型沒了。Agent 面對這種信息缺口它只會靠大模型預訓練時積累的“通用常識”去腦補而通用常識和數(shù)據(jù)倉庫的真實業(yè)務往往不是一回事。第二塊是枚舉值和取值邏輯。很多表用 tinyint 存狀態(tài)DDL 里根本不寫0 代表什么、1 代表什么。Agent 一旦猜錯生成的 SQL 雖然語法完全正確查出來的業(yè)務口徑卻是全錯的。這類問題比語法報錯隱蔽得多因為SQL跑得通、結(jié)果也非空但就是不對。第三塊是表與表之間的真實關系。外鍵約束寫得很清楚的庫是少數(shù)更多場景里兩個表靠一個名叫code的字段軟關聯(lián)業(yè)務上卻是一對多。Agent 看不到業(yè)務約束就很容易把關聯(lián)方向搞反或者在一張寬表上本來能直接過濾它偏要去 join 一張外表。1.2 直接拼 DDL 為什么不經(jīng)濟有人會說那把 DDL 全文貼進 prompt 不就行了我也試過。一個稍微規(guī)范一點的庫十來張表DDL 加起來幾千行很正常。塞進上下文之后Agent 確實能“看到”字段但注意它看到的是純物理結(jié)構(gòu)不是認知結(jié)構(gòu)。DDL 里有大量 Agent 不需要關心的內(nèi)容存儲配置、索引定義、字符集、分區(qū)策略。這些內(nèi)容不僅浪費 token還會干擾它對核心語義的聚焦。還有一個問題物理結(jié)構(gòu)有歧義。同一條 MySQL 的CREATE TABLE語句在不同版本里字段類型寫法不一樣注釋可能寫了一大段也可能完全沒有。你無法保證 Agent 每次能從這些原始文本里穩(wěn)定提取出同樣的信息。而知識包提供的是經(jīng)過清洗、歸一化、補全之后的確定性產(chǎn)物同一份 DDL 輸入永遠編譯出同一份知識包。這樣你至少能控制 Agent 拿到手的數(shù)據(jù)庫畫像是一致的不會因為模型心情不同而對同一張表產(chǎn)生兩種理解。所以這個項目我給自己定的目標是把數(shù)據(jù)庫知識的構(gòu)建從“讓模型臨場發(fā)揮”變成“預先編譯、按需檢索”。這也是 “Agent 就緒的數(shù)據(jù)庫 OKF 知識包” 這個名字的由來——先有格式再有編譯器最后才談 Agent。2. OKF 知識包長什么樣我給數(shù)據(jù)結(jié)構(gòu)定了哪些規(guī)矩OKF 是我在項目內(nèi)部給這套知識包格式起的代號全稱是 Open Knowledge Format。它不是公開標準而是我根據(jù)“Agent 消費數(shù)據(jù)庫知識”這個具體場景定制的一套 JSON 約定。為什么不用現(xiàn)成的 schema registry 或者說元數(shù)據(jù)模型因為那些模型面向的是工程師不是面向“需要理解語義的模型”。2.1 格式選型為什么是 JSON 渲染模板我一開始考慮過直接用 Markdown 文件每一張表寫一段說明。優(yōu)點是寫起來快但缺點是沒法程序化校驗、沒法按字段檢索、沒法穩(wěn)定渲染成不同長度的上下文。后來又試了 YAMLYAML 寫起來比 JSON 舒服但在代碼里做 schema 校驗、嵌套校驗時Pydantic 和 JSON 的配合最順。所以最終定的是JSON 作為知識包底層的“知識存儲格式”再加上一組渲染模板把 JSON 渲染成 Agent 真正讀到的文本片段。這里要區(qū)分兩個概念知識包是結(jié)構(gòu)化的底稿也就是 JSONAgent 消化的是渲染后的文本。底稿負責精確和無歧義渲染負責可讀和節(jié)省 token。你在 prompt 里給 Agent 看的永遠是不超過幾百字的渲染結(jié)果而不是把整個 JSON 丟過去。2.2 一張表的知識包長什么樣一個最小可用的知識包大致長這樣{ db: shop, version: 20250601, checksum: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08, tables: [ { name: customers, schema: public, comment: 客戶主數(shù)據(jù)一客戶一行, fields: [ { name: id, type: bigint, nullable: false, comment: 客戶唯一標識, aliases: [customer_id, user_id], enum_values: null, sensitive: false }, { name: status, type: tinyint, nullable: true, comment: 客戶狀態(tài)枚舉, enum_values: { 0: 正常, 1: 已凍結(jié), 2: 已注銷 }, sensitive: false }, { name: email, type: varchar(128), nullable: true, comment: 登錄郵箱個人敏感信息, aliases: [mail], enum_values: null, sensitive: true } ], pk: [id] } ], relationships: [ { from: {table: orders, field: customer_id}, to: {table: customers, field: id}, cardinality: many-to-one } ], glossary: [ { term: 活躍客戶, definition: 近30天內(nèi)至少下單1次的客戶, tables: [customers, orders] } ], sample_queries: [ { scenario: 統(tǒng)計本月成交額, sql: select sum(amount) from orders where created_at date_trunc(month, current_date) } ], meta: { generated_by: okf_compiler, source_ddl: ddl/shop_20250601.sql } }這里每個字段項都盡量包含四個要素屬性定義、類型約束、業(yè)務注釋、候選別名。尤其aliases字段是給 Agent 用的——用戶可能會說“客戶ID”“用戶ID”術(shù)語映射全部提前在知識包里做好Agent 就不用自己推斷。字段里的sensitive標記也很重要。它不是為了阻止 Agent 使用字段而是讓 Agent 在生成 SQL 時意識到這個字段涉及隱私輸出結(jié)果時可以提示用戶脫敏。后面講 Agent 消費時會再展開。2.3 為什么關系必須顯式聲明只把每張表的信息做成 JSON 是不夠的Agent 經(jīng)常需要對多表 join而 join 恰恰是幻覺重災區(qū)。我在知識包里單拎了一個relationships數(shù)組每條關系表達四類信息關聯(lián)方向、關聯(lián)字段、基數(shù)、業(yè)務約束。一開始我以為從外鍵約束里提取關系就行結(jié)果發(fā)現(xiàn)很多線上表根本沒有外鍵兩個表之間的關聯(lián)純粹是業(yè)務約定。后來我在編譯器里加了一個補充輸入文件讓人維護這些說明性的關系聲明。這是知識包區(qū)別于“自動抓取元數(shù)據(jù)”的關鍵自動抓取只能告訴你有什么知識包還要告訴你“為什么這兩個表能關聯(lián)”“關聯(lián)后統(tǒng)計口徑是什么”。這一層語義哪怕再強的解析器也猜不出來必須有人的輸入或已有的設計文檔參與。3. Python 編譯器的三層流水線DDL 到知識包之間發(fā)生了什么知識包的格式定了之后緊接著的問題是怎么生成。手工維護 JSON 不是不行但數(shù)據(jù)庫一變知識包就過時人很難每次都記得同步。所以我把整套流程做成了編譯器輸入是 DDL 和補充說明文件輸出是知識包 JSON 和一份渲染好的上下文索引。3.1 流水線概覽編譯器內(nèi)部是一個純函數(shù)式的流水線一端進 SQL 文本另一端出知識包中間不訪問數(shù)據(jù)庫。這么做的好處是可復現(xiàn)、可測試、不依賴環(huán)境。你只要把同一批輸入文件放進去任何時候跑出來的知識包都一模一樣。我把流水線分成三層第一層是解析層負責把CREATE TABLE這種 DDL 拆成 AST抽取出表名、字段名、類型、可空性、默認值、注釋、主鍵、外鍵。這一層不負責理解業(yè)務語義只負責把物理結(jié)構(gòu)變成中間表示。第二層是豐富層把解析結(jié)果和人工補充的語義說明合并。比如在補充文件里寫著customers.status 字段枚舉0 正常 1 凍結(jié) 2 注銷編譯器就把這段文本翻譯成結(jié)構(gòu)化的enum_values掛到對應字段上。這層還負責做歸一化字段別名統(tǒng)一小寫、全角轉(zhuǎn)半角、注釋去重等。第三層是輸出層把完整模型序列化成知識包 JSON同時用渲染模板生成面向 Agent 的文本片段最后計算整個知識包的 checksum 寫入meta字段。3.2 項目模塊結(jié)構(gòu)編譯器本身是一個 Python 包典型布局如下okf_compiler/ ├── cli.py # 命令行入口 ├── parser_sql.py # 解析 DDL 的 token/AST 邏輯 ├── models.py # Pydantic 數(shù)據(jù)模型對應知識包 schema ├── enrich.py # 合并人工語義說明、校驗一致性 ├── renderer.py # 渲染 Agent 可讀的上下文文本 ├── validator.py # 知識包合法性校驗 └── artifact/ └── templates/ ├── table.j2 # 單表文本模板 └── relationship.j2 # 關系文本模板為什么用 Python 而不是 Node 或者 Go原因很務實Python 生態(tài)里sqlparse、pydantic、jinja2這三個庫加起來幾乎覆蓋了解析、校驗、渲染的全部需求。sqlparse 能粗粒度地把 SQL 拆成語句和 token 流雖然它不做完整的 AST 語義分析但對付建表語句已經(jīng)夠用。pydantic 能給知識包做嚴格的類型校驗——字段類型寫錯、枚舉值類型不匹配編譯期就會報錯而不是等到 Agent 用的時候才暴露。jinja2 負責把結(jié)構(gòu)化數(shù)據(jù)渲染成文本模板模板里可以控制講多少細節(jié)、用多大篇幅。這里還有一個容易忽略的設計點編譯器必須是無狀態(tài)的。我沒有在項目里引入任何數(shù)據(jù)庫連接沒有在運行時去SHOW COLUMNS。為什么不呢第一很多數(shù)據(jù)庫權(quán)限受限Agent 的賬號可能根本沒有讀元數(shù)據(jù)的權(quán)限第二DDL 文件本身就是事實來源之一如果運行時再查一遍庫兩份事實不一致時你根本不知道以誰為準。凡是加入不確定性的環(huán)節(jié)都會讓后續(xù)排查變得困難。4. 核心代碼實戰(zhàn)從建表語句里蒸出知識片段接下來進入真正能抄的部分。下面這幾段代碼就是我項目里最核心的編譯邏輯簡略了很多錯誤處理但骨架是完整的。4.1 解析一條 CREATE TABLE解析我首選sqlparse。它的 AST 不如商業(yè)級解析器那么深但好處是容錯性好不會因為一兩個語法怪癖直接崩掉。做一個 DDL 編譯器穩(wěn)定比完整更重要。import sqlparse from sqlparse.sql import IdentifierList, Identifier from sqlparse.tokens import Keyword, Name, Punctuation def extract_create_table(statements): for stmt in statements: if not stmt.get_type() CREATE: continue tokens [t for t in stmt.tokens if not t.is_whitespace] table_name None columns [] started_columns False for token in tokens: if token.match(Keyword, TABLE): # 下一個非關鍵字的 token 通常就是表名 for t in tokens: if isinstance(t, Identifier) and not table_name: table_name t.get_real_name() continue if token.match(Punctuation, (): started_columns True elif started_columns: if isinstance(token, IdentifierList): for item in token.get_identifiers(): cols _extract_column(item) if cols: columns.append(cols) elif isinstance(token, Identifier): cols _extract_column(token) if cols: columns.append(cols) if table_name: yield {table: table_name, columns: columns} def _extract_column(identifier): # 以 status tinyint 這類簡單字段為主太復雜的語法暫時跳過 tokens [t for t in identifier.tokens if not t.is_whitespace] name tokens[0].value if tokens else None type_token for t in tokens[1:]: if t.is_keyword and t.value.upper() in (NOT, NULL, DEFAULT, COMMENT, PRIMARY, KEY, UNIQUE): break type_token t.value return {name: name, type: type_token.strip().lower()}這段代碼不追求解析完美主打“常見建表語句都能被拆干凈”。我建議你設計解析邏輯時只承諾四種能力取到表名、取到字段名、取到字段類型、取到字段注釋。主鍵、外鍵、默認值這些信息能解析出來就解析解析不出來的寧可交給人工補充文件也不要在解析器里硬寫一堆正則去猜。4.2 業(yè)務語義注入光有 DDL 遠遠不夠解析完只是拿到了結(jié)構(gòu)下一步要把業(yè)務術(shù)語掛載上去。我專門維護了一個enrich.yaml文件讓會寫 SQL 但不想動代碼的同事也能往里加業(yè)務描述tables: customers: comment: 客戶主數(shù)據(jù)一客戶一行 fields: status: comment: 客戶狀態(tài)枚舉 enums: 0: 正常 1: 已凍結(jié) 2: 已注銷 email: aliases: [mail] sensitive: true relationships: - from: orders.customer_id to: customers.id cardinality: many-to-one note: 訂單表通過 customer_id 指向客戶主數(shù)據(jù)不允許存在孤兒訂單 glossary: - term: 活躍客戶 definition: 近30天內(nèi)至少下單1次的客戶 related_tables: [customers, orders]然后是豐富層合并代碼from pydantic import BaseModel class FieldMeta(BaseModel): name: str type: str nullable: bool True comment: str | None None aliases: list[str] [] enum_values: dict[str, str] | None None sensitive: bool False class TableMeta(BaseModel): name: str schema: str | None None comment: str | None None fields: list[FieldMeta] pk: list[str] [] def merge_meta(parsed_table: dict, enrich_cfg: dict) - TableMeta: table_name parsed_table[table] cfg enrich_cfg[tables].get(table_name, {}) field_cfgs cfg.get(fields, {}) fields [] for raw_field in parsed_table[columns]: fcfg field_cfgs.get(raw_field[name], {}) fields.append( FieldMeta( nameraw_field[name], typeraw_field[type], commentfcfg.get(comment), aliasesfcfg.get(aliases, []), enum_valuesfcfg.get(enums), sensitivefcfg.get(sensitive, False), ) ) return TableMeta( nametable_name, schemacfg.get(schema), commentcfg.get(comment), fieldsfields, pkcfg.get(pk, []), )這一段干的事很樸素但我認為這是整個項目里價值密度最高的一段把人類腦子里的業(yè)務約定轉(zhuǎn)成機器可讀的數(shù)據(jù)。沒這一步知識包和普通的information_schema導出的元數(shù)據(jù)就沒有本質(zhì)區(qū)別。4.3 渲染成 Agent 直接消費的文本知識包里面存儲用 JSON但真正喂給 Agent 的是渲染后的文本。渲染模板我放在 jinja2 里保存成table.j2## Table: {{ table.name }}{{ table.schema or public }} {{ table.comment or }} 字段列表 {% for f in table.fields -%} - {{ f.name }}{{ f.comment or 暫無說明 }}{{ f.type }}{% if not f.nullable %}非空{(diào)% else %}可空{(diào)% endif %} {% if f.enum_values %} 枚舉值 {% for k, v in f.enum_values.items() %} - {{ k }} {{ v }} {% endfor %} {% endif %} {% if f.aliases %} 別稱{{ f.aliases | join(, ) }}{% endif %} {% if f.sensitive %} 敏感字段生成 SQL 與展示結(jié)果時須提示脫敏{% endif %} {% endfor %} 關聯(lián)關系 {% for r in table.relations -%} - {{ r.from.table }}.{{ r.from.field }} - {{ r.to.table }}.{{ r.to.field }}{{ r.cardinality }} {% endfor %}渲染出來的效果就是 Agent 真正讀到的一段文本比如## Table: customerspublic 客戶主數(shù)據(jù)一客戶一行 字段列表 - id客戶唯一標識bigint非空 - status客戶狀態(tài)枚舉tinyint可空 枚舉值 - 0 正常 - 1 已凍結(jié) - 2 已注銷 - email登錄郵箱個人敏感信息varchar(128)可空 別稱mail 敏感字段生成 SQL 與展示結(jié)果時須提示脫敏 關聯(lián)關系 - orders.customer_id - customers.idmany-to-one這段文本的核心價值在于它完全貼近“一個數(shù)據(jù)庫 DBA 給新人講解業(yè)務時說的話”而不是SHOW CREATE TABLE吐出來的冷冰冰的物理定義。5. 讓 Agent 真正用起來知識包的檢索與上下文裝配知識包編譯出來了不接進 Agent 的調(diào)用鏈路里就等于白做。我項目里接的方式不是全文塞 prompt而是按需檢索。Agent 需要知道哪張表的信息才把哪張表的知識片段取出來。5.1 上下文太長按需檢索假設你有 20 張表渲染后的全文可能有 5000 個 token。全部塞進系統(tǒng)提示Agent 會長篇大論地注意到無關表還浪費預算。正確的做法是把每張表的知識片段作為一個獨立的“檢索單元”用戶提問時先做一次粗粒度檢索只取最相關的三五張表。我自己用的是純 Python 實現(xiàn)的輕量檢索沒有上向量數(shù)據(jù)庫。為什么知識包本身是高度結(jié)構(gòu)化的文本關鍵詞重疊度已經(jīng)能匹配得很好用戶問“本月活躍客戶”分詞后命中的是“活躍客戶”“客戶”“customers”這個信號足夠強。向量檢索適合語義距離遠但表達相似的內(nèi)容而數(shù)據(jù)庫知識包恰恰要避免這種模糊匹配。簡單方案可控、無額外服務對于中小規(guī)模的庫完全夠用。檢索的輸入輸出很像一個工具函數(shù)def retrieve_knowledge_package(query: str, index: dict, top_k: int 3) - list[str]: tokens tokenize(query) scored [] for table_name, block in index.items(): score sum(1 for t in tokens if t in block.lower()) scored.append((score, table_name, block)) scored.sort(reverseTrue, keylambda x: (x[0], len(x[1]))) return [block for _, _, block in scored[:top_k] if _ 0]這段代碼沒什么黑魔法。但它把關鍵的一件事做了讓 Agent 在收到具體任務之前已經(jīng)拿到它應該看哪幾張表的提示。5.2 把知識包掛進工具函數(shù)與提示詞僅僅檢索還不夠要讓 Agent 在工具調(diào)用時真正“想到”去用。我注冊給 Agent 的工具有兩個def get_table_context(table_name: str) - str: 返回指定表的業(yè)務語義、字段枚舉、關聯(lián)關系等知識包片段 return render_table_block(table_name) def run_sql(sql: str) - list[dict]: 執(zhí)行只讀 SQL 查詢禁止修改操作 ...在系統(tǒng)提示詞里我會寫清楚使用順序先調(diào)用get_table_context獲取相關表的上下文再基于上下文寫 SQL最后調(diào)用run_sql。這是很典型的 ReAct 模式但關鍵不在于模式本身而在于get_table_context返回的內(nèi)容質(zhì)量。如果它返回的只是字段列表Agent 依然要猜如果返回的是帶枚舉、帶別名、帶關系提醒的知識片段Agent 寫出錯誤 join 的概率就會顯著下降。我再補一個經(jīng)常被忽略的細節(jié)知識包里不應包含真實數(shù)據(jù)只能包含結(jié)構(gòu)和語義。真實數(shù)據(jù)可能涉及隱私而且體積不可控。知識包只做“地圖”Agent 運行 SQL 之后拿到的結(jié)果才是“現(xiàn)場”。地圖和現(xiàn)場分離權(quán)限控制和數(shù)據(jù)安全都好做很多。6. 踩坑記錄與邊界控制什么情況下別硬上編譯器最后這部分是最想分享的。項目整體跑通不難但中間有不少決策如果重新來一遍我會做得更果斷。6.1 解析 SQL 的“80% 原則”第一個坑是過度追求解析器的完整度。我一開始想讓解析器支持存儲生成列、分區(qū)表、復雜默認表達式、索引定義結(jié)果一周時間全耗在這個上面真正的知識包結(jié)構(gòu)反而沒怎么動。后來我把解析目標砍到只剩四件事表名、字段名、字段類型、基礎注釋。凡是解析不了的直接跳過并打一條 warning在編譯日志里標出來讓人工補充文件去兜底。這里分享一個判斷標準知識包的錯誤容忍策略應該和 Agent 的容錯能力匹配。Agent 本身就很擅長從自由文本里抓重點你不需要給它一個 100% 精確的 AST你只需要給它 80% 的準確結(jié)構(gòu)加上 20% 的人工兜底效果就會好過追求完美解析。把精力花在補全業(yè)務語義上回報比高得多。6.2 包失效與重建策略checksum 和 CI第二個坑是知識包不同步。數(shù)據(jù)庫的 DDL 一改知識包還是舊版本Agent 拿到舊信息去查新表必然出錯。我用兩招解決。第一招是給知識包打 checksum。編譯時把所有輸入文件拼接后算一個哈希存在知識包meta.checksum里。每次 Agent 加載知識包時先核對發(fā)現(xiàn)不對就提示“知識包已過期需要重新編譯”。這一步成本極低但能避免很多詭異的線上問題。第二招是把編譯過程接進 CI。我現(xiàn)在的做法是數(shù)據(jù)庫的 DDL 遷移腳本一提交流水線自動跑一次編譯器。編譯失敗或者 checksum 變化都會在合并請求里直接標出來。這樣知識包始終跟隨數(shù)據(jù)庫結(jié)構(gòu)版本走而不是靠某個人想起來手動更新。變更類型知識包是否需要重建說明新增一張表需要新表可能被 Agent 需要新增/刪除字段需要字段列表變化修改字段注釋/枚舉需要語義變化是重構(gòu)核心只改索引或分區(qū)不需要Agent 不需要感知物理優(yōu)化只有數(shù)據(jù)量變化不需要結(jié)構(gòu)層知識包不存數(shù)據(jù)統(tǒng)計6.3 什么時候不要搞知識包編譯器最后一個建議可能有點反直覺表數(shù)量很少、結(jié)構(gòu)非常穩(wěn)定的項目不要上編譯器。如果是五六張表手動寫 JSON 或 Markdown 可能只需要半天而編譯器需要寫解析邏輯、寫合并邏輯、寫渲染模板、配 CI整套下來怎么也要一兩周。我判斷是否值得搞知識包編譯器的三個條件表數(shù)量超過兩位數(shù)表結(jié)構(gòu)在持續(xù)演進你確實要把數(shù)據(jù)庫能力開放給 Agent 做自動化查詢。三個條件至少滿足兩個才值得投入。如果只是給一個固定報表的數(shù)據(jù)庫接個問答 Demo那直接把業(yè)務口徑寫成一小段提示詞塞進系統(tǒng)提示里比做知識包高效得多。反過來如果目標是讓 Agent 自主探索一個持續(xù)變化的數(shù)據(jù)倉庫那么沒有知識包的 Agent 就是一臺沒有地圖的自動駕駛車遲早撞墻。我個人的體會是這個項目最有價值的部分不是那幾千行 Python 代碼而是它逼著我把數(shù)據(jù)庫的“隱性知識”顯式化了。過去 DBA 腦子里那點東西——哪個字段是敏感字段、哪張表和哪張表能用軟關聯(lián)、字段枚舉到底什么含義——現(xiàn)在全部變成了一份可以版本管理、可以自動校驗、可以隨時渲染給 Agent 看的知識包。從此 Agent 學到的不是猜出來的表結(jié)構(gòu)而是這個數(shù)據(jù)庫真實運行的業(yè)務規(guī)則。如果你也在做類似的事情建議先別急著調(diào)大模型先把數(shù)據(jù)庫知識管好后面所有環(huán)節(jié)都會輕松很多。