最近我搞了个大模型的小项目,主要功能是让大模型自动生成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 

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