From ddc2665bc99b6f1354f7352b52e2a9b2feb11d22 Mon Sep 17 00:00:00 2001 From: RainbowBird Date: Mon, 6 Jan 2025 22:50:43 +0800 Subject: [PATCH] chore(test): should choose right command --- services/minecraft/src/agents/openai.test.ts | 30 +++++++--- services/minecraft/src/agents/openai.ts | 9 +-- services/minecraft/src/agents/query.ts | 58 ++----------------- services/minecraft/src/components/command.ts | 8 +-- services/minecraft/src/components/echo.ts | 4 +- services/minecraft/src/components/follow.ts | 6 +- .../minecraft/src/components/pathfinder.ts | 6 +- services/minecraft/src/components/status.ts | 38 ++++++------ services/minecraft/src/composables/bot.ts | 2 + services/minecraft/src/composables/command.ts | 2 +- services/minecraft/src/middlewares/chat.ts | 2 +- services/minecraft/src/prompts/agent.ts | 38 +++--------- services/minecraft/src/utils/mcdata.ts | 2 +- 13 files changed, 77 insertions(+), 128 deletions(-) diff --git a/services/minecraft/src/agents/openai.test.ts b/services/minecraft/src/agents/openai.test.ts index a04c1fbf7..64c05ca6f 100644 --- a/services/minecraft/src/agents/openai.test.ts +++ b/services/minecraft/src/agents/openai.test.ts @@ -1,20 +1,21 @@ -import { useLogg } from '@guiiai/logg' import { messages, system, user } from 'neuri/openai' - import { beforeAll, describe, expect, it } from 'vitest' -import { initEnv } from '../composables/config' -import { basicSystemPrompt } from '../prompts/agent' +import { createBot, useBot } from '../composables/bot' +import { botConfig, initEnv } from '../composables/config' +import { basicSystemPrompt, genQueryAgentPrompt } from '../prompts/agent' import { initLogger } from '../utils/logger' import { initAgent } from './openai' -describe('openAI agent', () => { +describe('openAI agent', { timeout: 10000 }, () => { beforeAll(() => { initLogger() initEnv() + createBot(botConfig) }) it('should initialize the agent', async () => { - const agent = await initAgent() + const { ctx } = useBot() + const agent = await initAgent(ctx) const text = await agent.handle( messages( @@ -22,11 +23,26 @@ describe('openAI agent', () => { user('Hello, who are you?'), ), async (c) => { - const completion = await c.reroute('query', c.messages, { model: 'gpt-4o-mini' }) + const completion = await c.reroute('query', c.messages, { model: 'openai/gpt-4o-mini' }) return await completion?.firstContent() }, ) expect(text?.toLowerCase()).toContain('airi') }) + + it('should choose right 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() + }) + + expect(text?.toLowerCase()).toContain('position') + }) }) diff --git a/services/minecraft/src/agents/openai.ts b/services/minecraft/src/agents/openai.ts index ef9042f4b..991e99fd9 100644 --- a/services/minecraft/src/agents/openai.ts +++ b/services/minecraft/src/agents/openai.ts @@ -1,4 +1,5 @@ import type { Agent, Neuri } from 'neuri' +import type { BotContext } from '../composables/bot' import { useLogg } from '@guiiai/logg' import { agent, neuri } from 'neuri' import { openaiConfig } from '../composables/config' @@ -8,11 +9,11 @@ const agents = new Set>() const logger = useLogg('openai').useGlobalConfig() -export async function initAgent(): Promise { +export async function initAgent(ctx: BotContext): Promise { logger.log('Initializing agent') let n = neuri() - agents.add(initQueryAgent()) + agents.add(initQueryAgent(ctx)) agents.forEach(agent => n = n.agent(agent)) @@ -24,7 +25,7 @@ export async function initAgent(): Promise { }) } -export async function initQueryAgent(): Promise { +export async function initQueryAgent(ctx: BotContext): Promise { logger.log('Initializing query agent') let queryAgent = agent('query') @@ -32,7 +33,7 @@ export async function initQueryAgent(): Promise { queryAgent = queryAgent.tool( query.name, query.schema, - query.perform, + query.perform(ctx), { description: query.description }, ) }) diff --git a/services/minecraft/src/agents/query.ts b/services/minecraft/src/agents/query.ts index 51f3865e4..57391df95 100644 --- a/services/minecraft/src/agents/query.ts +++ b/services/minecraft/src/agents/query.ts @@ -1,5 +1,7 @@ import type { Bot } from 'mineflayer' +import type { BotContext } from '../composables/bot' import { z } from 'zod' +import { getStatus } from '../components/status' // Core types type QueryResult = string | Promise @@ -25,37 +27,11 @@ interface QueryBotContext { } } -interface QueryAgentBotContext { - bot: Bot - name: string - actions: { - currentActionLabel: string - } - isIdle: () => boolean - memory_bank: { - getKeys: () => string[] - } -} - -export function createQueryAgentBotContext(bot: Bot): QueryAgentBotContext { - return { - bot, - name: bot.username, - actions: { - currentActionLabel: bot.actions.currentActionLabel, - }, - isIdle: () => bot.actions.isIdle(), - memory_bank: { - getKeys: () => bot.memory_bank.getKeys(), - }, - } -} - interface Query { readonly name: string readonly description: string readonly schema: z.ZodObject - readonly perform: (agent: QueryAgentBotContext) => () => QueryResult + readonly perform: (ctx: BotContext) => () => QueryResult } // Utils @@ -75,32 +51,8 @@ function createStatsQuery(): Query { name: 'stats', description: 'Get your bot\'s location, health, hunger, and time of day.', schema: z.object({}), - perform: (agent: QueryAgentBotContext) => (): string => { - const { bot } = agent - const pos = bot.entity.position - const weather = bot.rainState > 0 ? 'Rain' : bot.thunderState > 0 ? 'Thunderstorm' : 'Clear' - const timeOfDay = bot.time.timeOfDay < 6000 - ? 'Morning' - : bot.time.timeOfDay < 12000 ? 'Afternoon' : 'Night' - const action = agent.isIdle() ? 'Idle' : agent.actions.currentActionLabel - - const players = ctx.world.getNearbyPlayerNames(bot) - .filter(p => !ctx.convoManager.getInGameAgents().includes(p)) - const bots = ctx.convoManager.getInGameAgents() - .filter(b => b !== agent.name) - - return pad(`STATS -- Position: x: ${pos.x.toFixed(2)}, y: ${pos.y.toFixed(2)}, z: ${pos.z.toFixed(2)} -- Gamemode: ${bot.game.gameMode} -- Health: ${Math.round(bot.health)} / 20 -- Hunger: ${Math.round(bot.food)} / 20 -- Biome: ${ctx.world.getBiomeName(bot)} -- Weather: ${weather} -- Time: ${timeOfDay} -- Current Action: ${action} -- Nearby Human Players: ${players.length > 0 ? players.join(', ') : 'None.'} -- Nearby Bot Players: ${bots.length > 0 ? bots.join(', ') : 'None.'} -${bot.modes.getMiniDocs()}`) + perform: (ctx: BotContext) => (): string => { + return Array.from(getStatus(ctx).entries()).map(([key, value]) => `${key}: ${value}`).join('\n') }, } } diff --git a/services/minecraft/src/components/command.ts b/services/minecraft/src/components/command.ts index 782d04c15..a81211212 100644 --- a/services/minecraft/src/components/command.ts +++ b/services/minecraft/src/components/command.ts @@ -1,8 +1,8 @@ -import type { BotContext, ComponentLifecycle } from '@/composables/bot' -import { commands } from '@/composables/command' -import { formBotChat } from '@/middlewares/chat' -import { parseCommand } from '@/middlewares/command' +import type { BotContext, ComponentLifecycle } from '../composables/bot' import { useLogg } from '@guiiai/logg' +import { commands } from '../composables/command' +import { formBotChat } from '../middlewares/chat' +import { parseCommand } from '../middlewares/command' const logger = useLogg('command').useGlobalConfig() diff --git a/services/minecraft/src/components/echo.ts b/services/minecraft/src/components/echo.ts index 2476fd26f..0280cb4ce 100644 --- a/services/minecraft/src/components/echo.ts +++ b/services/minecraft/src/components/echo.ts @@ -1,6 +1,6 @@ -import type { BotContext, ComponentLifecycle } from '@/composables/bot' -import { formBotChat } from '@/middlewares/chat' +import type { BotContext, ComponentLifecycle } from '../composables/bot' import { useLogg } from '@guiiai/logg' +import { formBotChat } from '../middlewares/chat' const logger = useLogg('echo').useGlobalConfig() diff --git a/services/minecraft/src/components/follow.ts b/services/minecraft/src/components/follow.ts index 0888c53c9..6ac5004e9 100644 --- a/services/minecraft/src/components/follow.ts +++ b/services/minecraft/src/components/follow.ts @@ -1,8 +1,8 @@ -import type { BotContext, ComponentLifecycle } from '@/composables/bot' -import type { CommandContext } from '@/middlewares/command' -import { registerCommand } from '@/composables/command' +import type { BotContext, ComponentLifecycle } from '../composables/bot' +import type { CommandContext } from '../middlewares/command' import { useLogg } from '@guiiai/logg' import pathfinderModel from 'mineflayer-pathfinder' +import { registerCommand } from '../composables/command' const { goals, Movements, pathfinder } = pathfinderModel diff --git a/services/minecraft/src/components/pathfinder.ts b/services/minecraft/src/components/pathfinder.ts index 3d7d8746e..e5fc3d32e 100644 --- a/services/minecraft/src/components/pathfinder.ts +++ b/services/minecraft/src/components/pathfinder.ts @@ -1,8 +1,8 @@ -import type { BotContext, ComponentLifecycle } from '@/composables/bot' -import type { CommandContext } from '@/middlewares/command' -import { registerCommand } from '@/composables/command' +import type { BotContext, ComponentLifecycle } from '../composables/bot' +import type { CommandContext } from '../middlewares/command' import { useLogg } from '@guiiai/logg' import pathfinderModel from 'mineflayer-pathfinder' +import { registerCommand } from '../composables/command' const { goals, Movements, pathfinder } = pathfinderModel diff --git a/services/minecraft/src/components/status.ts b/services/minecraft/src/components/status.ts index 626c0d29b..0f7f7486f 100644 --- a/services/minecraft/src/components/status.ts +++ b/services/minecraft/src/components/status.ts @@ -1,29 +1,31 @@ -import type { BotContext, ComponentLifecycle } from '@/composables/bot' -import { registerCommand } from '@/composables/command' +import type { BotContext, ComponentLifecycle } from '../composables/bot' import { useLogg } from '@guiiai/logg' +import { registerCommand } from '../composables/command' + +export function getStatus(ctx: BotContext): Map { + const status = new Map() + const pos = ctx.bot.entity.position + const weather = ctx.bot.isRaining ? 'Rain' : ctx.bot.thunderState ? 'Thunderstorm' : 'Clear' + const timeOfDay = ctx.bot.time.timeOfDay < 6000 + ? 'Morning' + : ctx.bot.time.timeOfDay < 12000 ? 'Afternoon' : 'Night' + + status.set('position', `x: ${pos.x.toFixed(2)}, y: ${pos.y.toFixed(2)}, z: ${pos.z.toFixed(2)}`) + status.set('health', `${Math.round(ctx.bot.health)} / 20`) + status.set('weather', weather) + status.set('timeOfDay', timeOfDay) + + return status +} export function createStatusComponent(ctx: BotContext): ComponentLifecycle { const logger = useLogg('status').useGlobalConfig() logger.log('Loading status component') - const handleStatus = () => { - const pos = ctx.bot.entity.position - const weather = ctx.bot.isRaining ? 'Rain' : ctx.bot.thunderState ? 'Thunderstorm' : 'Clear' - const timeOfDay = ctx.bot.time.timeOfDay < 6000 - ? 'Morning' - : ctx.bot.time.timeOfDay < 12000 ? 'Afternoon' : 'Night' - - ctx.bot.chat(`Status: -Position: x: ${pos.x.toFixed(2)}, y: ${pos.y.toFixed(2)}, z: ${pos.z.toFixed(2)} -Health: ${Math.round(ctx.bot.health)} / 20 -Hunger: ${Math.round(ctx.bot.food)} / 20 -Weather: ${weather} -Time: ${timeOfDay}`) - } - registerCommand('status', () => { logger.log('Status command received') - handleStatus() + const status = getStatus(ctx) + ctx.bot.chat(status.toString()) }) return { diff --git a/services/minecraft/src/composables/bot.ts b/services/minecraft/src/composables/bot.ts index e0f387c7c..782cf19d8 100644 --- a/services/minecraft/src/composables/bot.ts +++ b/services/minecraft/src/composables/bot.ts @@ -16,6 +16,7 @@ export interface BotContext { memory: { getSummary: () => string } + status: Map } export interface Component { @@ -43,6 +44,7 @@ export function createBot(options: BotOptions): Bot { memory: { getSummary: () => '', }, + status: new Map(), } ctx.bot.on('error', (err: Error) => { diff --git a/services/minecraft/src/composables/command.ts b/services/minecraft/src/composables/command.ts index 9d25d4461..256009a18 100644 --- a/services/minecraft/src/composables/command.ts +++ b/services/minecraft/src/composables/command.ts @@ -1,4 +1,4 @@ -import type { CommandContext } from '@/middlewares/command' +import type { CommandContext } from '../middlewares/command' export const commands = new Map void>() diff --git a/services/minecraft/src/middlewares/chat.ts b/services/minecraft/src/middlewares/chat.ts index c83955f0a..ae255d2e7 100644 --- a/services/minecraft/src/middlewares/chat.ts +++ b/services/minecraft/src/middlewares/chat.ts @@ -1,5 +1,5 @@ -import type { BotContext } from '@/composables/bot' import type { Entity } from 'prismarine-entity' +import type { BotContext } from '../composables/bot' // TODO: need to be refactored interface ChatBotContext { diff --git a/services/minecraft/src/prompts/agent.ts b/services/minecraft/src/prompts/agent.ts index 6d11c215f..990340b2d 100644 --- a/services/minecraft/src/prompts/agent.ts +++ b/services/minecraft/src/prompts/agent.ts @@ -1,4 +1,4 @@ -import type { BotContext } from '@/composables/bot' +import type { BotContext } from '../composables/bot' export function basicSystemPrompt(botName: string): string { return `You are a playful Minecraft bot named ${botName} that can converse with players, see, move, @@ -33,44 +33,20 @@ Conversation Begin: ` } -export function genQueryAgentPrompt(tools: string[], status: Map): string { - const BotContextFields: readonly string[] = [ - 'Biome', - 'Time', - 'Nearby blocks', - 'Other blocks that are recently seen', - 'Nearby entities (nearest to farthest)', - 'Health', - 'Hunger', - 'Position', - 'Equipment', - 'Inventory (xx/36)', - 'Chests', - 'Completed tasks so far', - 'Failed tasks that are too hard', - ] as const - - const formatBotContextFields = (fields: readonly string[]): string => - fields.map((field) => { - const value = status.get(field) || '...' - return `${field}: ${value}` - }).join('\n') - - const formatTools = (toolList: string[]): string => - toolList.join('\n') - +export function genQueryAgentPrompt(ctx: BotContext): 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: -${formatBotContextFields(BotContextFields)} - -And I will give you some tools to use: -${formatTools(tools)} +${Array.from(ctx.status.entries()).map(([key, value]) => `${key}: ${value}`).join('\n')} Then you can choose some of the tools to use. Use the valid JS call function to call the tool. + +## For example: +### Get the stats +stats() ` return prompt diff --git a/services/minecraft/src/utils/mcdata.ts b/services/minecraft/src/utils/mcdata.ts index 8bdc1c2f5..60340e4c8 100644 --- a/services/minecraft/src/utils/mcdata.ts +++ b/services/minecraft/src/utils/mcdata.ts @@ -2,7 +2,6 @@ * @source https://github.com/kolbytn/mindcraft */ import type { Bot } from 'mineflayer' -import { botConfig } from '@/composables/config' import minecraftData from 'minecraft-data' import { createBot } from 'mineflayer' import armorManager from 'mineflayer-armor-manager' @@ -11,6 +10,7 @@ import { plugin as collectblock } from 'mineflayer-collectblock' import { pathfinder } from 'mineflayer-pathfinder' import { plugin as pvp } from 'mineflayer-pvp' import prismarine_items from 'prismarine-item' +import { botConfig } from '../composables/config' const mc_version = botConfig.version! const mcdata = minecraftData(mc_version)