大模型实现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 小时前
⚡⚡⚡Vite 被发现存在安全漏洞🕷,请及时升级到安全版本
前端·javascript·vue.js
小兵张健2 小时前
运用 AI,看这一篇就够了(上)
前端·后端·cursor
不怕麻烦的鹿丸2 小时前
node.js判断在线图片链接是否是webp,并将其转格式后上传
前端·javascript·node.js
vvilkim2 小时前
控制CSS中的继承:灵活管理样式传递
前端·css
南城巷陌2 小时前
Next.js中not-found.js触发方式详解
前端·next.js
拉不动的猪3 小时前
前端打包优化举例
前端·javascript·vue.js
Bigger3 小时前
Tauri(十五)——多窗口之间通信方案
前端·rust·app
倔强青铜三3 小时前
WXT浏览器插件开发中文教程(3)----WXT全部入口项详解
前端·javascript·vue.js
Aphasia3114 小时前
快速上手tailwindcss
前端·css·面试
程序员荒生4 小时前
基于 Next.js 搞定个人公众号登陆流程
前端·微信·开源