diff --git a/services/minecraft/src/mineflayer/llm-agent.ts b/services/minecraft/src/mineflayer/llm-agent.ts index ac41dc3f7..145e9f8b3 100644 --- a/services/minecraft/src/mineflayer/llm-agent.ts +++ b/services/minecraft/src/mineflayer/llm-agent.ts @@ -2,7 +2,7 @@ import type { Client } from '@proj-airi/server-sdk' import type { Neuri, NeuriContext } from 'neuri' import type { MineflayerPlugin } from '../libs/mineflayer/plugin' import { useLogg } from '@guiiai/logg' -import { assistant, type ChatCompletion, system, user } from 'neuri/openai' +import { assistant, system, user } from 'neuri/openai' import { formBotChat } from '../libs/mineflayer/message' import { genActionAgentPrompt, genStatusPrompt } from '../prompts/agent' @@ -30,11 +30,11 @@ export function LLMAgent(options: { agent: Neuri, airiClient: Client }): Minefla logger.log('thinking...') const handleCompletion = async (c: NeuriContext): Promise => { - const completion = await c.reroute('action', c.messages, { model: 'openai/gpt-4o-mini' }) as ChatCompletion | { error: { message: string } } & ChatCompletion + logger.log('rerouting...') + const completion = await c.reroute('action', c.messages, { model: 'openai/gpt-4o-mini' }) if (!completion || 'error' in completion) { logger.withFields({ completion }).error('Completion') - logger.withFields({ messages: c.messages }).log('messages') - throw new Error(completion?.error?.message ?? 'Unknown error') + throw completion?.error || new Error('Unknown error') } const content = await completion?.firstContent() @@ -49,8 +49,10 @@ export function LLMAgent(options: { agent: Neuri, airiClient: Client }): Minefla 3, // retryLimit 1000, // delayInterval in ms handleCompletion, + { onError: err => logger.withError(err).log('error occurred') }, ) + logger.log('handling...') return await retirableHandler(c) }) @@ -63,22 +65,20 @@ export function LLMAgent(options: { agent: Neuri, airiClient: Client }): Minefla options.airiClient.onEvent('input:text:voice', async (event) => { logger.withFields({ user: event.data.discord?.guildMember, message: event.data.transcription }).log('Chat message received') + // long memory + bot.memory.chatHistory.push(user(`NekoMeowww: ${event.data.transcription}`)) + + // short memory const statusPrompt = await genStatusPrompt(bot) - bot.memory.chatHistory.push(system(statusPrompt)) - bot.memory.chatHistory.push(user(`${'NekoMeowww'}: ${event.data.transcription}`)) - - // logger.withFields({ chatHistory: bot.memory.chatHistory }).log('chatHistory') - logger.withFields({ statusPrompt }).log('statusPrompt') - - const content = await agent.handleStateless([...bot.memory.chatHistory], async (c) => { + const content = await agent.handleStateless([...bot.memory.chatHistory, system(statusPrompt)], async (c: NeuriContext) => { logger.log('thinking...') const handleCompletion = async (c: NeuriContext): Promise => { - const completion = await c.reroute('action', c.messages, { model: 'openai/gpt-4o-mini' }) || { error: { message: 'Unknown error' } } + logger.log('rerouting...') + const completion = await c.reroute('action', c.messages, { model: 'openai/gpt-4o-mini' }) if (!completion || 'error' in completion) { logger.withFields({ completion }).error('Completion') - logger.withFields({ messages: c.messages }).log('messages') - throw new Error(completion?.error?.message ?? 'Unknown error') + throw completion?.error || new Error('Unknown error') } const content = await completion?.firstContent() @@ -93,8 +93,10 @@ export function LLMAgent(options: { agent: Neuri, airiClient: Client }): Minefla 3, // retryLimit 1000, // delayInterval in ms handleCompletion, + { onError: err => logger.withError(err).log('error occurred') }, ) + logger.log('handling...') return await retirableHandler(c) }) diff --git a/services/minecraft/src/skills/actions/collect-block.ts b/services/minecraft/src/skills/actions/collect-block.ts index e88f209c4..35c539ca2 100644 --- a/services/minecraft/src/skills/actions/collect-block.ts +++ b/services/minecraft/src/skills/actions/collect-block.ts @@ -9,6 +9,10 @@ import { pickupNearbyItems } from './world-interactions' const logger = useLogg('Action:CollectBlock').useGlobalConfig() +function isMessagable(err: unknown): err is { message: string } { + return (err instanceof Error || (typeof err === 'object' && !!err && 'message' in err && typeof err.message === 'string')) +} + export async function collectBlock( mineflayer: Mineflayer, blockType: string, @@ -101,6 +105,10 @@ export async function collectBlock( } catch (err) { logger.log(`Failed to collect ${blockType}: ${err}.`) + if (isMessagable(err) && err.message.includes('Digging aborted')) { + break + } + continue } } diff --git a/services/minecraft/src/utils/reliability.ts b/services/minecraft/src/utils/reliability.ts index 91050f381..3d277f53e 100644 --- a/services/minecraft/src/utils/reliability.ts +++ b/services/minecraft/src/utils/reliability.ts @@ -8,17 +8,28 @@ import { sleep } from './helper' * @param func Function to be called * @returns A wrapped function with the same signature as func */ -export function toRetriable(retryLimit: number, delayInterval: number, func: (...args: A[]) => Promise): (...args: A[]) => Promise { +export function toRetriable( + retryLimit: number, + delayInterval: number, + func: (...args: A[]) => Promise, + hooks?: { + onError?: (err: unknown) => void + }, +): (...args: A[]) => Promise { let retryCount = 0 return async function (args: A): Promise { try { return await func(args) } catch (err) { + if (hooks?.onError) { + hooks.onError(err) + } + if (retryCount < retryLimit) { retryCount++ await sleep(delayInterval) - return await toRetriable(retryLimit, delayInterval, func)(args) + return await toRetriable(retryLimit - retryCount, delayInterval, func)(args) } else { throw err