章
目
录
最近我搞了个大模型的小项目,主要功能是让大模型自动生成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
这段代码实现了一个带有重试机制的大模型调用类,通过这个类可以方便地调用大模型并处理可能出现的错误,希望对大家在相关开发中有所帮助。