大模型生成SQL时能力不足?试试这个重试策略!

人工智能 潘老师 1个月前 (03-23) 49 ℃ (0) 扫码查看

最近我搞了个大模型的小项目,主要功能是让大模型自动生成SQL语句,获取数据后在前端页面画图展示。这项目的关键就在于大模型生成SQL的准确性,至于前后端部分,都是些常规操作。

一、不同模型的表现

我最先尝试的是deepseek的R1模型,不得不说,这模型确实牛。它生成的SQL语句,基本上都能正常运行,不会报错,而且查询出来的数据也很符合预期。不过,它有个比较让人头疼的问题,就是接口调用速度太慢了。每次调用接口,至少都得等两三分钟才有结果。要是需要多次调用接口,那用户体验简直太差了,这在实际应用中可太影响使用了。

其实,除了官方接口,阿里百炼提供的R1接口速度就快很多,差不多一分钟左右就能返回结果,这个速度还是能接受的。

再看看其他模型,和deepseek的R1比起来,它们的能力就没那么强了。这些模型生成的SQL语句,跑起来经常报错。但它们也不是一无是处,速度快就是它们的优势,一般3到7秒就能出结果。这主要是因为它们少了复杂的推理过程。

这么看来,要是能结合这些模型的优点,让生成的SQL又快又准,那就完美了。

二、重试策略(retry)的思路

大家平时让大模型写代码的时候,要是代码报错了,是不是经常把报错信息再丢给大模型,让它修改?实现大模型生成SQL的优化,我用的也是类似思路。

每次大模型返回SQL结果后,我们就直接拿去运行,并且用try catch语句把这个过程包起来。要是运行时报错了,就把报错信息加上上下文,再传给大模型,让它重新生成一版SQL,就这样反复操作,直到不再报错为止。

三、重试策略的实践要点

在实际操作这个重试过程的时候,得防止大模型“犯傻”。要是它一直生成错误的SQL,不停地重试可不行,所以得设置一个最大重试次数。

那要是达到最大重试次数,结果还是不对该怎么办呢?我想了两种解决办法:
一种是直接把错误信息返回给前端,让用户重新修改问题描述,然后再重试;
另一种是准备一个兜底的模型,一旦达到最大重试次数还不行,就自动切换到兜底模型,比如deepseek,用它来获取SQL。

我拿智谱的GLM – 4 – Air模型做了测试,发现这个模型直接给出正确结果的概率不超过一半。对于那些生成错误的结果,大部分重试一两次就能成功,只有很少一部分需要重试三次,基本上重试五次以上的情况非常少。

从测试结果来看,对于大部分问题,采用这种重试方案,再配合deepseek兜底,总的耗时比直接用deepseek要快很多,所以我觉得这个方案的可行性非常高。

四、基于langchain.js的代码实现

下面给大家展示一段基于langchain.js的代码封装,这里对历史记录的维护比较简单,就用了一个数组,没有使用内置的history memory模块。

在调用的时候,只需要调用invokeWithRetry方法,然后传入用户的问题、SQL语句查询函数,还有最大重试次数就可以了。这里的callback可不局限于SQL查询,也可以是其他任何任务,所以这个方案的应用场景还是挺广泛的。

// 引入必要的模块
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

这段代码实现了一个带有重试机制的大模型调用类,通过这个类可以方便地调用大模型并处理可能出现的错误,希望对大家在相关开发中有所帮助。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/ai/16163.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】