chore: actions unit test

This commit is contained in:
RainbowBird
2025-01-07 03:35:18 +08:00
parent d7a9b92623
commit fbd19aed02
5 changed files with 96 additions and 35 deletions
@@ -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<void>((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()
})
})
+24 -20
View File
@@ -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')
})
})
})
+14
View File
@@ -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<string, ComponentLifecycle>
@@ -21,6 +23,12 @@ export interface BotContext {
}
status: Map<string, string>
// 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')
})
+3 -4
View File
@@ -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<string, number> {
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<boolean> {
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,
+12 -11
View File
@@ -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.