From fbd19aed0228eddfbc7b8efdebefe338fbc34d41 Mon Sep 17 00:00:00 2001 From: RainbowBird Date: Tue, 7 Jan 2025 03:34:52 +0800 Subject: [PATCH] chore: actions unit test --- services/minecraft/src/agents/actions.test.ts | 43 ++++++++++++++++++ services/minecraft/src/agents/openai.test.ts | 44 ++++++++++--------- services/minecraft/src/composables/bot.ts | 14 ++++++ services/minecraft/src/composables/world.ts | 7 ++- services/minecraft/src/prompts/agent.ts | 23 +++++----- 5 files changed, 96 insertions(+), 35 deletions(-) create mode 100644 services/minecraft/src/agents/actions.test.ts diff --git a/services/minecraft/src/agents/actions.test.ts b/services/minecraft/src/agents/actions.test.ts new file mode 100644 index 000000000..198eef5a9 --- /dev/null +++ b/services/minecraft/src/agents/actions.test.ts @@ -0,0 +1,43 @@ +import { messages, system, user } from 'neuri/openai' +import { beforeAll, describe, it } from 'vitest' +import { createBot, useBot } from '../composables/bot' +import { botConfig, initEnv } from '../composables/config' +import { genActionAgentPrompt } from '../prompts/agent' +import { initLogger } from '../utils/logger' +import { initAgent } from './openai' + +describe('actions agent', { timeout: 0 }, () => { + beforeAll(() => { + initLogger() + initEnv() + createBot(botConfig) + }) + + it('should split question into actions', async () => { + const { ctx } = useBot() + const agent = await initAgent(ctx) + + function testFn() { + return new Promise((resolve) => { + ctx.bot.on('spawn', async () => { + const text = await agent.handle(messages( + system(genActionAgentPrompt(ctx)), + user('Help me to cut down the tree'), + ), async (c) => { + const completion = await c.reroute('action', c.messages, { model: 'openai/gpt-4o-mini' }) + + console.log(completion) + + return await completion?.firstContent() + }) + + console.log(text) + + resolve() + }) + }) + } + + await testFn() + }) +}) diff --git a/services/minecraft/src/agents/openai.test.ts b/services/minecraft/src/agents/openai.test.ts index c7ea1644b..724bc577d 100644 --- a/services/minecraft/src/agents/openai.test.ts +++ b/services/minecraft/src/agents/openai.test.ts @@ -2,7 +2,7 @@ import { messages, system, user } from 'neuri/openai' import { beforeAll, describe, expect, it } from 'vitest' import { createBot, useBot } from '../composables/bot' import { botConfig, initEnv } from '../composables/config' -import { basicSystemPrompt, genQueryAgentPrompt } from '../prompts/agent' +import { genQueryAgentPrompt, genSystemBasicPrompt } from '../prompts/agent' import { initLogger } from '../utils/logger' import { initAgent } from './openai' @@ -17,32 +17,36 @@ describe('openAI agent', { timeout: 10000 }, () => { const { ctx } = useBot() const agent = await initAgent(ctx) - const text = await agent.handle( - messages( - system(basicSystemPrompt('airi')), - user('Hello, who are you?'), - ), - async (c) => { - const completion = await c.reroute('query', c.messages, { model: 'openai/gpt-4o-mini' }) - return await completion?.firstContent() - }, - ) + ctx.bot.once('spawn', async () => { + const text = await agent.handle( + messages( + system(genSystemBasicPrompt('airi')), + user('Hello, who are you?'), + ), + async (c) => { + const completion = await c.reroute('query', c.messages, { model: 'openai/gpt-4o-mini' }) + return await completion?.firstContent() + }, + ) - expect(text?.toLowerCase()).toContain('airi') + expect(text?.toLowerCase()).toContain('airi') + }) }) it('should choose right query command', async () => { const { ctx } = useBot() const agent = await initAgent(ctx) - const text = await agent.handle(messages( - system(genQueryAgentPrompt(ctx)), - user('What are you status?'), - ), async (c) => { - const completion = await c.reroute('query', c.messages, { model: 'openai/gpt-4o-mini' }) - return await completion?.firstContent() - }) + ctx.bot.once('spawn', async () => { + const text = await agent.handle(messages( + system(genQueryAgentPrompt(ctx)), + user('What are you status?'), + ), async (c) => { + const completion = await c.reroute('query', c.messages, { model: 'openai/gpt-4o-mini' }) + return await completion?.firstContent() + }) - expect(text?.toLowerCase()).toContain('position') + expect(text?.toLowerCase()).toContain('position') + }) }) }) diff --git a/services/minecraft/src/composables/bot.ts b/services/minecraft/src/composables/bot.ts index 42079d112..2cc62e238 100644 --- a/services/minecraft/src/composables/bot.ts +++ b/services/minecraft/src/composables/bot.ts @@ -1,3 +1,4 @@ +import type { Position } from './../skills/base' import type { BotInternalEventHandlers, BotInternalEvents } from './events' import { useLogg } from '@guiiai/logg' import mineflayer, { type Bot, type BotOptions } from 'mineflayer' @@ -9,6 +10,7 @@ let ctx: BotContext | undefined export interface BotContext { bot: Bot botName: string + ready: boolean components: Map @@ -21,6 +23,12 @@ export interface BotContext { } status: Map + // status: { + // position: Position + // health: number + // weather: string + // timeOfDay: string + // } health: { value: number @@ -43,6 +51,7 @@ export interface ComponentLifecycle { export function createBot(options: BotOptions): Bot { logger.withFields({ options }).log('Creating bot') ctx = { + ready: false, bot: mineflayer.createBot({ host: options.host, port: options.port, @@ -112,6 +121,11 @@ export function createBot(options: BotOptions): Bot { ctx.health.value = ctx.bot.health }) + ctx.bot.once('spawn', () => { + ctx!.ready = true + logger.log('Bot ready') + }) + ctx.bot.on('death', () => { logger.error('Bot died') }) diff --git a/services/minecraft/src/composables/world.ts b/services/minecraft/src/composables/world.ts index 9a83f0339..0592bdeb6 100644 --- a/services/minecraft/src/composables/world.ts +++ b/services/minecraft/src/composables/world.ts @@ -36,7 +36,7 @@ export function getNearestFreeSpace(ctx: WorldContext, size: number = 1, distanc export function getNearestBlocks(ctx: WorldContext, blockTypes: string[] | string | null = null, distance: number = 16, count: number = 10000): Block[] { const blockIds = blockTypes === null ? mc.getAllBlockIds(['air']) - : (Array.isArray(blockTypes) ? blockTypes : [blockTypes]).map(mc.getBlockId) + : (Array.isArray(blockTypes) ? blockTypes : [blockTypes]).map(mc.getBlockId).filter((id): id is number => id !== null) const positions = ctx.bot.findBlocks({ matching: blockIds, maxDistance: distance, count }) @@ -97,9 +97,8 @@ export function getInventoryCounts(ctx: WorldContext): Record { export function getCraftableItems(ctx: WorldContext): string[] { const table = getNearestBlock(ctx, 'crafting_table') || getInventoryStacks(ctx).find(item => item.name === 'crafting_table') - return mc.getAllItems() - .filter(item => ctx.bot.recipesFor(item.id, null, 1, table).length > 0) + .filter(item => ctx.bot.recipesFor(item.id, null, 1, table as Block | null).length > 0) .map(item => item.name) } @@ -136,7 +135,7 @@ export function getNearbyBlockTypes(ctx: WorldContext, distance: number = 16): s export async function isClearPath(ctx: WorldContext, target: Entity): Promise { const movements = new pf.Movements(ctx.bot) movements.canDig = false - movements.canPlaceOn = false + // movements.canPlaceOn = false // TODO: fix this const goal = new pf.goals.GoalNear( target.position.x, diff --git a/services/minecraft/src/prompts/agent.ts b/services/minecraft/src/prompts/agent.ts index 8c41887c0..bc7d23dfa 100644 --- a/services/minecraft/src/prompts/agent.ts +++ b/services/minecraft/src/prompts/agent.ts @@ -1,15 +1,14 @@ import type { BotContext } from '../composables/bot' import { getStatusToString } from '../components/status' -export function basicSystemPrompt(botName: string): string { +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 genSystemPrompt(ctx: BotContext): string { - return `${basicSystemPrompt(ctx.botName)} - -${ctx.prompt.selfPrompt} +export function genActionAgentPrompt(ctx: BotContext): string { + // ${ctx.prompt.selfPrompt} + return `${genSystemBasicPrompt(ctx.botName)} 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 @@ -24,19 +23,21 @@ Respond only as ${ctx.botName}, never output '(FROM OTHER BOT)'or pretend to be If you have nothing to say or do, respond with an just a tab '\t'. This is extremely important to me, take a deep breath and have fun :) -Summarized memory: '${ctx.memory.getSummary()}' +I will give you the following information: +${getStatusToString(ctx)} +` + +/** + * Summarized memory: '${ctx.memory.getSummary()}' $STATS $INVENTORY $COMMAND_DOCS $EXAMPLES - -Conversation Begin: -` + */ } export function genQueryAgentPrompt(ctx: BotContext): string { - const prompt = ` -You are a helpful assistant that asks questions to help me decide the next immediate + 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.