fix(telegram-bot): fix velin rendering, action loop, and LLM compatibility (#1333)
This commit is contained in:
@@ -69,20 +69,18 @@ export async function sendMessage(
|
||||
chatContext.currentTask = null
|
||||
}
|
||||
|
||||
// Check if we should abort due to new messages since processing began
|
||||
if (botContext.unreadMessages[chatId] && botContext.unreadMessages[chatId].length > 0) {
|
||||
botContext.logger.log(`Not sending message to ${chatId} - new messages arrived`)
|
||||
return // Don't send the message, let the next processing loop handle it
|
||||
}
|
||||
// Note: removed "new messages arrived" check — it was preventing the bot
|
||||
// from ever responding in fast-paced chats. The agent loop handles new messages naturally.
|
||||
|
||||
const systemContent = String(await messageSplit())
|
||||
const req = {
|
||||
apiKey: env.LLM_API_KEY!,
|
||||
baseURL: env.LLM_API_BASE_URL!,
|
||||
model: env.LLM_MODEL!,
|
||||
messages: message.messages(
|
||||
message.system(await messageSplit()),
|
||||
message.user('This is the input message:'),
|
||||
message.user(responseText),
|
||||
{ role: 'system' as const, content: systemContent },
|
||||
{ role: 'user' as const, content: 'This is the input message:' },
|
||||
{ role: 'user' as const, content: String(responseText) },
|
||||
),
|
||||
abortSignal: abortController.signal,
|
||||
} satisfies GenerateTextOptions
|
||||
|
||||
@@ -55,7 +55,7 @@ async function dispatchAction(ctx: BotContext, action: Action, abortController:
|
||||
}
|
||||
}
|
||||
|
||||
return () => handleLoopStep(ctx, chatCtx)
|
||||
return
|
||||
}
|
||||
case 'send_sticker':
|
||||
{
|
||||
@@ -135,7 +135,7 @@ async function dispatchAction(ctx: BotContext, action: Action, abortController:
|
||||
chatCtx.actions.push({ action, result: `AIRI System: List of chats:${(await listJoinedChats()).map(chat => `ID:${chat.chat_id}, Name:${chat.chat_name}`).join('\n')}` })
|
||||
}
|
||||
|
||||
return () => handleLoopStep(ctx, chatCtx)
|
||||
return
|
||||
case 'send_message':
|
||||
{
|
||||
const chatCtx = ensureChatContext(ctx, action.chatId)
|
||||
@@ -168,8 +168,6 @@ async function dispatchAction(ctx: BotContext, action: Action, abortController:
|
||||
if (chatCtx) {
|
||||
chatCtx.messages.push(message.user(`AIRI System: The action you sent ${action.action} haven't implemented yet by developer.`))
|
||||
}
|
||||
|
||||
return () => handleLoopStep(ctx, chatCtx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,36 +33,34 @@ export async function imagineAnAction(
|
||||
|
||||
let responseText = ''
|
||||
|
||||
const systemContent = String(div(
|
||||
await systemTicking(),
|
||||
await personality(),
|
||||
))
|
||||
const userContent = String(div(
|
||||
vif(
|
||||
globalStates?.incomingMessages?.length > 0,
|
||||
div(
|
||||
'Incoming messages:',
|
||||
globalStates?.incomingMessages?.filter(Boolean).map(msg => `- ${msg?.text}`).join('\n'),
|
||||
),
|
||||
),
|
||||
'History actions:',
|
||||
actions.map(a => `- Action: ${JSON.stringify(a.action)}, Result: ${JSON.stringify(a.result)}`).join('\n'),
|
||||
span(`
|
||||
Currently, it's ${new Date()} on the server that hosts you.
|
||||
The others in the group may live in a different timezone, so please be aware of the time difference.
|
||||
`),
|
||||
`You have total ${Object.values(globalStates.unreadMessages).reduce((acc, cur) => acc + cur.length, 0)} unread messages.`,
|
||||
'Unread messages count are:',
|
||||
Object.entries(globalStates.unreadMessages).map(([key, value]) => `ID:${key}, Unread message count:${value.length}`).join('\n'),
|
||||
'Based on the context, What do you want to do? Choose a right action from the listing of the tools you want to take next.',
|
||||
'Respond with the action and parameters you choose in JSON only, without any explanation and markups.',
|
||||
))
|
||||
const requestMessages = message.messages(
|
||||
message.system(
|
||||
div(
|
||||
await systemTicking(),
|
||||
await personality(),
|
||||
),
|
||||
),
|
||||
{ role: 'system' as const, content: systemContent },
|
||||
...messages,
|
||||
message.user(
|
||||
div(
|
||||
vif(
|
||||
globalStates?.incomingMessages?.length > 0,
|
||||
div(
|
||||
'Incoming messages:',
|
||||
globalStates?.incomingMessages?.filter(Boolean).map(msg => `- ${msg?.text}`).join('\n'),
|
||||
),
|
||||
),
|
||||
'History actions:',
|
||||
actions.map(a => `- Action: ${JSON.stringify(a.action)}, Result: ${JSON.stringify(a.result)}`).join('\n'),
|
||||
span(`
|
||||
Currently, it's ${new Date()} on the server that hosts you.
|
||||
The others in the group may live in a different timezone, so please be aware of the time difference.
|
||||
`),
|
||||
`You have total ${Object.values(globalStates.unreadMessages).reduce((acc, cur) => acc + cur.length, 0)} unread messages.`,
|
||||
'Unread messages count are:',
|
||||
Object.entries(globalStates.unreadMessages).map(([key, value]) => `ID:${key}, Unread message count:${value.length}`).join('\n'),
|
||||
'Based on the context, What do you want to do? Choose a right action from the listing of the tools you want to take next.',
|
||||
'Respond with the action and parameters you choose in JSON only, without any explanation and markups.',
|
||||
),
|
||||
),
|
||||
{ role: 'user' as const, content: userContent },
|
||||
)
|
||||
|
||||
try {
|
||||
@@ -114,7 +112,58 @@ export async function imagineAnAction(
|
||||
.replace(/\n```$/, '')
|
||||
.trim()
|
||||
|
||||
const action = parse(responseText) as Action
|
||||
const raw = parse(responseText) as Record<string, unknown>
|
||||
|
||||
// Normalize: some models wrap fields inside "parameters", flatten to top level
|
||||
if (raw.parameters && typeof raw.parameters === 'object') {
|
||||
const params = raw.parameters as Record<string, unknown>
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (!(key in raw))
|
||||
raw[key] = value
|
||||
}
|
||||
delete raw.parameters
|
||||
}
|
||||
|
||||
// Normalize action name aliases
|
||||
const actionAliases: Record<string, string> = {
|
||||
read_messages: 'read_unread_messages',
|
||||
get_unread_messages: 'read_unread_messages',
|
||||
check_messages: 'read_unread_messages',
|
||||
reply_to_a_message_from_a_chat: 'send_message',
|
||||
reply_message: 'send_message',
|
||||
get_messages_from_chat: 'read_unread_messages',
|
||||
Read_unread_messages: 'read_unread_messages',
|
||||
}
|
||||
if (typeof raw.action === 'string' && actionAliases[raw.action])
|
||||
raw.action = actionAliases[raw.action]
|
||||
|
||||
// Normalize field name aliases
|
||||
if (!raw.chatId) {
|
||||
raw.chatId = raw.recipient_id ?? raw.group_id ?? raw.chat_id ?? raw.user_id ?? raw.conversation_id ?? raw.unread_message_id ?? raw.id
|
||||
delete raw.recipient_id
|
||||
delete raw.group_id
|
||||
delete raw.chat_id
|
||||
delete raw.user_id
|
||||
delete raw.conversation_id
|
||||
delete raw.unread_message_id
|
||||
}
|
||||
if (!raw.content) {
|
||||
raw.content = raw.message ?? raw.text ?? raw.chat_message
|
||||
delete raw.message
|
||||
delete raw.text
|
||||
delete raw.chat_message
|
||||
}
|
||||
|
||||
// Fallback: infer chatId from unreadMessages if only one chat has unread
|
||||
if (!raw.chatId) {
|
||||
const unreadChatIds = Object.keys(globalStates.unreadMessages).filter(
|
||||
k => globalStates.unreadMessages[k]?.length > 0,
|
||||
)
|
||||
if (unreadChatIds.length >= 1)
|
||||
raw.chatId = unreadChatIds[0]
|
||||
}
|
||||
|
||||
const action = raw as unknown as Action
|
||||
s.setAttribute('telegram.bot.id', botId)
|
||||
s.setAttribute('telegram.module.generate_agent_action.parsed_action', JSON.stringify(action))
|
||||
|
||||
|
||||
@@ -16,12 +16,13 @@ export function importVelin(module: string, base: string): VelinModule {
|
||||
return {
|
||||
render: async (data) => {
|
||||
const content = (await readFile(relativeOf(module, base))).toString('utf-8')
|
||||
|
||||
if (isMarkdown(module)) {
|
||||
return renderMarkdownString(content, data)
|
||||
}
|
||||
|
||||
return renderSFCString(content, data)
|
||||
const result = isMarkdown(module)
|
||||
? await renderMarkdownString(content, data)
|
||||
: await renderSFCString(content, data)
|
||||
// renderMarkdownString returns { rendered, props } instead of a plain string
|
||||
if (result && typeof result === 'object' && 'rendered' in result)
|
||||
return (result as { rendered: string }).rendered
|
||||
return result
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user