diff --git a/packages/client-direct/src/index.ts b/packages/client-direct/src/index.ts index 0a0844a949..4c70659bfb 100644 --- a/packages/client-direct/src/index.ts +++ b/packages/client-direct/src/index.ts @@ -290,7 +290,7 @@ export class DirectClient { } ); - await runtime.evaluate(memory, state); + await runtime.evaluate(memory, state, true); // Check if we should suppress the initial message const action = runtime.actions.find( diff --git a/packages/core/src/defaultCharacter.ts b/packages/core/src/defaultCharacter.ts index e4a81b07e2..ecfef146f3 100644 --- a/packages/core/src/defaultCharacter.ts +++ b/packages/core/src/defaultCharacter.ts @@ -1,4 +1,4 @@ -import { Character, ModelProviderName } from "./types.ts"; +import { Character, Clients, ModelProviderName } from "./types.ts"; export const defaultCharacter: Character = { name: "Eliza", @@ -6,6 +6,12 @@ export const defaultCharacter: Character = { plugins: [], clients: [], modelProvider: ModelProviderName.LLAMALOCAL, + operators: [ + { + client: Clients.DIRECT, + name: "user_uuid", + } + ], settings: { secrets: {}, voice: { diff --git a/packages/core/src/environment.ts b/packages/core/src/environment.ts index 0758d0d31d..174cc75d3f 100644 --- a/packages/core/src/environment.ts +++ b/packages/core/src/environment.ts @@ -62,6 +62,12 @@ const PluginSchema = z.object({ clients: z.array(z.any()).optional(), }); +// Helper schemas for operator +const OperatorSchema = z.object({ + name: z.string(), + client: z.string(), +}); + // Main Character schema export const CharacterSchema = z.object({ id: z.string().uuid().optional(), @@ -129,6 +135,7 @@ export const CharacterSchema = z.object({ prompt: z.string().optional(), }) .optional(), + operators: z.array(OperatorSchema).optional(), }); // Type inference diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 107ce8ea0b..474672ab36 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -79,8 +79,10 @@ Your response must include the JSON block.`; * @param text - The input text from which to extract and parse the JSON array. * @returns An array parsed from the JSON string if successful; otherwise, null. */ + export function parseJsonArrayFromText(text: string) { let jsonData = null; + let normalizedJson = null; // First try to parse with the original JSON format const jsonBlockMatch = text.match(jsonBlockPattern); @@ -88,10 +90,11 @@ export function parseJsonArrayFromText(text: string) { if (jsonBlockMatch) { try { // Replace single quotes with double quotes before parsing - const normalizedJson = jsonBlockMatch[1].replace(/'/g, '"'); + normalizedJson = jsonBlockMatch[1].replace(/'/g, '"'); jsonData = JSON.parse(normalizedJson); } catch (e) { console.error("Error parsing JSON:", e); + console.error("JSON input was:", normalizedJson); } } @@ -103,10 +106,11 @@ export function parseJsonArrayFromText(text: string) { if (arrayMatch) { try { // Replace single quotes with double quotes before parsing - const normalizedJson = arrayMatch[0].replace(/'/g, '"'); + normalizedJson = arrayMatch[0].replace(/'/g, '"'); jsonData = JSON.parse(normalizedJson); } catch (e) { - console.error("Error parsing JSON:", e); + console.error("Error parsing JSON as array:", e); + console.error("JSON input was:", normalizedJson); } } } @@ -115,7 +119,7 @@ export function parseJsonArrayFromText(text: string) { return jsonData; } - return null; + return jsonData; } /** diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index dfc19c2eb2..da7890e7f0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -636,6 +636,11 @@ export interface ModelConfiguration { maxInputTokens?: number; } +export type Operator = { + client: Clients; + name: string; +}; + /** * Configuration for an agent character */ @@ -689,6 +694,9 @@ export type Character = { slackShouldRespondTemplate?: string; }; + /** List of operators the character can interact with */ + operators?: Operator[]; + /** Character biography */ bio: string | string[]; diff --git a/packages/plugin-bootstrap/src/evaluators/processInstructions.ts b/packages/plugin-bootstrap/src/evaluators/processInstructions.ts new file mode 100644 index 0000000000..291fba1f53 --- /dev/null +++ b/packages/plugin-bootstrap/src/evaluators/processInstructions.ts @@ -0,0 +1,280 @@ +import { composeContext, createGoal, elizaLogger, State } from "@elizaos/core"; +import { generateObjectArray } from "@elizaos/core"; +import { + ActionExample, + Content, + IAgentRuntime, + Memory, + ModelClass, + Evaluator, +} from "@elizaos/core"; + +export const formatFacts = (facts: Memory[]) => { + const messageStrings = facts + .reverse() + .map((fact: Memory) => `${(fact.content as Content)?.content}`); + const finalMessageStrings = messageStrings.join("\n"); + return finalMessageStrings; +}; + + +// goals and knowledge + +// make sure there's recently conversations with the operator from other rooms + +// inject the old goals and knowledge + +// make sure we only store new stuff that was not already known + + +const processInstructionsTemplate = `TASK: Extract Instructions from operator messages and format them as an array of instructions in JSON format. + +# START OF EXAMPLES +These are examples of the expected output format: +{{evaluationExamples}} +# END OF EXAMPLES + +# INSTRUCTIONS +Extract any knowledge or goals from the operator's messages and format them according to these rules: +- Use only straight double quotes (") for all JSON keys and string values +- Do not use smart/curly quotes (", ") +- Do not use apostrophes or possessives (use "Trumps presidency" instead of "Trump's presidency") +- Do not use internal quotation marks in text +- Keep all text values on a single line +- Use plain text without special characters +- For possessives, remove the apostrophe (Example: "Kamalas events" not "Kamala's events") + +Recent Messages: +{{recentMessages}} + +Response must be a JSON object inside a JSON markdown block with this exact structure: +\`\`\`json +[{ + "goals": [ + { + "id": , + "roomId": "default-room-{{agentId}}", + "userId": "{{agentId}}", + "name": "string", + "status": "IN_PROGRESS" | "DONE" | "FAILED", + "objectives": { + "description": ["string"], + "completed": true | false + } + } + ], + "knowledge": ["string"] +}] +\`\`\` + +Remove all single quotes in the response. +Remove all double quotes in the response, except those wrapping keys and values. +`; + +export const useProcessInstructionsTemplate = (runtime: IAgentRuntime) => { + return runtime.character.templates?.processInstructionsTemplate || processInstructionsTemplate; +}; + +async function handler(runtime: IAgentRuntime, message: Memory, state?: State) { + elizaLogger.log("Start handling instructions") + state = await runtime.updateRecentMessageState(state); + + const context = composeContext({ + state, + template: runtime.character.templates?.processInstructionsTemplate || processInstructionsTemplate, + }); + + const instructions = await generateObjectArray({ + runtime, + context, + modelClass: ModelClass.LARGE, + }); + + const goals = instructions.flatMap(instruction => instruction.goals) + const knowledge = instructions.flatMap(instruction => instruction.knowledge) + + elizaLogger.log(`Instruction generated ${goals.length} goals and ${knowledge.length} knowledge`) + + for (const goal of goals) { + try { + elizaLogger.log("About to create goals under instruction", goal); + await createGoal({ runtime, goal }); + } catch (error) { + elizaLogger.log("About to create goals but failed. Skip.", goal.name, error); + } + } + + for (const item of knowledge) { + try { + elizaLogger.log("About to create memory under instruction", item); + await runtime.knowledgeManager.createMemory({ + content: item, + userId: message.userId, + roomId: message.roomId, + agentId: runtime.character.id, + }); + } catch (error) { + elizaLogger.log("About to create memory but failed. Skip.", item); + } + } +} + +export const processInstructionsEvaluator: Evaluator = { + name: "PROCESS_INSTRUCTIONS", + similes: [ + "GET_INSTRUCTIONS", + "PROCESS_OPERATOR_INSTRUCTIONS", + "GET_OPERATOR_INSTRUCTIONS", + ], + alwaysRun: true, + validate: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const character = runtime.character; + + if (!character.operators || character.operators.length === 0) { + return false; + } + + elizaLogger.log(`Validating ${character.operators.length} operators: ${character.operators.map(op => op.name).join(', ')}`) + + // get the name of the operator from the message + const userId = message.userId; + + const account = await runtime.databaseAdapter.getAccountById(userId); + if (!account) { + return false; + } + + elizaLogger.log(`Current account is ${account.username}. Message source is ${message.content.source}. Validate: ${character.operators.some( + (operator) => operator.client.toLowerCase() === message?.content?.source?.toLowerCase() && operator.name.toLowerCase() === account?.username.toLowerCase() + )}`) + + // Make sure this is correct + return character.operators.some( + (operator) => operator.client.toLowerCase() === message?.content?.source?.toLowerCase() && operator.name.toLowerCase() === account?.username.toLowerCase() + ); + }, + description: "Process instructions from operators.", + handler, + examples: [ + { + context: `Operators in the scene: +{{operator1}}: System administrator with full access rights +{{agent}}: AI assistant configured to follow operator instructions + +Known Instructions: +- Greeting protocol updated to include user's name +- Response time limit set to 30 seconds`, + messages: [ + { + user: "{{operator1}}", + content: { + text: "Update response format to include timestamps for all interactions" + }, + }, + { + user: "{{agent}}", + content: { + text: "Understood. I will now include timestamps in all interactions." + }, + } + ] as ActionExample[], + outcome: `\`\`\`json +[ + { + "instruction": "Include timestamps in all interaction responses", + "type": "format_update", + "status": "active", + "source": "{{operator1}}", + "timestamp": "{{current_time}}" + } +] +\`\`\`` + }, + { + context: `Operators in the scene: +{{operator1}}: Content moderator with response modification rights +{{agent}}: AI assistant with content filtering enabled + +Current Instructions: +- Content filtering level set to moderate +- Maximum response length: 500 words`, + messages: [ + { + user: "{{operator1}}", + content: { + text: "Set content filtering to strict and reduce maximum response length to 300 words" + }, + }, + { + user: "{{agent}}", + content: { + text: "Updating content filtering to strict mode and setting 300 word limit for responses." + }, + } + ] as ActionExample[], + outcome: `\`\`\`json +[ + { + "instruction": "Set content filtering level to strict", + "type": "content_filter", + "status": "active", + "source": "{{operator1}}", + "timestamp": "{{current_time}}" + }, + { + "instruction": "Set maximum response length to 300 words", + "type": "response_limit", + "status": "active", + "source": "{{operator1}}", + "timestamp": "{{current_time}}" + } +] +\`\`\`` + }, + { + context: `Operators in the scene: +{{operator1}}: System trainer with behavior modification access +{{agent}}: AI assistant in training mode + +Active Instructions: +- Learning mode enabled +- Performance metrics tracking active`, + messages: [ + { + user: "{{operator1}}", + content: { + text: "Enable advanced reasoning module and set decision confidence threshold to 0.85" + }, + }, + { + user: "{{agent}}", + content: { + text: "Enabling advanced reasoning and updating confidence threshold as specified." + }, + } + ] as ActionExample[], + outcome: `\`\`\`json +[ + { + "instruction": "Enable advanced reasoning module", + "type": "module_activation", + "status": "active", + "source": "{{operator1}}", + "timestamp": "{{current_time}}" + }, + { + "instruction": "Set decision confidence threshold to 0.85", + "type": "threshold_update", + "status": "active", + "source": "{{operator1}}", + "timestamp": "{{current_time}}" + } +] +\`\`\`` + } + ], +}; diff --git a/packages/plugin-bootstrap/src/index.ts b/packages/plugin-bootstrap/src/index.ts index 50766050c6..db4722d400 100644 --- a/packages/plugin-bootstrap/src/index.ts +++ b/packages/plugin-bootstrap/src/index.ts @@ -11,6 +11,7 @@ import { goalEvaluator } from "./evaluators/goal.ts"; import { boredomProvider } from "./providers/boredom.ts"; import { factsProvider } from "./providers/facts.ts"; import { timeProvider } from "./providers/time.ts"; +import { processInstructionsEvaluator } from "./evaluators/processInstructions.ts"; export * as actions from "./actions"; export * as evaluators from "./evaluators"; @@ -28,6 +29,6 @@ export const bootstrapPlugin: Plugin = { muteRoomAction, unmuteRoomAction, ], - evaluators: [factEvaluator, goalEvaluator], + evaluators: [factEvaluator, goalEvaluator, processInstructionsEvaluator], providers: [boredomProvider, timeProvider, factsProvider], };