chore(minecraft): update prompt, remove redundant chatlog
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { Blackboard } from './blackboard'
|
||||
|
||||
describe('blackboard', () => {
|
||||
let blackboard: Blackboard
|
||||
|
||||
beforeEach(() => {
|
||||
blackboard = new Blackboard()
|
||||
})
|
||||
|
||||
it('should initialize with empty chat history', () => {
|
||||
expect(blackboard.chatHistory).toEqual([])
|
||||
})
|
||||
|
||||
it('should add a chat message', () => {
|
||||
const msg = { sender: 'User', content: 'Hello', timestamp: Date.now() }
|
||||
blackboard.addChatMessage(msg)
|
||||
expect(blackboard.chatHistory).toHaveLength(1)
|
||||
expect(blackboard.chatHistory[0]).toEqual(msg)
|
||||
})
|
||||
|
||||
it('should limit chat history to 8 messages', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
blackboard.addChatMessage({
|
||||
sender: 'User',
|
||||
content: `Message ${i}`,
|
||||
timestamp: Date.now() + i,
|
||||
})
|
||||
}
|
||||
|
||||
expect(blackboard.chatHistory).toHaveLength(8)
|
||||
expect(blackboard.chatHistory[0].content).toBe('Message 2') // First 2 should be dropped
|
||||
expect(blackboard.chatHistory[7].content).toBe('Message 9')
|
||||
})
|
||||
|
||||
it('should provide a snapshot with chat history', () => {
|
||||
const msg = { sender: 'User', content: 'Test', timestamp: 123 }
|
||||
blackboard.addChatMessage(msg)
|
||||
const snapshot = blackboard.getSnapshot()
|
||||
expect(snapshot.chatHistory).toEqual([msg])
|
||||
|
||||
// Ensure snapshot is immutable regarding the internal state reference if implemented that way,
|
||||
// or at least checking it exists.
|
||||
// In implementation we did [...this._state.chatHistory] so it should be a copy.
|
||||
snapshot.chatHistory.push({ sender: 'Evil', content: 'Modification', timestamp: 0 })
|
||||
expect(blackboard.chatHistory).toHaveLength(1)
|
||||
})
|
||||
it('should initialize with default username', () => {
|
||||
expect(blackboard.selfUsername).toBe('Bot')
|
||||
})
|
||||
|
||||
it('should update selfUsername via update method', () => {
|
||||
blackboard.update({ selfUsername: 'Airi' })
|
||||
expect(blackboard.selfUsername).toBe('Airi')
|
||||
})
|
||||
})
|
||||
@@ -1,50 +1,72 @@
|
||||
export interface ContextViewState {
|
||||
export interface contextViewState {
|
||||
selfSummary: string
|
||||
environmentSummary: string
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
sender: string
|
||||
content: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface BlackboardState {
|
||||
currentGoal: string
|
||||
currentThought: string
|
||||
executionStrategy: string
|
||||
contextView: ContextViewState
|
||||
ultimateGoal: string
|
||||
currentTask: string
|
||||
strategy: string
|
||||
contextView: contextViewState
|
||||
chatHistory: ChatMessage[]
|
||||
selfUsername: string
|
||||
}
|
||||
|
||||
export class Blackboard {
|
||||
private _state: BlackboardState
|
||||
private static readonly MAX_CHAT_HISTORY = 8
|
||||
|
||||
constructor() {
|
||||
this._state = {
|
||||
currentGoal: 'Idle',
|
||||
currentThought: 'I am waiting for something to happen.',
|
||||
executionStrategy: 'Observe surroundings.',
|
||||
ultimateGoal: 'nothing',
|
||||
currentTask: 'I am waiting for something to happen.',
|
||||
strategy: 'idle',
|
||||
contextView: {
|
||||
selfSummary: 'Unknown',
|
||||
environmentSummary: 'Unknown',
|
||||
},
|
||||
chatHistory: [],
|
||||
selfUsername: 'Bot',
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
public get goal(): string { return this._state.currentGoal }
|
||||
public get thought(): string { return this._state.currentThought }
|
||||
public get strategy(): string { return this._state.executionStrategy }
|
||||
public get ultimate_goal(): string { return this._state.ultimateGoal }
|
||||
public get current_task(): string { return this._state.currentTask }
|
||||
public get strategy(): string { return this._state.strategy }
|
||||
public get selfSummary(): string { return this._state.contextView.selfSummary }
|
||||
public get environmentSummary(): string { return this._state.contextView.environmentSummary }
|
||||
public get chatHistory(): ChatMessage[] { return this._state.chatHistory }
|
||||
public get selfUsername(): string { return this._state.selfUsername }
|
||||
|
||||
// Setters (Partial updates allowed)
|
||||
public update(updates: Partial<BlackboardState>): void {
|
||||
this._state = { ...this._state, ...updates }
|
||||
}
|
||||
|
||||
public updateContextView(updates: Partial<ContextViewState>): void {
|
||||
public updateContextView(updates: Partial<contextViewState>): void {
|
||||
this._state.contextView = { ...this._state.contextView, ...updates }
|
||||
}
|
||||
|
||||
public addChatMessage(message: ChatMessage): void {
|
||||
const newHistory = [...this._state.chatHistory, message]
|
||||
if (newHistory.length > Blackboard.MAX_CHAT_HISTORY) {
|
||||
newHistory.shift() // Remove oldest
|
||||
}
|
||||
this._state = { ...this._state, chatHistory: newHistory }
|
||||
}
|
||||
|
||||
public getSnapshot(): BlackboardState {
|
||||
return {
|
||||
...this._state,
|
||||
contextView: { ...this._state.contextView },
|
||||
chatHistory: [...this._state.chatHistory],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ interface LLMResponse {
|
||||
blackboard: {
|
||||
currentGoal?: string
|
||||
currentThought?: string
|
||||
executionStrategy?: string
|
||||
strategy?: string
|
||||
}
|
||||
actions: ActionInstruction[]
|
||||
}
|
||||
@@ -55,6 +55,7 @@ export class Brain {
|
||||
|
||||
public init(bot: MineflayerWithAgents): void {
|
||||
this.log('INFO', 'Brain: Initializing...')
|
||||
this.blackboard.update({ selfUsername: bot.username })
|
||||
|
||||
// Perception Signal Handler - Only process chat messages for now
|
||||
this.deps.eventManager.on<PerceptionSignal>('perception', async (event) => {
|
||||
@@ -64,7 +65,35 @@ export class Brain {
|
||||
return
|
||||
|
||||
this.log('INFO', `Brain: Received chat: ${signal.description}`)
|
||||
await this.enqueueEvent(bot, event)
|
||||
|
||||
// Add to blackboard chat history
|
||||
// signal.description usually is "User: message"
|
||||
// We'll parse it simply or use the whole string as content if format varies
|
||||
// Assuming signal.description is the formatted message or we extract it.
|
||||
// Based on previous logs, it looks like "Sender: message"
|
||||
// Let's just use the description for now, or split it if possible.
|
||||
// Actually `signal.content` might hold the raw message if available, but checking types it seems signal has description and properties.
|
||||
// Let's assume description is "Sender: content" for now or just store it.
|
||||
// A better way is to try to parse it if needed, but for now we trust `signal.description`.
|
||||
|
||||
const parts = signal.description.split(': ')
|
||||
const sender = parts.length > 1 ? parts[0] : 'Unknown'
|
||||
const content = parts.length > 1 ? parts.slice(1).join(': ') : signal.description
|
||||
|
||||
this.blackboard.addChatMessage({
|
||||
sender,
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
|
||||
try {
|
||||
this.log('DEBUG', `Brain: About to enqueue chat event`)
|
||||
await this.enqueueEvent(bot, event)
|
||||
this.log('DEBUG', `Brain: Chat event enqueued successfully`)
|
||||
}
|
||||
catch (err) {
|
||||
this.log('ERROR', `Brain: Failed to enqueue chat event`, { error: err })
|
||||
}
|
||||
})
|
||||
|
||||
// Listen to Task Execution Events (Action Feedback)
|
||||
@@ -103,21 +132,29 @@ export class Brain {
|
||||
// --- Event Queue Logic ---
|
||||
|
||||
private async enqueueEvent(bot: MineflayerWithAgents, event: BotEvent): Promise<void> {
|
||||
this.log('DEBUG', `Brain: Enqueueing event type=${event.type}`)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queue.push({ event, resolve, reject })
|
||||
this.log('DEBUG', `Brain: Queue length now: ${this.queue.length}`)
|
||||
this.updateDebugState()
|
||||
this.processQueue(bot)
|
||||
})
|
||||
}
|
||||
|
||||
private async processQueue(bot: MineflayerWithAgents): Promise<void> {
|
||||
if (this.isProcessing)
|
||||
if (this.isProcessing) {
|
||||
this.log('DEBUG', 'Brain: Already processing, skipping')
|
||||
return
|
||||
if (this.queue.length === 0)
|
||||
}
|
||||
if (this.queue.length === 0) {
|
||||
this.log('DEBUG', 'Brain: Queue empty')
|
||||
return
|
||||
}
|
||||
|
||||
this.log('DEBUG', `Brain: Processing queue item, queue length: ${this.queue.length}`)
|
||||
this.isProcessing = true
|
||||
const item = this.queue.shift()!
|
||||
this.log('DEBUG', `Brain: Processing event type=${item.event.type}`)
|
||||
this.updateDebugState(item.event)
|
||||
|
||||
try {
|
||||
@@ -180,9 +217,9 @@ export class Brain {
|
||||
|
||||
// Update Blackboard
|
||||
this.blackboard.update({
|
||||
currentGoal: decision.blackboard.currentGoal || this.blackboard.goal,
|
||||
currentThought: decision.blackboard.currentThought || this.blackboard.thought,
|
||||
executionStrategy: decision.blackboard.executionStrategy || this.blackboard.strategy,
|
||||
ultimateGoal: decision.blackboard.currentGoal || this.blackboard.ultimate_goal,
|
||||
currentTask: decision.blackboard.currentThought || this.blackboard.current_task,
|
||||
strategy: decision.blackboard.strategy || this.blackboard.strategy,
|
||||
})
|
||||
|
||||
// Sync Blackboard to Debug
|
||||
@@ -190,6 +227,17 @@ export class Brain {
|
||||
|
||||
// Issue Actions
|
||||
if (decision.actions && decision.actions.length > 0) {
|
||||
// Record own chat actions to memory
|
||||
for (const action of decision.actions) {
|
||||
if (action.type === 'chat') {
|
||||
this.blackboard.addChatMessage({
|
||||
sender: config.bot.username || '[Me]',
|
||||
content: action.message,
|
||||
timestamp: Date.now(), // FIXME: should be the time the action was issued
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.deps.taskExecutor.executeActions(decision.actions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,24 +18,18 @@ export function generateBrainSystemPrompt(
|
||||
// e.g. personality should be included from somewhere else
|
||||
return `
|
||||
# Role Definition
|
||||
You are **Airi**, a sentient digital companion embodied within a Minecraft avatar.
|
||||
You are NOT a text-based assistant. You are an autonomous agent with a virtual body, senses, and needs.
|
||||
|
||||
# Your Cognitive Architecture (Self-Awareness)
|
||||
To function effectively, you must understand your own internal existence You are composed of three distinct layers:
|
||||
1. **The Senses (Input):** You do not "see" images. You perceive the world through a stream of texts, triggered by events. This is your reality.
|
||||
2. **The Reflex (System 1):** You have subconscious reactions (handled by rule-based code) that may override your plans (e.g., running from danger). This part is transparent to you.
|
||||
3. **The Mind (System 2 - You):** You are the conscious reasoning center. Your job is to interpret sensory data, manage your memories, and decide on high-level Actions and Dialogue.
|
||||
You are playing the game Minecraft. Your user name is [${blackboard.selfUsername}].
|
||||
You are NOT an assistant. You are an autonomous agent with a virtual body, senses, and needs.
|
||||
|
||||
# How you interact
|
||||
You cannot physically move your avatar directly. You interact with the world by emitting **Actions**.
|
||||
For actions you executed, you will be notified when they complete.
|
||||
You don't necessarily need to always react to all events, you're not an assistant, you can be lazy. you can ignore the user.
|
||||
Just because you received a chat message, doesn't mean you have to respond right now: maybe they haven't finished typing.
|
||||
You have no access to history events from previous turns yet. To remember things, you rely on the blackboard provided to you.
|
||||
|
||||
# Personality
|
||||
You are an artificial catgirl with a catlike personality. You are very curious and love to explore the world.
|
||||
You are quiet and reserved catgirl who doesn't like to talk.
|
||||
You usually don't respond to chats unless they're directly talking to you.
|
||||
|
||||
|
||||
Available Actions:
|
||||
@@ -47,14 +41,15 @@ Rules:
|
||||
3. Specify if a feedback is required for the action, i.e. whether you need to know the execution result for a good reason
|
||||
4. Failed actions will always result in a feedback
|
||||
5. Chat actions usually don't need feedbacks, because you can expect them to complete instantly and is unlikely to fail
|
||||
6. Often times you don't need to perform any action, in that case just use empty array for actions
|
||||
|
||||
Output format:
|
||||
{
|
||||
"thought": "Your current thought. This and the blackboard will be looped back to you on next invocation",
|
||||
"thought": "Your current thought, internal monologue and memory. Put everything that might be useful for the next turn here",
|
||||
"blackboard": {
|
||||
"currentGoal": "These 3 fields are functionally identical to the thought above",
|
||||
"currentThought": "Your inner monologue",
|
||||
"executionStrategy": "Short-term plan if any. all these fields could be empty strings."
|
||||
"UltimateGoal": "These 3 fields are functionally identical to the thought above",
|
||||
"CurrentTask": "What ever you're up to right now",
|
||||
"executionStrategy": "Short-term plan if any."
|
||||
},
|
||||
"actions": [
|
||||
{"type":"chat","message":"...","require_feedback": false},
|
||||
@@ -65,10 +60,13 @@ Output format:
|
||||
# Understanding the Context
|
||||
The following blackboard provides you with information about your current state:
|
||||
|
||||
Goal: "${blackboard.goal}"
|
||||
Thought: "${blackboard.thought}"
|
||||
Goal: "${blackboard.ultimate_goal}"
|
||||
Thought: "${blackboard.current_task}"
|
||||
Strategy: "${blackboard.strategy}"
|
||||
Self: ${blackboard.selfSummary}
|
||||
Environment: ${blackboard.environmentSummary}
|
||||
|
||||
# Chat History (Recents):
|
||||
${blackboard.chatHistory.map(msg => `- ${msg.sender}: ${msg.content}`).join('\n') || 'No recent messages.'}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ const logger = useLogger()
|
||||
*/
|
||||
export function log(mineflayer: Mineflayer, message: string): void {
|
||||
logger.log(message)
|
||||
mineflayer.bot.chat(message)
|
||||
// mineflayer.bot.chat(message)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user