egg_server/utils/llm/index.ts

129 lines
3.3 KiB
TypeScript

import loggerIns from "@egg/logger"
import { JsonOutputParser } from "@langchain/core/output_parsers"
import { PromptTemplate } from "@langchain/core/prompts"
import { adjustTimeRange, getSpecificTime, getTimeRange } from "../time"
import { getLangfuse, getModel } from "./base"
/**
* 调用LLM模型
* @param promptName 提示Key
* @param variables 变量
* @param requestId 请求ID
* @param temperature 温度
* @returns
*/
const invoke = async (
promptName: string,
variables: Record<string, any>,
requestId: string,
temperature = 0,
jsonMode = false
) => {
const logger = loggerIns.child({ requestId })
const attemptInvoke = async () => {
const { langfuse, langfuseHandler } = await getLangfuse("invoke", requestId)
const prompt = await langfuse.getPrompt(promptName)
const langchainTextPrompt = PromptTemplate.fromTemplate(
prompt.getLangchainPrompt()
).withConfig({
metadata: { langfusePrompt: prompt },
})
const chain = langchainTextPrompt.pipe(await getModel(temperature))
if (jsonMode) {
chain.pipe(new JsonOutputParser())
}
const { content } = await chain.invoke(variables, {
callbacks: [langfuseHandler],
})
return content
}
let result
let attempts = 0
do {
try {
result = await attemptInvoke()
break
} catch (e: any) {
logger.error(`调用LLM模型失败`, { promptName, error: e.message })
attempts++
}
} while (attempts < 3)
if (!result) {
logger.error("多次调用LLM模型失败", { promptName, attempts: 3 })
return ""
}
return result
}
/**
* 时间解析器
* @param userInput
* @param requestId
* @returns
*/
const timeParser = async (userInput: string, requestId: string) => {
const logger = loggerIns.child({ requestId })
const time = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })
const weekDay = `星期${"日一二三四五六"[new Date().getDay()]}`
const invokeParser = async () => {
try {
const res = (await invoke(
"timeParser",
{
time,
weekDay,
userInput,
lastWeekStart: getSpecificTime("lastMonday", 0),
lastWeekEnd: getSpecificTime("lastSunday", 24),
yesterdayAfternoonStart: getSpecificTime("yesterday", 12),
yesterdayAfternoonEnd: getSpecificTime("yesterday", 18),
threeDayStart: getSpecificTime("twoDaysAgo", 0),
threeDayEnd: getSpecificTime("today", 24),
},
requestId
)) as string
return JSON.parse(res.replaceAll("`", ""))
} catch (e: any) {
logger.error("时间解析失败", { userInput, error: e.message })
// 如果解析失败,则返回空字符串
return { s: "", e: "" }
}
}
const validateResult = (result: any) => {
const regex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/
return regex.test(result.s) && regex.test(result.e)
}
let result
let attempts = 0
do {
result = await invokeParser()
attempts++
} while (!validateResult(result) && attempts < 3)
// 如果解析失败,则返回过去三天的时间范围
if (!validateResult(result)) {
return getTimeRange("threeDays")
}
// 解析成功,调整时间范围
return adjustTimeRange(result.s, result.e)
}
const llm = {
timeParser,
invoke,
}
export default llm