首页/新闻资讯/正文详情

如何让 LLM 通过反思提升 SQL 正确率:以 Gemini + sqlite 为例,配 TaoToken 统一 Key 跑通 Agent Reflection

发布时间:2026/9/25 14:02:19 来源:云帆数科 栏目:资讯中心
如何让 LLM 通过反思提升 SQL 正确率:以 Gemini + sqlite 为例,配 TaoToken 统一 Key 跑通 Agent Reflection
1. 为什么 Gemini 写 sqlite SQL 总在“看起来对”的地方翻车你让 Gemini 写一句 sqlite SQL它给你一段语法完全合法、跑起来也不报错的查询但结果就是不对。比如问“NBA 球员体重的中位数是多少”它给你SELECT WT FROM nba_roster WHERE WT ! ORDER BY REPLACE(WT, lbs, ) LIMIT 1 OFFSET (...) / 2执行没报错返回的却是一个带lbs后缀的字符串根本不是数值中位数。这就是 LLM 生成 SQL 最典型的失败模式语法正确、语义错误、静默返回幻觉结果。Agent Reflection智能体反思要解决的就是这件事。它的核心思路特别朴素先让模型出第一版 SQL拿去真库执行把执行结果或报错原样回灌给模型让它自己判断“这版查询有没有回答原问题”然后重写。这跟人写 SQL 的习惯一模一样——先写一版跑一下看结果对不对不对就改。区别在于反思闭环把这个“跑一下、看一眼、改一版”的循环自动化了而且可以重复多轮。这篇面向的是已经在用 Gemini 或类似模型做 Text-to-SQL、但被首轮正确率折磨的开发者。我会用 sqlite 的nba_roster.db做例子把 Agent Reflection 的闭环拆成可复制的代码同时用 TaoToken 统一 Key 管理模型调用避免在多个 API Key 和 base_url 之间来回切换。目标很明确把首轮正确率提升到可观测、可复现的稳定水平而不是靠运气。2. TaoToken 前置统一 Key 与 base_url 的配置骨架在写反思逻辑之前先把模型调用的入口统一掉。我试过在代码里硬编码 Gemini 的 API Key 和 base_url一旦要换模型或加一个备用模型就得改多处。TaoToken 的作用是提供一个统一的 OpenAI 兼容入口你只需要维护一个 Key 和一个 base_url模型名通过参数切换。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不加 UTM 参数。你需要先去控制台创建一个 API Key然后把它写进环境变量或配置文件。2.1 config.toml 骨架如果你用 Python 项目推荐把配置放在config.toml里用tomllibPython 3.11或tomli读取[llm] base_url https://taotoken.net/api api_key sk-your-taotoken-key model gemini-2.0-flash max_tokens 512 temperature 0.0 [reflection] max_rounds 3 execute_before_reflect truetemperature 0.0很关键。反思场景要的是稳定复现不是创意发散。温度调高会让模型在重写 SQL 时引入不必要的“发挥”反而降低收敛速度。2.2 settings.json 骨架如果你用 Node.js 或需要跨语言共享配置settings.json版本如下{ llm: { baseUrl: https://taotoken.net/api, apiKey: sk-your-taotoken-key, model: gemini-2.0-flash, maxTokens: 512, temperature: 0.0 }, reflection: { maxRounds: 3, executeBeforeReflect: true } }两个配置文件的字段一一对应选你项目顺手的那份即可。Key 不要提交到 Git用.env或本地覆盖文件管理。2.3 读取配置并初始化客户端import tomllib import openai with open(config.toml, rb) as f: cfg tomllib.load(f) client openai.OpenAI( api_keycfg[llm][api_key], base_urlcfg[llm][base_url], ) MODEL cfg[llm][model] MAX_ROUNDS cfg[reflection][max_rounds]到这里模型调用的入口就统一了。后面无论跑首轮生成还是反思重写都走同一个client换模型只改config.toml里的model字段。3. 可复制配置sqlite 建表、schema 注入与首轮 SQL 生成反思闭环要能跑起来前提是有一个真实的 sqlite 库和一份清晰的 schema 描述。schema 描述的质量直接决定首轮 SQL 的准确率这一步不能省。3.1 建表与造数据import sqlite3 conn sqlite3.connect(./nba_roster.db) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS nba_roster ( Team TEXT, NAME TEXT, Jersey TEXT, POS TEXT, AGE INTEGER, HT TEXT, WT TEXT, COLLEGE TEXT, SALARY TEXT ) ) cur.executemany( INSERT INTO nba_roster VALUES (?,?,?,?,?,?,?,?,?), [ (Toronto Raptors, Otto Porter Jr., 0, PF, 22, 6 7\, 232 lbs, Michigan, $9,945,830), (Boston Celtics, Jayson Tatum, 0, SF, 25, 6 8\, 210 lbs, Duke, $32,600,060), (Denver Nuggets, Nikola Jokic, 15, C, 28, 6 11\, 284 lbs, --, $47,607,350), ], ) conn.commit()注意WT字段是TEXT类型值形如232 lbs这就是后面中位数查询翻车的根源——模型需要先剥离单位再转数值。3.2 schema 描述函数def get_schema(): return 0|Team|TEXT eg. Toronto Raptors 1|NAME|TEXT eg. Otto Porter Jr. 2|Jersey|TEXT eg. 0 and when null has a value NA 3|POS|TEXT eg. PF 4|AGE|INT eg. 22 in years 5|HT|TEXT eg. 6 7 or 6 10 6|WT|TEXT eg. 232 lbs and when null has a value -- 7|COLLEGE|TEXT eg. Michigan and when null has a value -- 8|SALARY|TEXT eg. $9,945,830 and when null has a value -- 把每个字段的类型、示例值、空值表示都写清楚模型生成 SQL 时才有依据。这一步偷懒反思阶段就要花更多轮次去补。3.3 首轮 SQL 生成def make_message(user, system): messages [] if system: messages.append({role: system, content: system}) messages.append({role: user, content: user}) return messages def get_llm_sql(messages): resp client.chat.completions.create( modelMODEL, messagesmessages, max_tokenscfg[llm][max_tokens], temperaturecfg[llm][temperature], ) text resp.choices[0].message.content if sqlite in text: return text.replace(sqlite\n, ).replace(\n, ).strip() if sql in text: return text.replace(sql\n, ).replace(\n, ).strip() return text.strip() question What is the median weight in the NBA? system fYou are an NBA analyst with 15 years of experience writing complex SQL queries. Consider the nba_roster table with the following schema: {get_schema()} Write a sqlite query to answer the following question. Follow instructions exactly. messages make_message(question, system) sql_v1 get_llm_sql(messages) print(首轮 SQL:, sql_v1)首轮输出大概率是类似SELECT WT FROM nba_roster WHERE WT ! ORDER BY REPLACE(WT, lbs, ) LIMIT 1 OFFSET (...) / 2的查询。语法没问题但WT是带单位的字符串排序和取中位数的逻辑都不对。4. 验证请求执行、报错捕获与反思回灌反思闭环的关键动作是“执行 回灌”。执行结果无论是报错还是错误数据都要作为上下文喂回模型。4.1 执行并捕获结果import pandas as pd def execute_sql(sql): try: df pd.read_sql(sql, conconn) return df, None except Exception as e: return None, str(e) df_v1, err_v1 execute_sql(sql_v1) print(首轮执行结果:) print(df_v1 if err_v1 is None else err_v1)首轮执行不报错但返回的是一个带lbs的字符串不是数值中位数。这就是需要反思的信号执行成功不等于答案正确。4.2 构造反思消息并重写def reflect_and_rewrite(question, sql, result, error, system): if error: feedback fQuery: {sql}\nExecution error: {error}\nThis query failed. Write a corrected sqlite query. else: feedback ( fQuestion: {question}\n fQuery: {sql}\n fExecution result: {result.to_string()}\n fThis result does not correctly answer the question. fWrite a corrected sqlite query that returns the numeric median weight. ) messages make_message(feedback, system) return get_llm_sql(messages) sql_v2 reflect_and_rewrite(question, sql_v1, df_v1, err_v1, system) print(反思后 SQL:, sql_v2)反思提示词里要把原问题、上一版 SQL、执行结果或报错三样都带上。模型看到“返回的是字符串不是数值”这个事实才会去写CAST(SUBSTR(WT, 1, INSTR(WT, )) AS INTEGER)这类转换逻辑。4.3 多轮反思循环def run_reflection_loop(question, system, max_rounds3): messages make_message(question, system) sql get_llm_sql(messages) history [] for i in range(max_rounds): df, err execute_sql(sql) history.append({round: i, sql: sql, error: err, rows: None if df is None else len(df)}) if err is None and df is not None and len(df) 0: # 这里可以加一个答案校验函数判断是否真的回答了问题 pass sql reflect_and_rewrite(question, sql, df, err, system) return sql, history final_sql, history run_reflection_loop(question, system, MAX_ROUNDS) print(最终 SQL:, final_sql) df_final, err_final execute_sql(final_sql) print(df_final if err_final is None else err_final)max_rounds建议设 3。实测下来大多数语义错误在 1 到 2 轮反思内就能收敛设太多轮反而可能让模型在“过度修正”里绕圈。5. 本篇常见错排查反思不生效的六个坑反思闭环跑不通通常不是模型能力问题而是工程细节没对齐。下面这几个坑我基本都踩过。坑一schema 描述太粗。只写字段名不写类型和示例值模型不知道WT是带单位的字符串首轮就会写出错误的排序逻辑。把示例值写进 schema首轮正确率能明显提升。坑二反思提示词没带执行结果。只告诉模型“这版不对重写”模型没有判断依据重写出来的 SQL 可能换汤不换药。必须把df.to_string()或报错信息原样带上。坑三temperature 没调低。反思阶段需要稳定收敛温度高于 0.3 会让模型在重写时引入随机变化多轮之间不收敛。设成 0.0。坑四把执行成功当成答案正确。这是最隐蔽的坑。pd.read_sql不报错不代表结果回答了问题。需要在反思提示词里明确写出“结果没有正确回答问题”让模型自己判断语义。坑五max_tokens 太小。反思消息里带了执行结果上下文变长如果max_tokens还是 100模型可能截断输出SQL 不完整。设成 512 以上。坑六base_url 或 Key 配错。如果客户端初始化时base_url写成了别的地址或者 Key 失效首轮请求就会失败反思逻辑根本没机会跑。先用一个最简单的client.chat.completions.create验证连通性再跑完整闭环。排查顺序建议先确认客户端能通再确认 schema 描述完整然后确认反思提示词带了执行结果最后调 temperature 和 max_tokens。6. 语义一致 CTA把统一 Key 接进你的 Agent 工作流反思闭环跑通之后下一步是把它接进你日常的 Agent 工作流。如果你还在手动管理多个模型的 Key 和 base_url建议先把入口统一到 TaoToken再去控制台创建 API Key然后按接入文档把base_url和 Key 写进config.toml或settings.json。需要创建和管理 Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite需要看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite想先在网页里验证模型对话效果https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite长期跑编码或 Agent 任务考虑 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite如果你用的是 Claude Code 或 Anthropic 风格的调用对应的接入入口在这里https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite把反思循环里的client换成统一入口之后你可以在config.toml里一行切换模型对比 Gemini 和其他模型在同一个 sqlite 任务上的首轮正确率和反思收敛轮数。这才是把“首轮正确率提升到可观测稳定水平”落地的关键——不是靠某一次调参而是靠可复现的配置和可对比的模型入口。

相关推荐

2026 国际写作竞赛盘点:用 TaoToken 统一 Key 打通 AI 辅助写作工作流
2026 国际写作竞赛盘点:用 TaoToken 统一 Key 打通 AI 辅助写作工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 14:02:19

AI时代开发者进化论:用TaoToken统一Key打通Claude Code与Agent工作流,像CEO一样指挥“一人军队”
AI时代开发者进化论:用TaoToken统一Key打通Claude Code与Agent工作流,像CEO一样指挥“一人军队”

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 14:02:13

openclaw 接入 minimax 的 config.toml 骨架与 TaoToken 统一 Key 配置
openclaw 接入 minimax 的 config.toml 骨架与 TaoToken 统一 Key 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 14:02:13

Atlas 300V 24G推理加速卡YOLO模型部署实战经验总结
Atlas 300V 24G推理加速卡YOLO模型部署实战经验总结

“atlas 300v 24g 是运算加速卡吗”这个问题,最近在好几个技术群里都有人问。我现在可以明确地说:是的,它是一张推理加速卡,而且是昇腾生态里相当能打的一张。如果你的工作流里刚好有YOLO系列模型的部署需求,这块卡能让… · 2026/9/25 14:37:00

gsd-core 测试治理实践:Phase Lifecycle 测试集群 20→4 合并与 lint-test-file-count 身份棘轮机制
gsd-core 测试治理实践:Phase Lifecycle 测试集群 20→4 合并与 lint-test-file-count 身份棘轮机制

【免费下载链接】gsd-core Git. Ship. Done - Core 项目地址: https://gitcode.com/gh_mirrors/ge/gsd-core 点击查看 免费下载 本篇以 gsd-core 仓库归档 changeset(.changeset/archived/3740-consolidate-phase-tests.md)为核心&#xff0… · 2026/9/25 14:36:54

Atlas 300V 24G实战:YOLOv8推理迁移与性能调优全指南
Atlas 300V 24G实战:YOLOv8推理迁移与性能调优全指南

如果你最近在关注国产AI算力,肯定绕不开“Atlas”这个词。但有朋友问我最多的一个问题是:Atlas 300V 24G到底算不算运算加速卡?它和我们熟悉的GPU推理卡有什么区别?更关键的是,网上铺天盖地的YOLO部署教程都是针对NVID… · 2026/9/25 14:36:54

BentoML 部署打包指南:从定义运行时环境到构建 Bento 与容器化发布
BentoML 部署打包指南:从定义运行时环境到构建 Bento 与容器化发布

模型推理服务人工智能后端大模型MLOpsLLMOps 【免费下载链接】BentoML The easiest way to serve AI apps and models - Build Model Inference APIs, Job queues, LLM apps, Multi-model pipelines, and more! 项目地址: https://gitcode.com/gh_mirrors/be/BentoM… · 2026/9/25 14:36:17

Wireless_Toolkit:用浏览器操控 Kali 无线安全测试工具(Wi-Fi + 蓝牙)
Wireless_Toolkit:用浏览器操控 Kali 无线安全测试工具(Wi-Fi + 蓝牙)

目录1. 整体架构2. 硬件与任务槽3. 安全约束3.1 绝不碰 wlan03.2 提权白名单3.3 子进程安全4. 硬件检测页5. Wi-Fi 功能5.1 扫描5.2 抓握手5.3 抓 PMKID最近把平时做无线安全测试用的那堆命令行工具,整合成了一个浏览器可操控的控制台,叫 Wireless_Toolk… · 2026/9/25 14:36:04

Atlas 300V 24G推理卡实战:从ONNX到OM跑通YOLOv5/YOLOv8
Atlas 300V 24G推理卡实战:从ONNX到OM跑通YOLOv5/YOLOv8

先说个最近的真实场景。手里有一个YOLO检测项目要上线,常规思路是直接买GPU,结果一看价格和功耗,人直接麻了。后来同事问了一句:“Atlas 300V 24G那张卡能不能跑YOLO?”我查了一圈资料、拿卡实测了几周,发现… · 2026/9/25 14:35:40

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码