diff --git a/bun.lockb b/bun.lockb index 9d1b09d..c45b339 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/constant/card.ts b/constant/card.ts index f7f84ac..317db36 100644 --- a/constant/card.ts +++ b/constant/card.ts @@ -1,14 +1,44 @@ import { cardComponent } from "@egg/lark-msg-tool" -const functionSelector = { +const groupSelector = { config: { update_multi: true, }, elements: [ { tag: "markdown", - content: "已经选中的群聊:**${chatName}**\n", + content: "请选择要对话的群聊", }, + { + tag: "action", + actions: [ + { + tag: "select_static", + placeholder: { + tag: "plain_text", + content: "请选择群名称", + }, + value: { + cardGroup: "groupAgent", + cardName: "groupSelector", + }, + options: "${groupOptions}", + }, + ], + }, + { + tag: "hr", + }, + cardComponent.commonNote, + ], + header: cardComponent.pendingHeader, +} + +const functionSelector = { + config: { + update_multi: true, + }, + elements: [ { tag: "action", actions: [ @@ -19,11 +49,10 @@ const functionSelector = { content: "请选择功能", }, value: { - chatId: "${chatId}", - chatName: "${chatName}", - action: "sendTimeScopeSelector", + cardGroup: "groupAgent", + cardName: "functionSelector", }, - options: "${functions}", + options: "${functionOptions}", }, ], }, @@ -42,8 +71,7 @@ const timeScopeSelector = { elements: [ { tag: "markdown", - content: - "已经选中群聊:**${chatName}**\n\n已经选择功能:**${functionName}**\n\n请选择时间范围", + content: "请选择时间范围", }, { tag: "action", @@ -56,13 +84,9 @@ const timeScopeSelector = { }, type: "default", value: { - chatId: "${chatId}", - chatName: "${chatName}", - functionId: "${functionId}", - functionName: "${functionName}", + cardGroup: "groupAgent", + cardName: "timeScopeSelector", timeScope: "1", - requestId: "${requestId}", - action: "manageGroupMsg", }, }, { @@ -73,13 +97,9 @@ const timeScopeSelector = { }, type: "default", value: { - chatId: "${chatId}", - chatName: "${chatName}", - functionId: "${functionId}", - functionName: "${functionName}", + cardGroup: "groupAgent", + cardName: "timeScopeSelector", timeScope: "3", - requestId: "${requestId}", - action: "manageGroupMsg", }, }, { @@ -90,13 +110,9 @@ const timeScopeSelector = { }, type: "default", value: { - chatId: "${chatId}", - chatName: "${chatName}", - functionId: "${functionId}", - functionName: "${functionName}", + cardGroup: "groupAgent", + cardName: "timeScopeSelector", timeScope: "7", - requestId: "${requestId}", - action: "manageGroupMsg", }, }, ], @@ -116,8 +132,7 @@ const resultReport = { elements: [ { tag: "markdown", - content: - "群聊:**${chatName}**,功能:**${functionName}**,总结天数:**${timeScope}**\n\n以下内容由AI模型生成,耗时:**${processingTime}s**", + content: "${content}", }, { tag: "hr", @@ -134,10 +149,18 @@ const resultReport = { header: cardComponent.successHeader, } +export const functionOptionList = [ + { + id: "summary-qwen-72b-instruct-int4", + name: "总结消息", + }, +] + const cardMap = { functionSelector, timeScopeSelector, resultReport, + groupSelector, } export default cardMap diff --git a/package.json b/package.json index 136cb0c..5aac0c3 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,9 @@ "typescript": "^5.5.4" }, "dependencies": { - "@dotenvx/dotenvx": "^1.19.2", + "@dotenvx/dotenvx": "^1.19.3", "@egg/hooks": "^1.2.0", - "@egg/lark-msg-tool": "^1.9.0", + "@egg/lark-msg-tool": "^1.13.0", "@egg/logger": "^1.4.4", "@egg/net-tool": "^1.9.2", "@egg/path-tool": "^1.4.1", diff --git a/routes/bot/actionMsg.ts b/routes/bot/actionMsg.ts index 580ec73..6f5ef80 100644 --- a/routes/bot/actionMsg.ts +++ b/routes/bot/actionMsg.ts @@ -1,10 +1,8 @@ import { Context } from "../../types" import groupAgent from "./groupAgent" -const ACTION_MAP = { - sendFunctionSelector: groupAgent.sendFunctionSelector, - sendTimeScopeSelector: groupAgent.sendTimeScopeSelector, - manageGroupMsg: groupAgent.manageGroupMsg, +const GROUP_MAP = { + groupAgent, } /** @@ -12,13 +10,16 @@ const ACTION_MAP = { * @param {Context.Data} ctx - 上下文数据,包含body, larkService和logger */ const manageAction = async (ctx: Context.Data) => { - const { body, logger } = ctx - const { action } = body?.action?.value as { - action: keyof typeof ACTION_MAP + const { + larkBody: { actionValue }, + logger, + } = ctx + const { cardGroup } = actionValue as { + cardGroup: keyof typeof GROUP_MAP } - logger.info(`Got lark action: ${action}`) - if (!action) return - const func = ACTION_MAP[action] + logger.info(`Got lark action cardGroup: ${cardGroup}`) + if (!cardGroup) return + const func = GROUP_MAP[cardGroup] if (!func) return return func(ctx) } @@ -31,9 +32,11 @@ export const manageActionMsg = async (ctx: Context.Data) => { const { larkBody: { actionType }, } = ctx + // 只处理按钮和静态选择器 if (!["button", "select_static"].includes(actionType!)) return ctx.genResp.ok() const card = await manageAction(ctx) + // 如果有返回卡片则返回卡片 if (card) return ctx.genResp.json(card) return ctx.genResp.ok() } diff --git a/routes/bot/eventMsg.ts b/routes/bot/eventMsg.ts index 2ccf6c7..f15ce32 100644 --- a/routes/bot/eventMsg.ts +++ b/routes/bot/eventMsg.ts @@ -85,7 +85,7 @@ const manageIdMsg = ({ }: Context.Data): void => { larkService.message.sendCard2Chat( chatId, - larkCard.genTempCard("chatId", { chat_id: chatId }) as string + larkCard.genTempCard("chatId", { chat_id: chatId }) ) } @@ -156,9 +156,9 @@ const manageCMDMsg = (ctx: Context.Data) => { return } // 选择群组信息 - if (msgText.trim() === "/groupchat") { + if (msgText.trim().startsWith("/groupchat")) { logger.info(`bot command is /groupchat, chatId: ${chatId}`) - groupAgent.sendGroupSelector(ctx) + groupAgent(ctx) return } return diff --git a/routes/bot/groupAgent/chatHistory.ts b/routes/bot/groupAgent/chatHistory.ts index beadfad..24046be 100644 --- a/routes/bot/groupAgent/chatHistory.ts +++ b/routes/bot/groupAgent/chatHistory.ts @@ -1,4 +1,5 @@ import { parseJsonString } from "@egg/hooks" +import { LarkEvent } from "@egg/lark-msg-tool" import logger from "@egg/logger" import { Context, LarkServer } from "../../../types" @@ -41,22 +42,48 @@ const extractTextFromJson = (data: any): string => { * @param context - 上下文数据 * @returns 聊天消息数组 */ -const getChatHistory = async ({ - larkService, - larkBody: { - actionValue: { chatId, timeScope }, - }, -}: Context.Data): Promise => { - // 获取历史消息,timeScope为1、3、7,分别代表1天、3天、7天 +const getChatHistory = async ( + { larkService }: Context.Data, + { + chatId, + timeScope, + startTime, + endTime, + mentions: targetUsers, + }: { + chatId: string + timeScope?: string + startTime?: string + endTime?: string + mentions?: LarkEvent.Mention[] + } +): Promise => { // 获取服务器的时区偏移量(以分钟为单位) const serverTimezoneOffset = new Date().getTimezoneOffset() // 上海时区的偏移量(UTC+8,以分钟为单位) const shanghaiTimezoneOffset = -8 * 60 - // 计算时间戳,调整为上海时区 - const endTimeTimestamp = - Math.round(new Date().getTime() / 1000) + - (shanghaiTimezoneOffset - serverTimezoneOffset) * 60 - const startTimeTimestamp = endTimeTimestamp - Number(timeScope) * 24 * 60 * 60 + + let startTimeTimestamp: number + let endTimeTimestamp: number + + if (startTime && endTime) { + // 将startTime和endTime转换为时间戳,并调整为上海时区 + startTimeTimestamp = Math.round( + new Date(startTime).getTime() / 1000 + + (shanghaiTimezoneOffset - serverTimezoneOffset) * 60 + ) + endTimeTimestamp = Math.round( + new Date(endTime).getTime() / 1000 + + (shanghaiTimezoneOffset - serverTimezoneOffset) * 60 + ) + } else { + // 计算时间戳,调整为上海时区 + endTimeTimestamp = + Math.round(new Date().getTime() / 1000) + + (shanghaiTimezoneOffset - serverTimezoneOffset) * 60 + startTimeTimestamp = endTimeTimestamp - Number(timeScope) * 24 * 60 * 60 + } + // 获取群聊中的历史记录 const { data: chatHistory } = await larkService.message.getHistory( chatId, @@ -65,6 +92,12 @@ const getChatHistory = async ({ ) if (chatHistory.length === 0) return [] + const targetUsersSet = new Set( + targetUsers + ?.filter?.((user) => user.id.user_id) + ?.map?.((user) => user.id.open_id) ?? [] + ) + // 清洗数据 // 取出所有的被AT的人,以及发送者 const mentions: Map = new Map() @@ -80,12 +113,28 @@ const getChatHistory = async ({ mentions.set(mention.id, mention.name) } } - if (chat.sender && chat.sender.sender_type === "user") { - senders.add(chat.sender.id) + // 过滤掉不是文本和post消息的消息 + if (!allowedMsgTypes.includes(chat.msg_type)) { + continue } - if (allowedMsgTypes.includes(chat.msg_type)) { - filteredMsg.push(chat) + + // 过滤掉不是用户发送的消息 + if (!chat.sender || chat.sender.sender_type !== "user") { + continue } + + // 如果目标用户集合不为空,并且消息的发送者不在目标用户集合中,并且消息中提到的所有用户都不在目标用户集合中,则跳过该消息 + if ( + targetUsersSet.size !== 0 && + !targetUsersSet.has(chat.sender.id) && + (!chat.mentions || + chat.mentions.every((mention) => !targetUsersSet.has(mention.id))) + ) { + continue + } + + senders.add(chat.sender.id) + filteredMsg.push(chat) } // 取出没有被AT的发送者 diff --git a/routes/bot/groupAgent/index.ts b/routes/bot/groupAgent/index.ts index 06f3b7f..16fcc63 100644 --- a/routes/bot/groupAgent/index.ts +++ b/routes/bot/groupAgent/index.ts @@ -1,114 +1,117 @@ -import { genCardOptions } from "@egg/lark-msg-tool" +import { genCardOptions, LarkEvent } from "@egg/lark-msg-tool" -import { Context } from "../../../types" +import { functionOptionList } from "../../../constant/card" +import { Context, LarkServer } from "../../../types" import llm from "../../../utils/llm" import getChatHistory from "./chatHistory" /** - * 发送群组选择器 - * @param ctx - 上下文数据,包含body和larkService - */ -const sendGroupSelector = async ({ - larkService, - logger, - larkCard, - larkBody: { chatId }, -}: Context.Data) => { - const cardGender = larkCard.child("groupAgent") - const { data: innerList } = await larkService.chat.getInnerList() - logger.info(`Inner list: ${JSON.stringify(innerList)}`) - // 组织群组数据 - const groups = innerList.map((v) => ({ - text: v.name, - value: `${v.chat_id}|${v.name}`, - })) - larkService.message.sendCard2Chat( - chatId, - cardGender.genTempCard("groupSelector", { groups }) as string - ) -} - -/** - * 发送功能选择器 + * 生成群组选择器 * @param ctx - 上下文数据 + * @param innerList - 内部群组列表 + * @param commonVal - 通用值 */ -const sendFunctionSelector = async ({ - logger, - larkCard, - larkBody: { actionOption }, -}: Context.Data) => { - const cardGender = larkCard.child("groupAgent", false) - logger.debug(`Action option: ${JSON.stringify(actionOption)}`) - const [chatId, chatName] = (actionOption ?? "").split("|") - if (!chatId || !chatName) { - logger.error( - `Invalid targetChatId or targetChatName: ${JSON.stringify(actionOption)}` +const genGroupSelector = ( + { larkCard }: Context.Data, + innerList: LarkServer.ChatGroupData[], + commonVal: Record = {} +) => { + const cardGender = larkCard.child("groupAgent") + // 组织群组数据 + const groupOptions = genCardOptions( + innerList.reduce( + (acc, item) => { + acc[item.name] = `${item.chat_id}|${item.name}` + return acc + }, + {} as Record ) - return cardGender.genErrorCard("Invalid targetChatId or targetChatName") - } - return cardGender.genCard("functionSelector", { - functions: genCardOptions({ - 总结消息: "summary-qwen-72b-instruct-int4|总结消息", - }), - chatId, - chatName, + ) + return cardGender.genCard("groupSelector", { + groupOptions, + ...commonVal, }) } /** - * 发送时间范围选择器 + * 生成功能选择器 + * @param ctx - 上下文数据 + * @param commonVal - 通用值 + */ +const genFunctionSelector = ( + { larkCard }: Context.Data, + commonVal: Record = {} +) => { + const cardGender = larkCard.child("groupAgent") + // 组织功能数据 + const functionOptions = genCardOptions( + functionOptionList.reduce( + (acc, item) => { + acc[item.name] = `${item.id}|${item.name}` + return acc + }, + {} as Record + ) + ) + return cardGender.genCard("functionSelector", { + functionOptions, + ...commonVal, + }) +} + +/** + * 生成时间范围选择器 * @param ctx - 上下文数据,包含body和larkService */ -const sendTimeScopeSelector = async ({ - logger, - larkCard, - larkBody: { actionOption, actionValue }, -}: Context.Data) => { - const cardGender = larkCard.child("groupAgent", false) - logger.debug(`Action option: ${JSON.stringify(actionOption)}`) - const [functionId, functionName] = (actionOption ?? "").split("|") - if (!functionId || !functionName) { - logger.error( - `Invalid functionId or functionName: ${JSON.stringify(actionOption)}` - ) - return cardGender.genErrorCard("Invalid functionId or functionName") - } - logger.debug(`Action value: ${JSON.stringify(actionValue)}`) - const { chatId, chatName } = actionValue - if (!chatName || !chatId) { - logger.error(`Invalid chatName or chatId: ${JSON.stringify(actionValue)}`) - return cardGender.genErrorCard("Invalid chatName or chatId") - } - return cardGender.genCard("timeScopeSelector", { +const genTimeScopeSelector = async ( + { larkCard }: Context.Data, + commonVal: Record = {} +) => { + return larkCard.child("groupAgent").genCard("timeScopeSelector", commonVal) +} + +const sendGroupReport = async ( + ctx: Context.Data, + messageId: string, + { chatId, chatName, functionId, functionName, - }) -} - -const sendGroupReport = async (ctx: Context.Data) => { - const { - larkService, - logger, - requestId, - larkCard, - larkBody: { actionValue, messageId }, - } = ctx + timeScope, + startTime, + endTime, + mentions, + }: { + chatId: string + chatName: string + functionId: string + functionName: string + timeScope?: string + startTime?: string + endTime?: string + mentions?: LarkEvent.Mention[] + } +) => { + const { larkService, logger, larkCard } = ctx const cardGender = larkCard.child("groupAgent") - const { chatName, functionId, functionName, timeScope } = actionValue - + const updateCard = (content: any) => + larkService.message.update(messageId, content) + // action需要返回loading的消息,event需要主动update卡片,所以loading就放外边了 // 记录发送loading消息后的时间戳 - const startTime = Date.now() + const processStart = Date.now() // 获取聊天记录 - const chatHistory = await getChatHistory(ctx) + const chatHistory = await getChatHistory(ctx, { + chatId, + timeScope, + startTime, + endTime, + mentions, + }) // 如果没有历史记录则返回错误消息 if (chatHistory.length === 0) { logger.error("Chat history is empty") - await larkService.message.update( - messageId, - cardGender.genErrorCard("未找到聊天记录") - ) + await updateCard(cardGender.genErrorCard("未找到聊天记录")) return } logger.debug(`Chat history: ${JSON.stringify(chatHistory)}`) @@ -119,20 +122,30 @@ const sendGroupReport = async (ctx: Context.Data) => { time: new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }), }) // 记录大模型返回结果后的时间戳 - const endTime = Date.now() + const processEnd = Date.now() // 计算时间差并存储在processingTime变量中,以秒为单位 - const processingTime = ((endTime - startTime) / 1000).toFixed(2) + const processingTime = ((processEnd - processStart) / 1000).toFixed(2) logger.info(`LLM takes time: ${processingTime}s, result: ${llmRes}`) + + // 动态生成content内容 + const timeRange = timeScope + ? `总结天数:**${timeScope}**` + : `时间范围:**${startTime}** 至 **${endTime}**` + const mentionsList = mentions + ? `圈选的用户:\n${mentions + .filter((v) => v.id.user_id) + .map((mention: any) => `- ${mention.name}`) + .join("\n")}` + : "" + + const content = `群聊:**${chatName}**,功能:**${functionName}**,${timeRange}\n\n${mentionsList}\n\n以下内容由AI模型生成,耗时:**${processingTime}s**` + // 更新消息卡片 await larkService.message.update( messageId, cardGender.genCard("resultReport", { - chatName, - functionName, + content, llmRes, - timeScope, - requestId, - processingTime, }) ) } catch (error: any) { @@ -145,37 +158,228 @@ const sendGroupReport = async (ctx: Context.Data) => { } /** - * 发送结果报告 - * @param ctx - 上下文数据,包含body和larkService + * 解析用户输入中的值并发送对应的表单卡片 + * @param ctx - 上下文数据 + * @param innerList - 内部群组列表 */ -const manageGroupMsg = async (ctx: Context.Data) => { +const parseGroupAgentQuery = async ( + ctx: Context.Data, + innerList: LarkServer.ChatGroupData[], + mentions?: LarkEvent.Mention[] +) => { const { - logger, + larkBody: { msgText, chatId: rawChatId }, + larkService, larkCard, - larkBody: { actionValue }, } = ctx + const cardGender = larkCard.child("groupAgent") - const cardGender = larkCard.child("groupAgent", false) + // 发送一个loading的消息 + const { + data: { message_id }, + } = await larkService.message.sendCard2Chat( + rawChatId, + cardGender.genPendingCard("分析中,请稍等...") + ) - logger.debug(`Action value: ${JSON.stringify(actionValue)}`) + const updateCard = (content: any) => + larkService.message.update(message_id, content) - const { chatId, chatName, functionId, functionName, timeScope } = actionValue - if (!chatId || !chatName || !functionId || !functionName || !timeScope) { - logger.error(`Invalid value: ${JSON.stringify(actionValue)}`) - return cardGender.genErrorCard("Invalid value") + // 组织群组数据 + const groupInfo = JSON.stringify( + innerList.map((v) => ({ + name: v.name, + id: v.chat_id, + })) + ) + // 获取功能信息 + const functionInfo = JSON.stringify(functionOptionList) + + // 使用大模型解析用户输入 + const { chatId, chatName, functionName, functionId, startTime, endTime } = + await llm.parseGroupAgentQuery(msgText, groupInfo, functionInfo) + + // 判断顺序是 群组 -> 功能 -> 时间范围 + + // 返回群组选择器,其他的值往里边丢就行 + if (!chatId || !chatName) { + updateCard( + genGroupSelector(ctx, innerList, { + functionName, + functionId, + startTime, + endTime, + mentions, + }) + ) + return + } + // 返回功能选择器,其他的值往里边丢就行 + if (!functionId || !functionName) { + updateCard( + genFunctionSelector(ctx, { + chatId, + chatName, + startTime, + endTime, + mentions, + }) + ) + return + } + // 返回时间范围选择器,其他的值往里边丢就行 + if (!startTime || !endTime) { + updateCard( + genTimeScopeSelector(ctx, { + chatId, + chatName, + functionId, + functionName, + mentions, + }) + ) + return } - // 处理群组消息 - sendGroupReport(ctx) - // 发送一个loading的消息 + // 设置齐全,返回结果报告 + updateCard(cardGender.genPendingCard("正在爬楼中,请稍等...")) + sendGroupReport(ctx, message_id, { + chatId, + chatName, + functionId, + functionName, + startTime, + endTime, + mentions, + }) +} + +/** + * 管理事件消息 + * @param ctx - 上下文数据 + */ +const manageEventMsg = async (ctx: Context.Data) => { + const { + larkBody: { msgText, chatType, chatId: rawChatId, mentions }, + larkService, + logger, + } = ctx + + // 获取群组信息 + const { data: innerList } = await larkService.chat.getInnerList() + logger.info(`Inner list: ${JSON.stringify(innerList)}`) + const sendCard = (content: string) => + larkService.message.sendCard2Chat(rawChatId, content) + + // 去过去掉所有非必要的信息为空的话 + if (msgText.replace("/groupchat", "").replaceAll(" ", "") === "") { + // 私聊发送正常的群组选择器 + if (chatType === "p2p") { + logger.info("Send group selector to p2p chat") + sendCard(genGroupSelector(ctx, innerList, { mentions })) + return + } + // 如果是群聊,获取群聊名称并发送功能 + const { + data: { name: chatName }, + } = await larkService.chat.getChatInfo(rawChatId) + logger.info(`Send function selector to group chat: ${chatName}`) + sendCard(genFunctionSelector(ctx, { chatName, mentions })) + return + } + + // 用户有输入,使用大模型进行解析发送对应卡片 + await parseGroupAgentQuery(ctx, innerList, mentions) + return +} + +/** + * 管理Action消息 + * @param ctx - 上下文数据 + */ +const manageActionMsg = async (ctx: Context.Data) => { + const { + larkBody: { actionOption, actionValue, messageId }, + logger, + } = ctx + const cardGender = ctx.larkCard.child("groupAgent") + logger.debug(`Action option: ${JSON.stringify(actionOption)}`) + logger.debug(`Action value: ${JSON.stringify(actionValue)}`) + let { chatId, chatName, functionId, functionName } = actionValue + const { timeScope, startTime, endTime, cardName, mentions } = actionValue + // 如果是群组选择器返回值 + if (cardName === "groupSelector") { + const [newChatId, newChatName] = (actionOption ?? "").split("|") + if (!newChatId || !newChatName) { + logger.error( + `Invalid targetChatId or targetChatName: ${JSON.stringify(actionOption)}` + ) + return cardGender.genErrorCard("Invalid targetChatId or targetChatName") + } + chatId = newChatId + chatName = newChatName + } + // 如果是功能选择器返回值 + if (cardName === "functionSelector") { + const [newFunctionId, newFunctionName] = (actionOption ?? "").split("|") + if (!newFunctionId || !newFunctionName) { + logger.error( + `Invalid functionId or functionName: ${JSON.stringify(actionOption)}` + ) + return cardGender.genErrorCard("Invalid functionId or functionName") + } + functionId = newFunctionId + functionName = newFunctionName + } + // 时间返回的返回值就会带在timeScope里,不需要再处理 + // 理论上来说,这里的chatId, chatName肯定是有值的,不需要再判断 + // 判断是否需要返回功能选择器 + if (!functionId || !functionName) { + return genFunctionSelector(ctx, { + chatId, + chatName, + startTime, + endTime, + timeScope, + mentions, + }) + } + // 判断是否需要返回时间范围选择器 + if (!timeScope || !startTime || !endTime) { + return genTimeScopeSelector(ctx, { + chatId, + chatName, + functionId, + functionName, + mentions, + }) + } + // 设置齐全,返回结果报告 + sendGroupReport(ctx, messageId, { + chatId, + chatName, + functionId, + functionName, + timeScope, + startTime, + endTime, + mentions, + }) return cardGender.genPendingCard("正在爬楼中,请稍等...") } -const groupAgent = { - sendGroupSelector, - sendFunctionSelector, - sendTimeScopeSelector, - manageGroupMsg, +/** + * 群组Agent的主入口 + * @param ctx - 上下文数据 + */ +const groupAgent = async (ctx: Context.Data) => { + const { + larkBody: { isEventMsg, isActionMsg }, + } = ctx + // 如果是Event,则解析自然语言并发送对应的卡片 + if (isEventMsg) return manageEventMsg(ctx) + // 如果是Action,则取出用户选的值并判断是否需要继续发送表单卡片或者开始大模型推理 + if (isActionMsg) return manageActionMsg(ctx) } export default groupAgent diff --git a/services/lark/chat.ts b/services/lark/chat.ts index c92a78e..72d39f4 100644 --- a/services/lark/chat.ts +++ b/services/lark/chat.ts @@ -28,6 +28,15 @@ class LarkChatService extends LarkBaseService { message: "ok", } } + + /** + * 获取群聊信息 + * @param chatId 群聊ID + */ + async getChatInfo(chatId: string) { + const path = `/im/v1/chats/${chatId}` + return this.get>(path) + } } export default LarkChatService diff --git a/services/lark/message.ts b/services/lark/message.ts index 188a7b5..bfd5f06 100644 --- a/services/lark/message.ts +++ b/services/lark/message.ts @@ -13,9 +13,12 @@ class LarkMessageService extends LarkBaseService { receiveIdType: LarkServer.ReceiveIDType, receiveId: string, msgType: LarkServer.MsgType, - content: string + content: string | Record ) { const path = `/im/v1/messages?receive_id_type=${receiveIdType}` + if (typeof content === "object") { + content = JSON.stringify(content) + } if (msgType === "text" && !content.includes('"text"')) { content = JSON.stringify({ text: content }) } @@ -31,7 +34,10 @@ class LarkMessageService extends LarkBaseService { * @param receiveId 消息接收者的ID,ID类型应与查询参数receiveIdType 对应 * @param content 消息内容 */ - async sendCard2Chat(receiveId: string, content: string) { + async sendCard2Chat( + receiveId: string, + content: string | Record + ) { return this.send("chat_id", receiveId, "interactive", content) } @@ -49,8 +55,11 @@ class LarkMessageService extends LarkBaseService { * @param messageId 消息id * @param content 消息内容,JSON结构序列化后的字符串。不同msgType对应不同内容 */ - async update(messageId: string, content: string) { + async update(messageId: string, content: string | Record) { const path = `/im/v1/messages/${messageId}` + if (typeof content === "object") { + content = JSON.stringify(content) + } return this.patch(path, { content }) } diff --git a/test/agent.http b/test/agent.http new file mode 100644 index 0000000..83e6447 --- /dev/null +++ b/test/agent.http @@ -0,0 +1,5 @@ + +POST http://localhost:3000/bot?app=egg +Content-Type: application/json + +{"schema":"2.0","header":{"event_id":"c0aed3b0911e18e8b941746256e8d4ce","token":"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj","create_time":"1728979404867","event_type":"im.message.receive_v1","tenant_key":"2ee61fe50f4f1657","app_id":"cli_a1eff35b43b89063"},"event":{"message":{"chat_id":"oc_8c789ce8f4ecc6695bb63ca6ec4c61ea","chat_type":"group","content":"{\"text\":\"@_user_1 /groupchat @_user_2 你好\"}","create_time":"1728979404353","mentions":[{"id":{"open_id":"ou_032f507d08f9a7f28b042fcd086daef5","union_id":"on_7111660fddd8302ce47bf1999147c011","user_id":""},"key":"@_user_1","name":"小煎蛋","tenant_key":"2ee61fe50f4f1657"},{"id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"key":"@_user_2","name":"赵英博","tenant_key":"2ee61fe50f4f1657"}],"message_id":"om_17492a15fdc15b3a0fda31352dae49dc","message_type":"text"},"sender":{"sender_id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"sender_type":"user","tenant_key":"2ee61fe50f4f1657"}}} \ No newline at end of file diff --git a/test/parseGroupAgentQuery.ts b/test/parseGroupAgentQuery.ts new file mode 100644 index 0000000..090ac1b --- /dev/null +++ b/test/parseGroupAgentQuery.ts @@ -0,0 +1,23 @@ +import llm from "../utils/llm" + +const groupInfo = JSON.stringify([ + { + id: "oc_ef98c2a9229657f99d4ef573a30fe91c", + name: "MIAI-FE 人工智能部-前端组", + }, + { + id: "oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1", + name: "方糖の家", + }, +]) + +const functionInfo = JSON.stringify([ + { + id: "summary-qwen-72b-instruct-int4", + name: "总结消息", + }, +]) + +const userInput = "方糖说了什么" + +llm.parseGroupAgentQuery(userInput, groupInfo, functionInfo).then(console.log) diff --git a/utils/genContext.ts b/utils/genContext.ts index 13c41fd..4a068fd 100644 --- a/utils/genContext.ts +++ b/utils/genContext.ts @@ -49,7 +49,7 @@ const genContext = async (req: Request) => { const path = new PathCheckTool(req.url) const larkCard = new LarkCard( "egg", - true, + false, requestId, cardMap, tempMap, diff --git a/utils/llm.ts b/utils/llm.ts index 6516b4b..eea0f78 100644 --- a/utils/llm.ts +++ b/utils/llm.ts @@ -60,24 +60,39 @@ const getModel = async (modelName: keyof typeof modelMap, temperature = 0) => { ) } -const timeConfig = z.object({ +const groupAgentConfig = z.object({ + chatId: z.string().describe("群聊ID"), + chatName: z.string().describe("群聊名称"), + functionId: z.string().describe("功能ID"), + functionName: z.string().describe("功能名称"), startTime: z.string().describe("开始时间,格式为 YYYY-MM-DD HH:mm:ss"), endTime: z.string().describe("结束时间,格式为 YYYY-MM-DD HH:mm:ss"), }) /** - * 解析时间 + * 解析GroupAgent用户输入 * @param userInput 用户输入 + * @param groupInfo 群聊信息 + * @param functionInfo 功能信息 * @returns */ -const parseTime = async (userInput: string) => { - const model = await getModel("deepseek-chat") - const structuredLlm = model.withStructuredOutput(timeConfig, { name: "time" }) +const parseGroupAgentQuery = async ( + userInput: string, + groupInfo: string, + functionInfo: string +) => { + const model = await getModel("gpt-4o") + const structuredLlm = model.withStructuredOutput(groupAgentConfig, { + name: "groupAgent", + }) return await structuredLlm.invoke( ` - 当前时间为 ${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })} - 你是一个专业的语义解析工程师,给定以下用户输入,帮我解析出开始时间和结束时间 - 如果不包含时间信息,请返回当天的起始时间到当前时间 + 当前时间为:${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })} + 所有可用群组信息:${groupInfo} + 所有支持功能信息:${functionInfo} + 你是一个专业的语义解析工程师,给定以下用户输入,帮我解析出群聊ID、群聊名称、功能ID、功能名称、开始时间和结束时间。 + 如果不包含对应内容,请返回空值。 + 用户输入: \`\`\` ${userInput.replaceAll("`", " ")} @@ -120,7 +135,7 @@ const invoke = async ( } const llm = { - parseTime, + parseGroupAgentQuery, invoke, }