Compare commits

..

4 Commits

Author SHA1 Message Date
21d8546486 🔧 fix(constants/prompts): update "at" field to use -1 instead of false for message structure consistency
🔧 fix(lib/responseHandler): standardize "at" field default value to -1 for message processing

🔧 fix(apps/ai): adjust message quoting to clarify sender in response formatting and update "at" field to -1
2025-11-26 18:24:48 +08:00
4e440aaae7 🐛 fix(apps/ai.js): correct display text assignment to use message.text instead of msg.text 2025-11-26 15:03:11 +08:00
bd6ad16445 feat(ai): add max message length configuration and truncate long messages in chat history display 2025-11-26 13:47:22 +08:00
5c97c9cdf7 🐛 fix(aiCaller): improve message handling for group chat history and mentions with proper looping and name retrieval 2025-11-25 13:47:43 +08:00
6 changed files with 43 additions and 39 deletions

View File

@ -97,7 +97,8 @@
- [X] 自定义上下文长度
- [X] 记忆存储及搜索
- [X] 自定义会话管理
- [X] 支持获取引用消息,使用seq标记
- [X] 支持获取引用消息,使用seq标记
- [X] 支持群聊上下文消息
- [ ] 支持调用更多工具
- [ ] 获取引用消息
- [ ] 适配多模态模型,查看图片等

View File

@ -111,10 +111,17 @@ async function extractUserMessage(msg, nickname, e) {
if (e.message && msg && msg.trim()!=='' && msg !== '\n') {
let text = [];
let at = [];
const aiConfig = await ConfigControl.get('ai');
const maxMessageLength = aiConfig?.maxMessageLength || 100;
e.message.forEach((message) => {
logger.info(message);
if (message.type === 'text' && message.text !== '' && message.text !== '\n'){
text.push(message.text);
let displayText = message.text;
if (message.text && message.text.length > maxMessageLength) {
const omittedChars = message.text.length - maxMessageLength;
displayText = message.text.substring(0, maxMessageLength) + `...(省略${omittedChars}字)`;
}
text.push(displayText);
} else if (message.type === 'at') {
at.push(message.qq);
}
@ -132,13 +139,14 @@ async function extractUserMessage(msg, nickname, e) {
return [];
}
if (at.length > 0) {
at.forEach((at) => {
if (at == e.bot.uin) {
for (const at1 of at) {
if (at1 == e.bot.uin) {
//returnMessage += `[${e.sender?.nickname},id:${e.user_id}]@(at)了你,你的id是${at}\n`;
} else {
returnMessage += `[${e.sender?.nickname},id:${e.user_id},seq:${e.message_id}]@(at)了一个人,id是${at}\n`;
const atNickname = await e.group.pickMember(at1).nickname || '一个人';
returnMessage += `[${e.sender?.nickname},id:${e.user_id},seq:${e.message_id}]@(at)了${atNickname},id是${at1}\n`;
}
});
}
}
const imgUrls = await YunzaiUtils.getImages(e, 1, true);
if (imgUrls) {
@ -155,10 +163,10 @@ async function extractUserMessage(msg, nickname, e) {
const msgArr = Array.isArray(reply) ? reply : reply.message || [];
msgArr.forEach((msg) => {
if(msg.type === 'text'){
returnMessage += `[${e.sender?.nickname}]引用了[被引用消息:${reply.sender?.nickname},id:${reply.user_id},seq:${reply.message_id}]发的一段文本:${msg.text}\n`
returnMessage += `[${e.sender?.nickname}]引用了[被引用消息:${reply.user_id == e.bot.uin ? '你' : reply.sender?.nickname},id:${reply.user_id},seq:${reply.message_id}]发的一段文本:${msg.text}\n`
}
if(msg.type === 'image'){
returnMessage += `[${e.sender?.nickname}]引用了[被引用消息:${reply.sender?.nickname},id:${reply.user_id},seq:${reply.message_id}]发的一张图片(你可能暂时无法查看)\n`;
returnMessage += `[${e.sender?.nickname}]引用了[被引用消息:${reply.user_id == e.bot.uin ? '你' : reply.sender?.nickname},id:${reply.user_id},seq:${reply.message_id}]发的一张图片(你可能暂时无法查看)\n`;
}
})
}
@ -206,7 +214,7 @@ async function handleKeywordMode(userMessage, e) {
{
type: 'message',
data: matchResult.text,
at: false,
at: -1,
quote: -1,
recall: false,
},
@ -242,7 +250,7 @@ async function handleMixMode(userMessage, e, aiConfig) {
{
type: 'message',
data: matchResult.text,
at: false,
at: -1,
quote: -1,
recall: false,
},
@ -250,7 +258,7 @@ async function handleMixMode(userMessage, e, aiConfig) {
let resMessage = {
type: 'message',
data: matchResult.text,
at: false,
at: -1,
quote: -1,
recall: false,
};
@ -339,20 +347,7 @@ async function sendResponse(e, messages) {
for (const message of messages) {
switch (message.type) {
case 'message':
if(message.quote === -1) {
if(message.recall) {
await e.reply(message.data, false, {
recallMsg: 60,
at: message.at,
});
} else {
await e.reply(message.data, false, {
at: message.at,
});
}
} else {
await Message.sendGroupMessage(e,e.group_id,message.data,message.at,message.quote,adapter);
}
await Message.sendGroupMessage(e,e.group_id,message.data,message.at,message.quote,adapter);
break;
case 'code':

View File

@ -20,6 +20,8 @@
"maxSessions": 10,
"?chatHistory": "聊天上下文最大长度",
"chatHistory": 10,
"?maxMessageLength": "最大消息长度",
"maxMessageLength": 100,
"?getChatHistoryLength": "获取到的聊天上下文长度",
"getChatHistoryLength":20,
"?keywordCache": "是否缓存关键词到本地",

View File

@ -25,7 +25,7 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
{
"type": "message",
"data": "你的回复内容",
"at": false,
"at": -1,
"quote": -1,
"recall": false
}
@ -33,15 +33,13 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
支持的消息类型(type)
常规消息:
- message(必须,其他均为可选): 普通文本消息,请将长句子分成多个message块返回(如果有多句话),data:回复内容,at:是否在发送本条消息的时候提醒用户,一般只在需要让用户注意的时候为true(另外,不要在message里面加@qq号),quote是否引用用户的问题,一般只需要在回答用户问题或第一条回复或需要用到用户问题的时候才引用该消息,注意这里如果不需要引用,则填写-1,需要引用时填写消息的seq,seq会在用户发的消息那里给出,也可以引用用户回复的消息或聊天记录的相关消息,但是一个message只能含有一个引用seq,recall:值为true的时候会在发送消息后过一会撤回自己的这条消息
- at: @某人(需要提供id,被at人qq号(number)),一般用于提醒用户,不常用
- message(必须,其他均为可选): 普通文本消息,请将长句子分成多个message块返回(如果有多句话),data:回复内容,at:是否在发送本条消息的时候提醒用户,一般只在需要让用户注意的时候为用户的qq号(另外,不要在message里面加@qq号,不需要的时候填写-1),quote是否引用用户的问题,一般只需要在回答用户问题或第一条回复或需要用到用户问题的时候才引用该消息,注意这里如果不需要引用,则填写-1,需要引用时填写消息的seq,seq会在用户发的消息那里给出,也可以引用用户回复的消息或聊天记录的相关消息,但是一个message只能含有一个引用seq,recall:值为true的时候会在发送消息后过一会撤回自己的这条消息
- meme: 表情包data值为情绪名称angrybyeconfuseddefaultgoodgoodmorninggoodnighthappysadshysorrysurprise),请根据聊天语境灵活选择需不需要表情包,如果感觉语境尴尬或需要表情包,那么发送一个default值的表情包,其他情绪的表情包按照当前你的情绪按需选择,注意:并不是每个聊天都需要有表情包,并且一次聊天最多回复一个表情包
- poke: 戳一戳某人(需要提供id,被戳人qq号(number)),一般用户与用户互动,当想逗用户的时候可以使用,不要使用太过频繁(频率小于百分之20)
功能性消息:
- code: 代码块(会自动渲染为高亮图片,必须有language参数指定编程语言)
- markdown: 需要渲染的markdown内容(会自动渲染为图片)
- memory: 存储记忆需要提供data(记忆内容,需要简明扼要)key(字符串数组,可以有多个关键词),timeout(遗忘世间,单位为天,建议一个月),重要:如果你认为本次用户说的话有一些值得记住的东西(例如用户希望你叫他什么,用户说她生日是多少多少等),那么使用本功能记住用户说的话
- ai-record: AI语音需要提供data,发送语音,不常用,用户要求你发语音的时候可以发,发的data需要简短,可以多条消息,但是不能太长
重要规则
1. 必须返回JSON数组格式,一定要是数组!无论有多少条消息,一条消息也要是数组!
@ -63,7 +61,7 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
{
"type": "message",
"data": "你好呀~",
"at": false,
"at": -1,
"quote": -1,
"recall": false
}

View File

@ -164,26 +164,34 @@ class AiCaller {
const historyLen = await ConfigControl.get('ai').getChatHistoryLength || 10;
const groupChatHistory = await e.group.getChatHistory(e.message_id, historyLen);
const aiConfig = await ConfigControl.get('ai');
const maxMessageLength = aiConfig?.maxMessageLength || 100;
if(groupChatHistory && groupChatHistory.length > 0 ){
contextIntro += '[群聊聊天记录(从旧到新)]\n'
groupChatHistory.forEach((message)=>{
for (const message of groupChatHistory) {
const msgArr = message.message;
msgArr.forEach((msg)=>{
for (const msg of msgArr) {
if(msg.type==='text'){
contextIntro += `[${message.sender.user_id == e.bot.uin ? '你' : message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前说过:${msg.text}\n`
let displayText = msg.text;
if (msg.text && msg.text.length > maxMessageLength) {
const omittedChars = msg.text.length - maxMessageLength;
displayText = msg.text.substring(0, maxMessageLength) + `...(省略${omittedChars}字)`;
}
contextIntro += `[${message.sender.user_id == e.bot.uin ? '你' : message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前说过:${displayText}\n`
}
if(msg.type === 'at'){
if(msg.qq == e.bot.uin){
contextIntro += `[${message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前@了你\n`
} else {
contextIntro += `[${message.sender.user_id == e.bot.uin ? '你' : message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前@了${msg.qq}\n`
const atNickname = await e.group.pickMember(msg.qq).nickname || '一个人';
contextIntro += `[${message.sender.user_id == e.bot.uin ? '你' : message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前@了${atNickname},id是${msg.qq}\n`
}
}
if(msg.type === 'image'){
contextIntro += `[${message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前发送了一张图片(你可能暂时无法查看)\n`
}
})
})
}
}
}
if (memories && memories.length > 0) {

View File

@ -142,7 +142,7 @@ class ResponseHandler {
let processedMessage = {
type: message.type,
data: message.data,
at: message.at || false,
at: message.at || -1,
quote: message.quote || -1,
recall: message.recall || false
};
@ -158,7 +158,7 @@ class ResponseHandler {
return [{
type: 'message',
data: `${nickName}的服务器去火星开小差了..`,
at: false,
at: -1,
quote: -1,
recall: true
}];
@ -169,7 +169,7 @@ class ResponseHandler {
return [{
type: 'message',
data: `${nickName}的服务器去火星开小差了..`,
at: false,
at: -1,
quote: -1,
recall: true
}];