Skip to content

Commit

Permalink
feat: planning, action, chat, memory agent implementation (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
luoling8192 committed Jan 21, 2025
1 parent 51ecf65 commit 314344a
Show file tree
Hide file tree
Showing 59 changed files with 2,858 additions and 1,037 deletions.
1 change: 1 addition & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ words:
- aichat
- airi
- antfu
- awilix
- bumpp
- collectblock
- convo
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@guiiai/logg": "^1.0.7",
"@proj-airi/server-sdk": "^0.1.4",
"@typeschema/zod": "^0.14.0",
"awilix": "^12.0.4",
"dotenv": "^16.4.7",
"es-toolkit": "^1.31.0",
"eventemitter3": "^5.0.1",
Expand Down Expand Up @@ -51,7 +52,7 @@
"vitest": "^3.0.2"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged && pnpm typecheck"
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
Expand Down
43 changes: 43 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

180 changes: 180 additions & 0 deletions src/agents/action/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import type { Mineflayer } from '../../libs/mineflayer'
import type { Action } from '../../libs/mineflayer/action'
import type { ActionAgent, AgentConfig } from '../../libs/mineflayer/base-agent'

import { useBot } from '../../composables/bot'
import { AbstractAgent } from '../../libs/mineflayer/base-agent'
import { ActionManager } from '../../manager/action'
import { actionsList } from './tools'

interface ActionState {
executing: boolean
label: string
startTime: number
}

/**
* ActionAgentImpl implements the ActionAgent interface to handle action execution
* Manages action lifecycle, state tracking and error handling
*/
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: '',
startTime: 0,
}
}

protected async initializeAgent(): Promise<void> {
this.logger.log('Initializing action agent')
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> {
if (!this.initialized) {
throw new Error('Action agent not initialized')
}

const action = this.actions.get(name)
if (!action) {
throw new Error(`Action not found: ${name}`)
}

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)
}
}

public async resumeAction(name: string, params: unknown[]): Promise<string> {
const action = this.actions.get(name)
if (!action) {
throw new Error(`Action not found: ${name}`)
}

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')
}

return this.formatActionOutput({
message: result.message,
timedout: result.timedout,
interrupted: false,
})
}
catch (error) {
this.logger.withFields({ name, params, error }).error('Failed to resume action')
throw error
}
finally {
this.updateActionState(false)
}
}

public getAvailableActions(): Action[] {
return Array.from(this.actions.values())
}

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')
}
}

private updateActionState(executing: boolean, label = ''): void {
this.currentActionState = {
executing,
label,
startTime: executing ? Date.now() : 0,
}
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}`
}
if (result.interrupted) {
return 'Action was interrupted'
}
return result.message ?? ''
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { messages, system, user } from 'neuri/openai'
import { beforeAll, describe, expect, it } from 'vitest'

import { initBot, useBot } from '../composables/bot'
import { botConfig, initEnv } from '../composables/config'
import { genSystemBasicPrompt } from '../prompts/agent'
import { initLogger } from '../utils/logger'
import { initAgent } from './openai'
import { initBot, useBot } from '../../composables/bot'
import { botConfig, initEnv } from '../../composables/config'
import { createNeuriAgent } from '../../composables/neuri'
import { initLogger } from '../../utils/logger'
import { generateSystemBasicPrompt } from '../prompt/llm-agent.plugin'

describe('openAI agent', { timeout: 0 }, () => {
beforeAll(() => {
Expand All @@ -16,13 +16,13 @@ describe('openAI agent', { timeout: 0 }, () => {

it('should initialize the agent', async () => {
const { bot } = useBot()
const agent = await initAgent(bot)
const agent = await createNeuriAgent(bot)

await new Promise<void>((resolve) => {
bot.bot.once('spawn', async () => {
const text = await agent.handle(
messages(
system(genSystemBasicPrompt('airi')),
system(generateSystemBasicPrompt('airi')),
user('Hello, who are you?'),
),
async (c) => {
Expand Down
Loading

0 comments on commit 314344a

Please sign in to comment.