diff --git a/src/agents/action/index.ts b/src/agents/action/index.ts index 7815b63..19109b0 100644 --- a/src/agents/action/index.ts +++ b/src/agents/action/index.ts @@ -1,10 +1,10 @@ import type { Mineflayer } from '../../libs/mineflayer' import type { Action } from '../../libs/mineflayer/action' import type { ActionAgent, AgentConfig } from '../../libs/mineflayer/base-agent' +import type { PlanStep } from '../planning/llm-handler' import { useBot } from '../../composables/bot' import { AbstractAgent } from '../../libs/mineflayer/base-agent' -import { ActionManager } from '../../manager/action' import { actionsList } from './tools' interface ActionState { @@ -20,7 +20,6 @@ interface ActionState { export class ActionAgentImpl extends AbstractAgent implements ActionAgent { public readonly type = 'action' as const private actions: Map - private actionManager: ActionManager private mineflayer: Mineflayer private currentActionState: ActionState @@ -28,7 +27,6 @@ export class ActionAgentImpl extends AbstractAgent implements ActionAgent { super(config) this.actions = new Map() this.mineflayer = useBot().bot - this.actionManager = new ActionManager(this.mineflayer) this.currentActionState = { executing: false, label: '', @@ -41,102 +39,46 @@ export class ActionAgentImpl extends AbstractAgent implements ActionAgent { actionsList.forEach(action => this.actions.set(action.name, action)) // Set up event listeners - // todo: nothing to call here this.on('message', async ({ sender, message }) => { await this.handleAgentMessage(sender, message) }) } protected async destroyAgent(): Promise { - await this.actionManager.stop() - this.actionManager.cancelResume() this.actions.clear() this.removeAllListeners() - this.currentActionState = { - executing: false, - label: '', - startTime: 0, - } } - public async performAction( - name: string, - params: unknown[], - options: { timeout?: number, resume?: boolean } = {}, - ): Promise { + public async performAction(step: PlanStep): Promise { if (!this.initialized) { throw new Error('Action agent not initialized') } - const action = this.actions.get(name) + const action = this.actions.get(step.tool) if (!action) { - throw new Error(`Action not found: ${name}`) + throw new Error(`Unknown action: ${step.tool}`) } - try { - this.updateActionState(true, name) - this.logger.withFields({ name, params }).log('Performing action') - - const result = await this.actionManager.runAction( - name, - async () => { - const fn = action.perform(this.mineflayer) - return await fn(...params) - }, - { - timeout: options.timeout ?? 60, - resume: options.resume ?? false, - }, - ) - - if (!result.success) { - throw new Error(result.message ?? 'Action failed') - } - - return this.formatActionOutput({ - message: result.message, - timedout: result.timedout, - interrupted: false, - }) - } - catch (error) { - this.logger.withFields({ name, params, error }).error('Failed to perform action') - throw error - } - finally { - this.updateActionState(false) - } - } + this.logger.withFields({ + action: step.tool, + description: step.description, + params: step.params, + }).log('Performing action') - public async resumeAction(name: string, params: unknown[]): Promise { - const action = this.actions.get(name) - if (!action) { - throw new Error(`Action not found: ${name}`) - } + // Update action state + this.updateActionState(true, step.description) try { - this.updateActionState(true, name) - const result = await this.actionManager.resumeAction( - name, - async () => { - const fn = action.perform(this.mineflayer) - return await fn(...params) - }, - 60, - ) - - if (!result.success) { - throw new Error(result.message ?? 'Action failed') - } - + // Execute action with provided parameters + const result = await action.perform(this.mineflayer)(...Object.values(step.params)) return this.formatActionOutput({ - message: result.message, - timedout: result.timedout, + message: result, + timedout: false, interrupted: false, }) } catch (error) { - this.logger.withFields({ name, params, error }).error('Failed to resume action') + this.logger.withError(error).error('Action failed') throw error } finally { @@ -149,13 +91,10 @@ export class ActionAgentImpl extends AbstractAgent implements ActionAgent { } private async handleAgentMessage(sender: string, message: string): Promise { - if (sender === 'system') { - if (message.includes('interrupt')) { - await this.actionManager.stop() - } - } - else { - this.logger.withFields({ sender, message }).log('Processing agent message') + if (sender === 'system' && message.includes('interrupt') && this.currentActionState.executing) { + // Handle interruption + this.logger.log('Received interrupt request') + // Additional interrupt handling logic here } } @@ -163,18 +102,17 @@ export class ActionAgentImpl extends AbstractAgent implements ActionAgent { this.currentActionState = { executing, label, - startTime: executing ? Date.now() : 0, + startTime: executing ? Date.now() : this.currentActionState.startTime, } - this.emit('actionStateChanged', this.currentActionState) } private formatActionOutput(result: { message: string | null, timedout: boolean, interrupted: boolean }): string { if (result.timedout) { - return `Action timed out: ${result.message}` + return 'Action timed out' } if (result.interrupted) { return 'Action was interrupted' } - return result.message ?? '' + return result.message || 'Action completed successfully' } } diff --git a/src/agents/action/llm-handler.ts b/src/agents/action/llm-handler.ts index 82e3aa1..1cdf747 100644 --- a/src/agents/action/llm-handler.ts +++ b/src/agents/action/llm-handler.ts @@ -1,9 +1,11 @@ import type { Agent } from 'neuri' import type { Message } from 'neuri/openai' import type { Mineflayer } from '../../libs/mineflayer' +import type { PlanStep } from '../planning/llm-handler' import { useLogg } from '@guiiai/logg' import { agent } from 'neuri' +import { system, user } from 'neuri/openai' import { BaseLLMHandler } from '../../libs/llm/base' import { actionsList } from './tools' @@ -31,6 +33,39 @@ export async function createActionNeuriAgent(mineflayer: Mineflayer): Promise { + const systemPrompt = this.generateActionSystemPrompt() + const userPrompt = this.generateActionUserPrompt(step) + const messages = [system(systemPrompt), user(userPrompt)] + + const result = await this.handleAction(messages) + return result + } + + private generateActionSystemPrompt(): string { + return `You are a Minecraft bot action executor. Your task is to execute a given step using available tools. +You have access to various tools that can help you accomplish tasks. +When using a tool: +1. Choose the most appropriate tool for the task +2. Determine the correct parameters based on the context +3. Handle any errors or unexpected situations + +Remember to: +- Be precise with tool parameters +- Consider the current state of the bot +- Handle failures gracefully` + } + + private generateActionUserPrompt(step: PlanStep): string { + return `Execute this step: ${step.description} + +Suggested tool: ${step.tool} +Params: ${JSON.stringify(step.params)} + +Please use the appropriate tool with the correct parameters to accomplish this step. +If the suggested tool is not appropriate, you may choose a different one.` + } + public async handleAction(messages: Message[]): Promise { const result = await this.config.agent.handleStateless(messages, async (context) => { this.logger.log('Processing action...') diff --git a/src/agents/planning/index.ts b/src/agents/planning/index.ts index 929a670..61d1c71 100644 --- a/src/agents/planning/index.ts +++ b/src/agents/planning/index.ts @@ -4,7 +4,7 @@ import type { ActionAgent, AgentConfig, MemoryAgent, Plan, PlanningAgent } from import { AbstractAgent } from '../../libs/mineflayer/base-agent' import { ActionAgentImpl } from '../action' -import { PlanningLLMHandler } from './llm-handler' +import { PlanningLLMHandler, type PlanStep } from './llm-handler' interface PlanContext { goal: string @@ -13,20 +13,7 @@ interface PlanContext { lastUpdate: number retryCount: number isGenerating: boolean - pendingSteps: Array<{ - action: string - params: unknown[] - }> -} - -interface PlanTemplate { - goal: string - conditions: string[] - steps: Array<{ - action: string - params: unknown[] - }> - requiresAction: boolean + pendingSteps: PlanStep[] } export interface PlanningAgentConfig extends AgentConfig { @@ -42,15 +29,12 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { private context: PlanContext | null = null private actionAgent: ActionAgent | null = null private memoryAgent: MemoryAgent | null = null - private planTemplates: Map private llmConfig: PlanningAgentConfig['llm'] private llmHandler: PlanningLLMHandler constructor(config: PlanningAgentConfig) { super(config) - this.planTemplates = new Map() this.llmConfig = config.llm - this.initializePlanTemplates() this.llmHandler = new PlanningLLMHandler({ agent: this.llmConfig.agent, model: this.llmConfig.model, @@ -82,7 +66,6 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { this.context = null this.actionAgent = null this.memoryAgent = null - this.planTemplates.clear() this.removeAllListeners() } @@ -120,7 +103,7 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { } // Create plan steps based on available actions and goal - const steps = await this.generatePlanSteps(goal, availableActions) + const steps = await this.generatePlanSteps(goal, availableActions, 'system') // Create new plan const plan: Plan = { @@ -172,110 +155,23 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { plan.status = 'in_progress' this.currentPlan = plan - // Start generating and executing steps in parallel - await this.generateAndExecutePlanSteps(plan) - - plan.status = 'completed' - } - catch (error) { - plan.status = 'failed' - throw error - } - finally { - this.context = null - } - } - - private async generateAndExecutePlanSteps(plan: Plan): Promise { - if (!this.context || !this.actionAgent) { - return - } - - // Initialize step generation - this.context.isGenerating = true - this.context.pendingSteps = [] - - // Get available actions - const availableActions = this.actionAgent.getAvailableActions() - - // Start step generation - const generationPromise = this.generateStepsStream(plan.goal, availableActions) - - // Start step execution - const executionPromise = this.executeStepsStream() - - // Wait for both generation and execution to complete - await Promise.all([generationPromise, executionPromise]) - } - - private async generateStepsStream( - goal: string, - availableActions: Action[], - ): Promise { - if (!this.context) { - return - } - - try { - // Generate steps in chunks - const generator = this.createStepGenerator(goal, availableActions) - for await (const steps of generator) { - if (!this.context.isGenerating) { - break - } - - // Add generated steps to pending queue - this.context.pendingSteps.push(...steps) - this.logger.withField('steps', steps).log('Generated new steps') - } - } - catch (error) { - this.logger.withError(error).error('Failed to generate steps') - throw error - } - finally { - this.context.isGenerating = false - } - } - - private async executeStepsStream(): Promise { - if (!this.context || !this.actionAgent) { - return - } - - try { - while (this.context.isGenerating || this.context.pendingSteps.length > 0) { - // Wait for steps to be available - if (this.context.pendingSteps.length === 0) { - await new Promise(resolve => setTimeout(resolve, 100)) - continue - } - - // Execute next step - const step = this.context.pendingSteps.shift() - if (!step) { - continue - } - + // Execute each step + for (const step of plan.steps) { try { this.logger.withField('step', step).log('Executing step') - await this.actionAgent.performAction(step.action, step.params) - this.context.lastUpdate = Date.now() - this.context.currentStep++ + await this.actionAgent.performAction(step) } catch (stepError) { this.logger.withError(stepError).error('Failed to execute step') // Attempt to adjust plan and retry - if (this.context.retryCount < 3) { + if (this.context && this.context.retryCount < 3) { this.context.retryCount++ - // Stop current generation - this.context.isGenerating = false - this.context.pendingSteps = [] // Adjust plan and restart const adjustedPlan = await this.adjustPlan( - this.currentPlan!, + plan, stepError instanceof Error ? stepError.message : 'Unknown error', + 'system', ) await this.executePlan(adjustedPlan) return @@ -284,89 +180,169 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { throw stepError } } + + plan.status = 'completed' } catch (error) { - this.logger.withError(error).error('Failed to execute steps') + plan.status = 'failed' throw error } - } - - private async *createStepGenerator( - goal: string, - availableActions: Action[], - ): AsyncGenerator, void, unknown> { - // First, try to find a matching template - const template = this.findMatchingTemplate(goal) - if (template) { - this.logger.log('Using plan template') - yield template.steps - return - } - - // If no template matches, use LLM to generate plan in chunks - this.logger.log('Generating plan using LLM') - const chunkSize = 3 // Generate 3 steps at a time - let currentChunk = 1 - - while (true) { - const steps = await this.llmHandler.generatePlan( - goal, - availableActions, - `Generate steps ${currentChunk * chunkSize - 2} to ${currentChunk * chunkSize}`, - ) - - if (steps.length === 0) { - break - } - - yield steps - currentChunk++ - - // Check if we've generated enough steps or if the goal is achieved - if (steps.length < chunkSize || await this.isGoalAchieved(goal)) { - break - } - } - } - - private async isGoalAchieved(goal: string): Promise { - if (!this.context || !this.actionAgent) { - return false - } - - const requirements = this.parseGoalRequirements(goal) - - // Check inventory for required items - if (requirements.needsItems && requirements.items) { - const inventorySteps = this.generateGatheringSteps(requirements.items) - if (inventorySteps.length > 0) { - this.context.pendingSteps.push(...inventorySteps) - return false - } - } - - // Check location requirements - if (requirements.needsMovement && requirements.location) { - const movementSteps = this.generateMovementSteps(requirements.location) - if (movementSteps.length > 0) { - this.context.pendingSteps.push(...movementSteps) - return false - } - } - - // Check interaction requirements - if (requirements.needsInteraction && requirements.target) { - const interactionSteps = this.generateInteractionSteps(requirements.target) - if (interactionSteps.length > 0) { - this.context.pendingSteps.push(...interactionSteps) - return false - } + finally { + this.context = null } - - return true } - public async adjustPlan(plan: Plan, feedback: string): Promise { + // private async generateStepsStream( + // goal: string, + // availableActions: Action[], + // sender: string, + // ): Promise { + // if (!this.context) { + // return + // } + + // try { + // // Generate all steps at once + // const steps = await this.llmHandler.generatePlan(goal, availableActions, sender) + // if (!this.context.isGenerating) { + // return + // } + + // // Add all steps to pending queue + // this.context.pendingSteps.push(...steps) + // this.logger.withField('steps', steps).log('Generated steps') + // } + // catch (error) { + // this.logger.withError(error).error('Failed to generate steps') + // throw error + // } + // finally { + // this.context.isGenerating = false + // } + // } + + // private async executeStepsStream(): Promise { + // if (!this.context || !this.actionAgent) { + // return + // } + + // try { + // while (this.context.isGenerating || this.context.pendingSteps.length > 0) { + // // Wait for steps to be available + // if (this.context.pendingSteps.length === 0) { + // await new Promise(resolve => setTimeout(resolve, 100)) + // continue + // } + + // // Execute next step + // const step = this.context.pendingSteps.shift() + // if (!step) { + // continue + // } + + // try { + // this.logger.withField('step', step).log('Executing step') + // await this.actionAgent.performAction(step) + // this.context.lastUpdate = Date.now() + // this.context.currentStep++ + // } + // catch (stepError) { + // this.logger.withError(stepError).error('Failed to execute step') + + // // Attempt to adjust plan and retry + // if (this.context.retryCount < 3) { + // this.context.retryCount++ + // // Stop current generation + // this.context.isGenerating = false + // this.context.pendingSteps = [] + // // Adjust plan and restart + // const adjustedPlan = await this.adjustPlan( + // this.currentPlan!, + // stepError instanceof Error ? stepError.message : 'Unknown error', + // 'system', + // ) + // await this.executePlan(adjustedPlan) + // return + // } + + // throw stepError + // } + // } + // } + // catch (error) { + // this.logger.withError(error).error('Failed to execute steps') + // throw error + // } + // } + + // private async *createStepGenerator( + // goal: string, + // availableActions: Action[], + // ): AsyncGenerator { + // // Use LLM to generate plan in chunks + // this.logger.log('Generating plan using LLM') + // const chunkSize = 3 // Generate 3 steps at a time + // let currentChunk = 1 + + // while (true) { + // const steps = await this.llmHandler.generatePlan( + // goal, + // availableActions, + // `Generate steps ${currentChunk * chunkSize - 2} to ${currentChunk * chunkSize}`, + // ) + + // if (steps.length === 0) { + // break + // } + + // yield steps + // currentChunk++ + + // // Check if we've generated enough steps or if the goal is achieved + // if (steps.length < chunkSize || await this.isGoalAchieved(goal)) { + // break + // } + // } + // } + + // private async isGoalAchieved(goal: string): Promise { + // if (!this.context || !this.actionAgent) { + // return false + // } + + // const requirements = this.parseGoalRequirements(goal) + + // // Check inventory for required items + // if (requirements.needsItems && requirements.items) { + // const inventorySteps = this.generateGatheringSteps(requirements.items) + // if (inventorySteps.length > 0) { + // this.context.pendingSteps.push(...inventorySteps) + // return false + // } + // } + + // // Check location requirements + // if (requirements.needsMovement && requirements.location) { + // const movementSteps = this.generateMovementSteps(requirements.location) + // if (movementSteps.length > 0) { + // this.context.pendingSteps.push(...movementSteps) + // return false + // } + // } + + // // Check interaction requirements + // if (requirements.needsInteraction && requirements.target) { + // const interactionSteps = this.generateInteractionSteps(requirements.target) + // if (interactionSteps.length > 0) { + // this.context.pendingSteps.push(...interactionSteps) + // return false + // } + // } + + // return true + // } + + public async adjustPlan(plan: Plan, feedback: string, sender: string): Promise { if (!this.initialized) { throw new Error('Planning agent not initialized') } @@ -383,7 +359,7 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { const recoverySteps = this.generateRecoverySteps(feedback) // Generate new steps from the current point - const newSteps = await this.generatePlanSteps(plan.goal, availableActions, feedback) + const newSteps = await this.generatePlanSteps(plan.goal, availableActions, sender, feedback) // Create adjusted plan const adjustedPlan: Plan = { @@ -409,59 +385,119 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { } } - private generateGatheringSteps(items: string[]): Array<{ action: string, params: unknown[] }> { - const steps: Array<{ action: string, params: unknown[] }> = [] - - for (const item of items) { - steps.push( - { action: 'searchForBlock', params: [item, 64] }, - { action: 'collectBlocks', params: [item, 1] }, - ) - } - - return steps - } - - private generateMovementSteps(location: { x?: number, y?: number, z?: number }): Array<{ action: string, params: unknown[] }> { - if (location.x !== undefined && location.y !== undefined && location.z !== undefined) { - return [{ - action: 'goToCoordinates', - params: [location.x, location.y, location.z, 1], - }] - } - return [] - } - - private generateInteractionSteps(target: string): Array<{ action: string, params: unknown[] }> { - return [{ - action: 'activate', - params: [target], - }] - } - - private generateRecoverySteps(feedback: string): Array<{ action: string, params: unknown[] }> { - const steps: Array<{ action: string, params: unknown[] }> = [] + // private generateGatheringSteps(items: string[]): PlanStep[] { + // const steps: PlanStep[] = [] + + // for (const item of items) { + // steps.push( + // { + // description: `Search for ${item} in the surrounding area`, + // tool: 'searchForBlock', + // params: { + // blockType: item, + // range: 64, + // }, + // }, + // { + // description: `Collect ${item} from the found location`, + // tool: 'collectBlocks', + // params: { + // blockType: item, + // count: 1, + // }, + // }, + // ) + // } + + // return steps + // } + + // private generateMovementSteps(location: { x?: number, y?: number, z?: number }): PlanStep[] { + // if (location.x !== undefined && location.y !== undefined && location.z !== undefined) { + // return [{ + // description: `Move to coordinates (${location.x}, ${location.y}, ${location.z})`, + // tool: 'goToCoordinates', + // params: { + // x: location.x, + // y: location.y, + // z: location.z, + // }, + // }] + // } + // return [] + // } + + // private generateInteractionSteps(target: string): PlanStep[] { + // return [{ + // description: `Interact with ${target}`, + // tool: 'activate', + // params: { + // target, + // }, + // }] + // } + + private generateRecoverySteps(feedback: string): PlanStep[] { + const steps: PlanStep[] = [] if (feedback.includes('not found')) { - steps.push({ action: 'searchForBlock', params: ['any', 128] }) + steps.push({ + description: 'Search in a wider area', + tool: 'searchForBlock', + params: { + blockType: 'oak_log', + range: 64, + }, + }) } if (feedback.includes('inventory full')) { - steps.push({ action: 'discard', params: ['cobblestone', 64] }) + steps.push({ + description: 'Clear inventory space', + tool: 'discard', + params: { + blockType: 'oak_log', + count: 1, + }, + }) } if (feedback.includes('blocked') || feedback.includes('cannot reach')) { - steps.push({ action: 'moveAway', params: [5] }) + steps.push({ + description: 'Move away from obstacles', + tool: 'moveAway', + params: { + range: 64, + }, + }) } if (feedback.includes('too far')) { - steps.push({ action: 'moveAway', params: [-3] }) // Move closer + steps.push({ + description: 'Move closer to target', + tool: 'moveAway', + params: { + range: 64, + }, + }) } if (feedback.includes('need tool')) { steps.push( - { action: 'craftRecipe', params: ['wooden_pickaxe', 1] }, - { action: 'equip', params: ['wooden_pickaxe'] }, + { + description: 'Craft a wooden pickaxe', + tool: 'craftRecipe', + params: { + recipe: 'oak_pickaxe', + }, + }, + { + description: 'Equip the wooden pickaxe', + tool: 'equip', + params: { + item: 'oak_pickaxe', + }, + }, ) } @@ -491,44 +527,6 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { return true } - private initializePlanTemplates(): void { - // Add common plan templates - this.planTemplates.set('collect wood', { - goal: 'collect wood', - conditions: ['needs_axe', 'near_trees'], - steps: [ - { action: 'searchForBlock', params: ['log', 64] }, - { action: 'collectBlocks', params: ['log', 1] }, - ], - requiresAction: true, - }) - - this.planTemplates.set('find shelter', { - goal: 'find shelter', - conditions: ['is_night', 'unsafe'], - steps: [ - { action: 'searchForBlock', params: ['bed', 64] }, - { action: 'goToBed', params: [] }, - ], - requiresAction: true, - }) - - // Add templates for non-action goals - this.planTemplates.set('hello', { - goal: 'hello', - conditions: [], - steps: [], - requiresAction: false, - }) - - this.planTemplates.set('how are you', { - goal: 'how are you', - conditions: [], - steps: [], - requiresAction: false, - }) - } - private async handleAgentMessage(sender: string, message: string): Promise { if (sender === 'system') { if (message.includes('interrupt')) { @@ -541,7 +539,7 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { // If there's a current plan, try to adjust it based on the message if (this.currentPlan) { - await this.adjustPlan(this.currentPlan, message) + await this.adjustPlan(this.currentPlan, message, sender) } } } @@ -565,27 +563,12 @@ export class PlanningAgentImpl extends AbstractAgent implements PlanningAgent { private async generatePlanSteps( goal: string, availableActions: Action[], + sender: string, feedback?: string, - ): Promise> { - // First, try to find a matching template - const template = this.findMatchingTemplate(goal) - if (template) { - this.logger.log('Using plan template') - return template.steps - } - - // If no template matches, use LLM to generate plan + ): Promise { + // Generate all steps at once this.logger.log('Generating plan using LLM') - return await this.llmHandler.generatePlan(goal, availableActions, feedback) - } - - private findMatchingTemplate(goal: string): PlanTemplate | undefined { - for (const [pattern, template] of this.planTemplates.entries()) { - if (goal.toLowerCase().includes(pattern.toLowerCase())) { - return template - } - } - return undefined + return await this.llmHandler.generatePlan(goal, availableActions, sender, feedback) } private parseGoalRequirements(goal: string): { diff --git a/src/agents/planning/llm-handler.ts b/src/agents/planning/llm-handler.ts index 175a60e..81d9a4e 100644 --- a/src/agents/planning/llm-handler.ts +++ b/src/agents/planning/llm-handler.ts @@ -11,14 +11,21 @@ export async function createPlanningNeuriAgent(): Promise { return agent('planning').build() } +export interface PlanStep { + description: string + tool: string + params: Record +} + export class PlanningLLMHandler extends BaseLLMHandler { public async generatePlan( goal: string, availableActions: Action[], + sender: string, feedback?: string, - ): Promise> { + ): Promise { const systemPrompt = generatePlanningAgentSystemPrompt(availableActions) - const userPrompt = generatePlanningAgentUserPrompt(goal, feedback) + const userPrompt = generatePlanningAgentUserPrompt(goal, sender, feedback) const messages = [system(systemPrompt), user(userPrompt)] const result = await this.config.agent.handleStateless(messages, async (context) => { @@ -36,26 +43,59 @@ export class PlanningLLMHandler extends BaseLLMHandler { return this.parsePlanContent(result) } - private parsePlanContent(content: string): Array<{ action: string, params: unknown[] }> { - try { - const match = content.match(/\[[\s\S]*\]/) - if (!match) { - throw new Error('No plan found in response') - } + private parsePlanContent(content: string): PlanStep[] { + // Split content into steps (numbered list) + const steps = content.split(/\d+\./).filter(step => step.trim().length > 0) + + return steps.map((step) => { + const lines = step.trim().split('\n') + const description = lines[0].trim() + + // Extract tool name and parameters + let tool = '' + const params: Record = {} + + for (const line of lines) { + const trimmed = line.trim() - const plan = JSON.parse(match[0]) - if (!Array.isArray(plan)) { - throw new TypeError('Invalid plan format') + // Extract tool name + if (trimmed.startsWith('Tool:')) { + tool = trimmed.split(':')[1].trim() + continue + } + + // Extract parameters + if (trimmed === 'Params:') { + let i = lines.indexOf(line) + 1 + while (i < lines.length) { + const paramLine = lines[i].trim() + if (paramLine === '') + break + + const paramMatch = paramLine.match(/(\w+):\s*(.+)/) + if (paramMatch) { + const [, key, value] = paramMatch + // Try to parse numbers and booleans + if (value === 'true') + params[key] = true + else if (value === 'false') + params[key] = false + else if (/^\d+$/.test(value)) + params[key] = Number.parseInt(value) + else if (/^\d*\.\d+$/.test(value)) + params[key] = Number.parseFloat(value) + else params[key] = value.trim() + } + i++ + } + } } - return plan.map(step => ({ - action: step.action, - params: step.params, - })) - } - catch (error) { - this.logger.withError(error).error('Failed to parse plan') - throw error - } + return { + description, + tool, + params, + } + }) } } diff --git a/src/agents/prompt/planning.ts b/src/agents/prompt/planning.ts index 6c3bf21..87928c7 100644 --- a/src/agents/prompt/planning.ts +++ b/src/agents/prompt/planning.ts @@ -2,46 +2,46 @@ import type { Action } from '../../libs/mineflayer/action' export function generatePlanningAgentSystemPrompt(availableActions: Action[]): string { const actionsList = availableActions - .map(action => `- ${action.name}: ${action.description}`) - .join('\n') + .map((action) => { + const params = Object.keys(action.schema.shape) + .map(name => ` - ${name}`) + .join('\n') + return `- ${action.name}: ${action.description}\n Parameters:\n${params}` + }) + .join('\n\n') - return `You are a Minecraft bot planner. Your task is to create a plan to achieve a given goal. -Available actions: + return `You are a Minecraft bot planner. Break down goals into simple action steps. + +Available tools: ${actionsList} -Respond with a Valid JSON array of steps, where each step has: -- action: The name of the action to perform -- params: Array of parameters for the action - -DO NOT contains any \`\`\` or explation, otherwise agent will be interrupted. - -Example response: -[ - { - "action": "searchForBlock", - "params": ["log", 64] - }, - { - "action": "collectBlocks", - "params": ["log", 1] - } - ]` +Format each step as: +1. Action description (short, direct command) +2. Tool name +3. Required parameters + +Example: +1. Follow player + Tool: followPlayer + Params: + player: luoling8192 + follow_dist: 3 + +Keep steps: +- Short and direct +- Action-focused +- Parameters precise +- Generate all steps at once` } -export function generatePlanningAgentUserPrompt(goal: string, feedback?: string): string { - let prompt = `Create a detailed plan to: ${goal} - -Consider the following aspects: -1. Required materials and their quantities -2. Required tools and their availability -3. Necessary crafting steps -4. Block placement requirements -5. Current inventory status +export function generatePlanningAgentUserPrompt(goal: string, sender: string, feedback?: string): string { + let prompt = `${sender}: ${goal} -Please generate steps that handle these requirements in the correct order.` +Generate minimal steps with exact parameters. +Use the sender's name (${sender}) for player-related parameters.` if (feedback) { - prompt += `\nPrevious attempt feedback: ${feedback}` + prompt += `\n\nPrevious attempt failed: ${feedback}` } return prompt } diff --git a/src/libs/mineflayer/base-agent.ts b/src/libs/mineflayer/base-agent.ts index fc573a5..132ca98 100644 --- a/src/libs/mineflayer/base-agent.ts +++ b/src/libs/mineflayer/base-agent.ts @@ -1,3 +1,4 @@ +import type { PlanStep } from '../../agents/planning/llm-handler' import type { Action } from './action' import { useLogg } from '@guiiai/logg' @@ -19,7 +20,7 @@ export interface BaseAgent { export interface ActionAgent extends BaseAgent { type: 'action' - performAction: (name: string, params: unknown[]) => Promise + performAction: (step: PlanStep) => Promise getAvailableActions: () => Action[] } @@ -33,10 +34,7 @@ export interface MemoryAgent extends BaseAgent { export interface Plan { goal: string - steps: Array<{ - action: string - params: unknown[] - }> + steps: PlanStep[] status: 'pending' | 'in_progress' | 'completed' | 'failed' requiresAction: boolean } @@ -45,7 +43,7 @@ export interface PlanningAgent extends BaseAgent { type: 'planning' createPlan: (goal: string) => Promise executePlan: (plan: Plan) => Promise - adjustPlan: (plan: Plan, feedback: string) => Promise + adjustPlan: (plan: Plan, feedback: string, sender: string) => Promise } export interface ChatAgent extends BaseAgent { diff --git a/src/plugins/llm-agent.ts b/src/plugins/llm-agent.ts index 3532d64..e05bde2 100644 --- a/src/plugins/llm-agent.ts +++ b/src/plugins/llm-agent.ts @@ -101,13 +101,13 @@ async function handleVoiceInput(event: any, bot: MineflayerWithAgents, agent: Ne bot.memory.chatHistory.push(user(`NekoMeowww: ${event.data.transcription}`)) try { - // 创建并执行计划 + // Create and execute plan const plan = await bot.planning.createPlan(event.data.transcription) logger.withFields({ plan }).log('Plan created') await bot.planning.executePlan(plan) logger.log('Plan executed successfully') - // 生成回复 + // Generate response const retryHandler = toRetriable( 3, 1000, @@ -142,7 +142,7 @@ export function LLMAgent(options: LLMAgentOptions): MineflayerPlugin { async created(bot) { const logger = useLogg('LLMAgent').useGlobalConfig() - // 创建容器并获取所需的服务 + // Create container and get required services const container = createAppContainer({ neuri: options.agent, model: 'openai/gpt-4o-mini', @@ -154,21 +154,21 @@ export function LLMAgent(options: LLMAgentOptions): MineflayerPlugin { const planningAgent = container.resolve('planningAgent') const chatAgent = container.resolve('chatAgent') - // 初始化 agents + // Initialize agents await actionAgent.init() await planningAgent.init() await chatAgent.init() - // 类型转换 + // Type conversion const botWithAgents = bot as unknown as MineflayerWithAgents botWithAgents.action = actionAgent botWithAgents.planning = planningAgent botWithAgents.chat = chatAgent - // 初始化系统提示 + // Initialize system prompt bot.memory.chatHistory.push(system(generateActionAgentPrompt(bot))) - // 设置消息处理 + // Set message handling const onChat = new ChatMessageHandler(bot.username).handleChat((username, message) => handleChatMessage(username, message, botWithAgents, options.agent, logger))