Skip to content

Commit

Permalink
refactor: planning agent v2 (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
luoling8192 authored Jan 21, 2025
1 parent 314344a commit e561678
Show file tree
Hide file tree
Showing 7 changed files with 430 additions and 436 deletions.
108 changes: 23 additions & 85 deletions src/agents/action/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -20,15 +20,13 @@ interface ActionState {
export class ActionAgentImpl extends AbstractAgent implements ActionAgent {
public readonly type = 'action' as const
private actions: Map<string, Action>
private actionManager: ActionManager
private mineflayer: Mineflayer
private currentActionState: ActionState

constructor(config: AgentConfig) {
super(config)
this.actions = new Map()
this.mineflayer = useBot().bot
this.actionManager = new ActionManager(this.mineflayer)
this.currentActionState = {
executing: false,
label: '',
Expand All @@ -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<void> {
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<string> {
public async performAction(step: PlanStep): Promise<string> {
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<string> {
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 {
Expand All @@ -149,32 +91,28 @@ export class ActionAgentImpl extends AbstractAgent implements ActionAgent {
}

private async handleAgentMessage(sender: string, message: string): Promise<void> {
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
}
}

private updateActionState(executing: boolean, label = ''): void {
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'
}
}
35 changes: 35 additions & 0 deletions src/agents/action/llm-handler.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -31,6 +33,39 @@ export async function createActionNeuriAgent(mineflayer: Mineflayer): Promise<Ag
}

export class ActionLLMHandler extends BaseLLMHandler {
public async executeStep(step: PlanStep): Promise<string> {
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<string> {
const result = await this.config.agent.handleStateless(messages, async (context) => {
this.logger.log('Processing action...')
Expand Down
Loading

0 comments on commit e561678

Please sign in to comment.