大模型实现sql生成 --- 能力不足时的retry

项目背景

最近做了一个大模型demo,自动生成sql,获取数据然后在前端页面画图。

这个demo的核心就是大模型生成sql的准确性,后面的都是常规前后端。

deepseek的优秀能力

首先用deepseek 试了一下,deepseek R1得益于他模型能力的强悍,出来的sql基本上都是能跑出数据,不会报错的,而且基本上也都是对的。

但是他的缺点就是,接口调用太慢了,一个接口基本上都至少要两三分钟才能出结果,如果要先后调用两次或者多次的话,交互体验太不友好了。

官方的接口慢,其实也有快的接口,阿里百炼提供的R1的接口会快很多,基本上一分钟左右就能出结果,其实是可以接受的。

其他模型的欠缺与优点

deepseek 的能力优秀,生成的sql 质量很高,跑起来不会报错,查询的数据也基本上是用户想看的。但是其他模型的能力没有R1 这么强,生成的sql跑起来经常直接报错,但是他们这些模型也有自己的优点,那就是快,基本上3s~7s就能出结果,因为少了推理。

那么有没有办法能利用起来这些模型,生成的sql又快,跑起来还不报错。

retry

其实和我们平时让大模型写代码的思路一样,先写一版,如果报错了,就直接把报错信息丢给大模型,让它对报错进行修正。

当大模型返回结果的时候,我们把sql直接运行起来,把这个过程try catch一下,如果报错了,把报错添加上下文中,再丢给大模型,让他再出一版,直到不报错。

实践

retry 的过程为了防止大模型实在太呆了,很多次retry都还是报错,应该设置一个重试的最大次数。

那么如果达到最大次数结果还是错的,我认为应该有两种方案:

  • 直接返回错误到前端,让用户修改问题的描述,然后重试
  • 有一个兜底的模型,当达到最大重试次数还是不行之后,自动切换成deepseek 获取sql

我试了一下智谱 的模型GLM-4-Air,最多不超过一半的问题能直接给出正确的结果,对于出问题的结果,大部分也就重试个一次两次就能成功,很少的三次还不成功的,基本上要5次以上。

也就是说对于大部分的问题,这种方案总时间还是比直接用deepseek 快很多的,我认为retry 方案再配合上deepseek进行兜底的方案的可行性是非常高的。

代码

下面展示了一个基于langchain.js的基本封装,历史记录就简单用一个数组进行维护了,没有用到内置的history memory模块。

在调用的时候,只要调用invokeWithRetry方法,传入用户的问题,和sql语句查询函数,以及最大重试次数。

当然,这里的callback不仅仅可以是sql查询,也可以是任何其他任务。

也就是说,这种方法方案的使用场景很多。

js 复制代码
import { RunnableSequence } from '@langchain/core/runnables'
import { PromptTemplate } from '@langchain/core/prompts'

class LLM {
  constructor(model, options = {}) {
    // 配置参数
    this.maxHistory = options.maxHistory || 5
    this.history = []
    this.systemLogs = []

    // 构建处理流水线
    this.chain = RunnableSequence.from([
      this._formatInput.bind(this),
      this._buildPromptTemplate(),
      model.bind({ responseType: 'text' })
    ])
  }

  // 核心调用方法(带自动重试)
  async invokeWithRetry(prompt, callback, maxRetries = 7) {
    let retryCount = 0
    let lastError = null

    while (retryCount <= maxRetries) {
      try {
        // 执行主调用流程
        console.log(`执行了${retryCount + 1}次`)
        const response = await this.invoke(prompt)

        // 执行回调并返回结果
        const result = await callback(response)
        return result
      } catch (error) {
        lastError = error
        console.log(error)
        this._recordRetryError(prompt, error, retryCount)

        if (retryCount >= maxRetries) break

        // 指数退避等待
        await this._sleep(1000 * Math.pow(2, retryCount))
        retryCount++
      }
    }

    // 重试耗尽后的处理
    this._logFatalError(prompt, lastError)
    throw this._formatFinalError(lastError, maxRetries)
  }

  // 基础调用方法
  async invoke(input) {
    try {
      const response = await this.chain.invoke({ input })
      this._saveHistory({ role: 'user', content: input })
      this._saveHistory({ role: 'ai', content: response })
      return response
    } catch (error) {
      this._logSystemError(input, error)
      throw error
    }
  }

  // 历史记录管理
  _saveHistory(entry) {
    this.history.push(entry)

    // 自动清理旧历史(保留最近N轮对话)
    if (this.history.length > this.maxHistory * 2) {
      this.history = this.history.slice(-this.maxHistory * 2)
    }
  }

  // 重试错误记录(带上下文信息)
  _recordRetryError(prompt, error, retryCount) {
    this._saveHistory({
      role: 'system',
      content: `重试#${retryCount}: ${error.message}`
    })

    this.systemLogs.push({
      timestamp: new Date().toISOString(),
      type: 'RETRY_ERROR',
      prompt,
      retryCount,
      error: error.stack
    })
  }

  // 其他辅助方法
  _formatInput({ input }) {
    return {
      formattedHistory: this.history
        .slice(-this.maxHistory * 2)
        .map((e) => `${e.role}: ${e.content}`)
        .join('\n'),
      currentInput: input
    }
  }

  _buildPromptTemplate() {
    return PromptTemplate.fromTemplate(`
      对话上下文(最多${this.maxHistory}轮):
      {formattedHistory}
      
      最新输入:
      {currentInput}
      
      请生成响应:
    `)
  }

  _sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms))
  }

  _logFatalError(prompt, error) {
    this.systemLogs.push({
      timestamp: new Date().toISOString(),
      type: 'FATAL_ERROR',
      prompt,
      error: error.stack
    })
  }

  _formatFinalError(error, maxRetries) {
    return new Error(`
      请求在${maxRetries}次重试后失败。
      最后错误:${error.message}
      历史记录:${JSON.stringify(this.history.slice(-2))}
    `)
  }
}

export default LLM
相关推荐
诗书画唱1 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel8 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子15 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
亚马逊云开发者18 分钟前
RAG-MCP 性能剖析:在 Amazon Bedrock 中多维度测试提示词优化的效果
llm
悟空聊架构21 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep23 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss26 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风27 分钟前
html二次作业
前端·html
前端双越老师27 分钟前
【干货】使用 langChian.js 实现掘金“智能总结” 考虑大文档和 token 限制
人工智能·langchain·node.js
江城开朗的豌豆31 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈