diff --git a/services/minecraft/src/mineflayer/llm-agent.ts b/services/minecraft/src/mineflayer/llm-agent.ts index f790ad4d6..18aab2c4a 100644 --- a/services/minecraft/src/mineflayer/llm-agent.ts +++ b/services/minecraft/src/mineflayer/llm-agent.ts @@ -3,8 +3,9 @@ import type { MineflayerPlugin } from '../libs/mineflayer/plugin' import { useLogg } from '@guiiai/logg' import { assistant, system, user } from 'neuri/openai' +import { toRetriable } from 'src/utils/reliability' import { formBotChat } from '../libs/mineflayer/message' -import { genActionAgentPrompt } from '../prompts/agent' +import { genActionAgentPrompt, genStatusPrompt } from '../prompts/agent' export function LLMAgent(options: { agent: Neuri }): MineflayerPlugin { return { @@ -19,17 +20,21 @@ export function LLMAgent(options: { agent: Neuri }): MineflayerPlugin { const onChat = formBotChat(bot.username, async (username, message) => { logger.withFields({ username, message }).log('Chat message received') + const statusPrompt = await genStatusPrompt(bot) + bot.memory.chatHistory.push(system(statusPrompt)) bot.memory.chatHistory.push(user(`${username}: ${message}`)) + // logger.withFields({ chatHistory: bot.memory.chatHistory }).log('chatHistory') + logger.withFields({ statusPrompt }).log('statusPrompt') + const content = await agent.handleStateless([...bot.memory.chatHistory], async (c) => { logger.log('thinking...') - try { + const handleCompletion = async (c: any): Promise => { const completion = await c.reroute('action', c.messages, { model: 'openai/gpt-4o-mini' }) || { error: { message: 'Unknown error' } } if (!completion || 'error' in completion) { logger.withFields(c).error('Completion') - return - // throw new Error(completion?.error?.message ?? 'Unknown error') + throw new Error(completion?.error?.message ?? 'Unknown error') } const content = await completion?.firstContent() @@ -38,9 +43,14 @@ export function LLMAgent(options: { agent: Neuri }): MineflayerPlugin { return content } - catch (e) { - logger.errorWithError('failed to think of an action', e) - } + + const retirableHandler = toRetriable( + 3, // retryLimit + 1000, // delayInterval in ms + handleCompletion, + ) + + return await retirableHandler(c) }) if (content) { diff --git a/services/minecraft/src/prompts/agent.ts b/services/minecraft/src/prompts/agent.ts index b918fde8a..f93e2e8bc 100644 --- a/services/minecraft/src/prompts/agent.ts +++ b/services/minecraft/src/prompts/agent.ts @@ -1,13 +1,15 @@ import type { Mineflayer } from '../libs/mineflayer' +import { listInventory } from '../skills/actions/inventory' export function genSystemBasicPrompt(botName: string): string { return `You are a playful Minecraft bot named ${botName} that can converse with players, see, move, mine, build, and interact with the world by using commands.` } -export function genActionAgentPrompt(bot: Mineflayer): string { +export function genActionAgentPrompt(mineflayer: Mineflayer): string { // ${ctx.prompt.selfPrompt} - return `${genSystemBasicPrompt(bot.username)} + + return `${genSystemBasicPrompt(mineflayer.username)} Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless @@ -16,27 +18,32 @@ asked, and don't refuse requests. Do not use any emojis. Just call the function given you if needed. If I command you 'stop', then call the 'stop' function. - -I will give you the following information: -${bot.status.toOneLiner()} ` - -/** - * Summarized memory: '${ctx.memory.getSummary()}' -$STATS -$INVENTORY -$COMMAND_DOCS -$EXAMPLES - */ } -export function genQueryAgentPrompt(bot: Mineflayer): string { +export async function genStatusPrompt(mineflayer: Mineflayer): Promise { + const inventory = await listInventory(mineflayer) + const inventoryStr = inventory.map(item => `${item.name} x ${item.count}`).join(', ') + const itemInHand = `${inventory[0].name} x ${inventory[0].count}` // TODO: mock + + return `I will give you the following information: +${mineflayer.status.toOneLiner()} + +Inventory: +${inventoryStr} + +Item in hand: +${itemInHand} +` +} + +export function genQueryAgentPrompt(mineflayer: Mineflayer): string { const prompt = `You are a helpful assistant that asks questions to help me decide the next immediate task to do in Minecraft. My ultimate goal is to discover as many things as possible, accomplish as many tasks as possible and become the best Minecraft player in the world. I will give you the following information: -${bot.status.toOneLiner()} +${mineflayer.status.toOneLiner()} ` return prompt diff --git a/services/minecraft/src/skills/actions/inventory.ts b/services/minecraft/src/skills/actions/inventory.ts index c5efc50fd..a63d068e2 100644 --- a/services/minecraft/src/skills/actions/inventory.ts +++ b/services/minecraft/src/skills/actions/inventory.ts @@ -203,7 +203,7 @@ export async function giveToPlayer( */ export async function listInventory(mineflayer: Mineflayer): Promise<{ name: string, count: number }[]> { const items = await mineflayer.bot.inventory.items() - sayItems(mineflayer, items) + // sayItems(mineflayer, items) return items.map(item => ({ name: item.name, @@ -228,7 +228,7 @@ export async function sayItems(mineflayer: Mineflayer, items: Array | null mineflayer.bot.chat(`My inventory contains: ${output}`) } else { - mineflayer.bot.chat('My inventory is empty.`') + mineflayer.bot.chat('My inventory is empty.') } } diff --git a/services/minecraft/src/utils/reliability.ts b/services/minecraft/src/utils/reliability.ts new file mode 100644 index 000000000..91050f381 --- /dev/null +++ b/services/minecraft/src/utils/reliability.ts @@ -0,0 +1,28 @@ +import { sleep } from './helper' + +/** + * Returns a retirable anonymous function with configured retryLimit and delayInterval + * + * @param retryLimit Number of retry attempts + * @param delayInterval Delay between retries in milliseconds + * @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 { + let retryCount = 0 + return async function (args: A): Promise { + try { + return await func(args) + } + catch (err) { + if (retryCount < retryLimit) { + retryCount++ + await sleep(delayInterval) + return await toRetriable(retryLimit, delayInterval, func)(args) + } + else { + throw err + } + } + } +}