chore(test): should choose right command

This commit is contained in:
RainbowBird
2025-01-06 22:50:43 +08:00
parent 578aad50e5
commit ddc2665bc9
13 changed files with 77 additions and 128 deletions
+23 -7
View File
@@ -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')
})
})
+5 -4
View File
@@ -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<Agent | Promise<Agent>>()
const logger = useLogg('openai').useGlobalConfig()
export async function initAgent(): Promise<Neuri> {
export async function initAgent(ctx: BotContext): Promise<Neuri> {
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<Neuri> {
})
}
export async function initQueryAgent(): Promise<Agent> {
export async function initQueryAgent(ctx: BotContext): Promise<Agent> {
logger.log('Initializing query agent')
let queryAgent = agent('query')
@@ -32,7 +33,7 @@ export async function initQueryAgent(): Promise<Agent> {
queryAgent = queryAgent.tool(
query.name,
query.schema,
query.perform,
query.perform(ctx),
{ description: query.description },
)
})
+5 -53
View File
@@ -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<string>
@@ -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<any>
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')
},
}
}
+4 -4
View File
@@ -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()
+2 -2
View File
@@ -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()
+3 -3
View File
@@ -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
@@ -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
+20 -18
View File
@@ -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<string, string> {
const status = new Map<string, string>()
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 {
@@ -16,6 +16,7 @@ export interface BotContext {
memory: {
getSummary: () => string
}
status: Map<string, string>
}
export interface Component {
@@ -43,6 +44,7 @@ export function createBot(options: BotOptions): Bot {
memory: {
getSummary: () => '',
},
status: new Map(),
}
ctx.bot.on('error', (err: Error) => {
@@ -1,4 +1,4 @@
import type { CommandContext } from '@/middlewares/command'
import type { CommandContext } from '../middlewares/command'
export const commands = new Map<string, (ctx: CommandContext) => void>()
+1 -1
View File
@@ -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 {
+7 -31
View File
@@ -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, string>): 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
+1 -1
View File
@@ -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)