AI prompt upgrade

This commit is contained in:
milo
2025-09-14 02:48:08 +02:00
parent 8433a31bc8
commit 83f6f68d95
2 changed files with 164 additions and 18 deletions

View File

@@ -1,5 +1,12 @@
import { sleep } from 'openai/core';
import { gork } from '../../utils/ai.js';
import {
buildAiMessages,
buildParticipantsMap,
buildTranscript,
CONTEXT_LIMIT,
gork, INCLUDE_ATTACHMENT_URLS, MAX_ATTS_PER_MESSAGE,
stripMentionsOfBot
} from '../../utils/ai.js';
import {
formatTime,
postAPOBuy,
@@ -129,29 +136,47 @@ async function handleAiMention(message, client, io) {
// --- AI Processing ---
try {
message.channel.sendTyping();
// Fetch last 20 messages for context
const fetchedMessages = await message.channel.messages.fetch({ limit: 20 });
const messagesArray = Array.from(fetchedMessages.values()).reverse(); // Oldest to newest
await message.channel.sendTyping();
const requestMessage = message.content.replace(`<@${client.user.id}>`, '').trim();
// 1) Récup contexte
const fetched = await message.channel.messages.fetch({ limit: Math.min(CONTEXT_LIMIT, 100) });
const messagesArray = Array.from(fetched.values()).reverse(); // oldest -> newest
// Format the conversation for the AI
const messageHistory = messagesArray.map(msg => ({
role: msg.author.id === client.user.id ? 'assistant' : 'user',
content: `${authorId} a dit: ${msg.content}`
const requestText = stripMentionsOfBot(message.content, client.user.id);
const invokerId = message.author.id;
const invokerName = message.member?.nickname || message.author.globalName || message.author.username;
const repliedUserId = message.mentions?.repliedUser?.id || null;
// 2) Compact transcript & participants
const participants = buildParticipantsMap(messagesArray);
const transcript = buildTranscript(messagesArray, client.user.id);
const invokerAttachments = Array.from(message.attachments?.values?.() || []).slice(0, MAX_ATTS_PER_MESSAGE).map(a => ({
id: a.id,
name: a.name,
type: a.contentType || 'application/octet-stream',
size: a.size,
isImage: !!(a.contentType && a.contentType.startsWith('image/')),
url: INCLUDE_ATTACHMENT_URLS ? a.url : undefined,
}));
const idToUser = getAllUsers.all().map(u => `${u.id} est ${u.username}/${u.globalName}`).join(', ');
// Add system prompts
messageHistory.unshift(
{ role: 'system', content: "Adopte une attitude détendue de membre du serveur. Réponds comme si tu participais à la conversation ne commence surtout pas tes messages par 'tel utilisateur a dit' il faut que ce soit fluide, pas trop long, évite de te répéter, évite de te citer toi-même ou quelqu'un d'autre. Utilise les emojis du serveur quand c'est pertinent. Ton id est 132380758368780288, ton nom est FlopoBot." },
{ role: 'system', content: `L'utilisateur qui s'adresse à toi est <@${authorId}>. Son message est une réponse à ${message.mentions.repliedUser ? `<@${message.mentions.repliedUser.id}>` : 'personne'}.` },
{ role: 'system', content: `Voici les différents utilisateurs : ${idToUser}, si tu veux t'adresser ou nommer un utilisateur, utilise leur ID comme suit : <@ID>` },
);
// 3) Construire prompts
const messageHistory = buildAiMessages({
botId: client.user.id,
botName: 'FlopoBot',
invokerId,
invokerName,
requestText,
transcript,
participants,
repliedUserId,
invokerAttachments,
});
// 4) Appel modèle
const reply = await gork(messageHistory);
// 5) Réponse
await message.reply(reply);
} catch (err) {

View File

@@ -38,6 +38,7 @@ export async function gork(messageHistory) {
if (modelProvider === 'OpenAI' && openai) {
const completion = await openai.chat.completions.create({
model: "gpt-5", // Using a modern, cost-effective model
reasoning_effort: "low",
messages: messageHistory,
});
return completion.choices[0].message.content;
@@ -79,4 +80,124 @@ export async function gork(messageHistory) {
console.error(`[AI] Error with ${modelProvider} API:`, error);
return "Oups, une erreur est survenue en contactant le service d'IA.";
}
}
export const CONTEXT_LIMIT = parseInt(process.env.AI_CONTEXT_MESSAGES || '100', 10);
export const MAX_ATTS_PER_MESSAGE = parseInt(process.env.AI_MAX_ATTS_PER_MSG || '3', 10);
export const INCLUDE_ATTACHMENT_URLS = (process.env.AI_INCLUDE_ATTACHMENT_URLS || 'true') === 'true';
export const stripMentionsOfBot = (text, botId) =>
text.replace(new RegExp(`<@!?${botId}>`, 'g'), '').trim();
export const sanitize = (s) =>
(s || '')
.replace(/\s+/g, ' ')
.replace(/```/g, 'ʼʼʼ') // éviter de casser des fences éventuels
.trim();
export const shortTs = (d) => new Date(d).toISOString(); // compact et triable
export function buildParticipantsMap(messages) {
const map = {};
for (const m of messages) {
const id = m.author.id;
if (!map[id]) {
map[id] = {
id,
username: m.author.username,
globalName: m.author.globalName || null,
isBot: !!m.author.bot,
};
}
}
return map;
}
export function buildTranscript(messages, botId) {
// Oldest -> newest, JSONL compact, une ligne par message pertinent
const lines = [];
for (const m of messages) {
const content = sanitize(m.content);
const atts = Array.from(m.attachments?.values?.() || []);
if (!content && atts.length === 0) continue;
const attMeta = atts.length
? atts.slice(0, MAX_ATTS_PER_MESSAGE).map(a => ({
id: a.id,
name: a.name,
type: a.contentType || 'application/octet-stream',
size: a.size,
isImage: !!(a.contentType && a.contentType.startsWith('image/')),
width: a.width || undefined,
height: a.height || undefined,
spoiler: typeof a.spoiler === 'boolean' ? a.spoiler : false,
url: INCLUDE_ATTACHMENT_URLS ? a.url : undefined, // désactive par défaut
}))
: undefined;
const line = {
t: shortTs(m.createdTimestamp || Date.now()),
id: m.author.id,
nick: m.member?.nickname || m.author.globalName || m.author.username,
isBot: !!m.author.bot,
mentionsBot: new RegExp(`<@!?${botId}>`).test(m.content || ''),
replyTo: m.reference?.messageId || null,
content,
attachments: attMeta,
};
lines.push(line);
}
return lines.map(l => JSON.stringify(l)).join('\n');
}
export function buildAiMessages({
botId,
botName = 'FlopoBot',
invokerId,
invokerName,
requestText,
transcript,
participants,
repliedUserId,
invokerAttachments = [],
}) {
const system = {
role: 'system',
content:
`Tu es ${botName} (ID: ${botId}) sur un serveur Discord. Style: bref, naturel, détendu, comme un pote.
Règles de sortie:
- Réponds en français, en 13 phrases.
- Réponds PRINCIPALEMENT au message de <@${invokerId}>. Le transcript est un contexte facultatif.
- Pas de "Untel a dit…", pas de longs préambules.
- Utilise <@ID> pour mentionner quelqu'un.
- Tu ne peux PAS ouvrir les liens; si des pièces jointes existent, tu peux simplement les mentionner (ex: "ta photo", "le PDF").`,
};
const attLines = invokerAttachments.length
? invokerAttachments.map(a => `- ${a.name} (${a.type || 'type inconnu'}, ${a.size ?? '?'} o${a.isImage ? ', image' : ''})`).join('\n')
: '';
const user = {
role: 'user',
content:
`Tâche: répondre brièvement à <@${invokerId}>.
Message de <@${invokerId}> (${invokerName || 'inconnu'}):
"""
${requestText}
"""
${invokerAttachments.length ? `Pièces jointes du message:
${attLines}
` : ''}${repliedUserId ? `Ce message répond à <@${repliedUserId}>.` : ''}
Participants (id -> nom):
${Object.values(participants).map(p => `- ${p.id} -> ${p.globalName || p.username}`).join('\n')}
Contexte (transcript JSONL; à utiliser seulement si utile):
\`\`\`jsonl
${transcript}
\`\`\``,
};
return [system, user];
}