diff --git a/docs/concepts/zee-workflows.mdx b/docs/concepts/zee-workflows.mdx index 7965fc5..2bcf298 100644 --- a/docs/concepts/zee-workflows.mdx +++ b/docs/concepts/zee-workflows.mdx @@ -9,10 +9,12 @@ The Zero-Employee Enterprise (ZEE) is a new business model where traditional wor A ZEE workflow contains a couple of components: - The [Agents](/concepts/agents) that are used to solve the problem. -- The **breakdown** agent breaks down the final goal into smaller tasks and assigns them to the agents provided to the workflow. -- The **mastermind** agent facilitate communication between all the agents via necessary context for the final goal. +- The **planner** agent breaks down the final goal into smaller tasks and assigns them to the agents provided to the workflow. +- The **router** agent facilitates communication between all the agents via necessary context for the final goal. - The **endgame** agent is the final agent that takes in the results from all the agents and formulates the final output. +> These agent names are reserved by the ZEE workflow. Make sure not to use these names for your agents. + ## Creating a Workflow To create a workflow, you need to create a new instance of the `ZeeWorkflow` class. diff --git a/docs/get-started/overview.mdx b/docs/get-started/overview.mdx index 11ca289..ca71d4f 100644 --- a/docs/get-started/overview.mdx +++ b/docs/get-started/overview.mdx @@ -14,7 +14,7 @@ The Agent SDK supports single model inference calls to multi-agent systems that flowchart LR %% Styling classDef input fill:#ffffff,stroke:#000000,stroke-width:2px, color:#000 - classDef breakdown fill:#f0f0f0,stroke:#000000,stroke-width:2px, color:#000 + classDef planner fill:#f0f0f0,stroke:#000000,stroke-width:2px, color:#000 classDef agent fill:#e0e0e0,stroke:#000000,stroke-width:2px, color:#000 classDef tool fill:#d0d0d0,stroke:#000000,stroke-width:2px, color:#000 classDef state fill:#fafafa,stroke:#000000,stroke-width:2px,stroke-dasharray: 5 5, color:#000 @@ -25,8 +25,8 @@ flowchart LR subgraph "Zero-Employee Enterprise" direction LR - breakdown{"šŸ” Breakdown Agent"} - mastermind{"🧠 Mastermind Agent"} + planner{"šŸ” Planner Agent"} + router{"🧠 Router Agent"} agent1("šŸ¤– Primary Agent") agent2("šŸ¤– Support Agent") agent3("šŸ¤– Task Agent") @@ -45,11 +45,11 @@ flowchart LR end %% Connections - inp1 --> breakdown - breakdown --> mastermind - mastermind <--> agent1 - mastermind <--> agent2 - mastermind <--> agent3 + inp1 --> planner + planner --> router + router <--> agent1 + router <--> agent2 + router <--> agent3 agent1 <--> tool1 agent2 <--> tool2 agent3 <--> tool3 @@ -57,8 +57,8 @@ flowchart LR %% Apply styles class inp1 input - class breakdown breakdown - class mastermind breakdown + class planner planner + class router router class agent1,agent2,agent3 agent class tool1,tool2,tool3,tool4 tool class id1 state diff --git a/packages/ai-agent-sdk/src/core/agent/agent.ts b/packages/ai-agent-sdk/src/core/agent/agent.ts index dd2378e..f72023c 100644 --- a/packages/ai-agent-sdk/src/core/agent/agent.ts +++ b/packages/ai-agent-sdk/src/core/agent/agent.ts @@ -2,6 +2,7 @@ import type { AgentConfig, AgentGenerateParameters, AgentResponse } from "."; import { systemMessage } from "../../functions"; import { Base } from "../base"; import { LLM } from "../llm"; +import { type CoreMessage } from "ai"; export class Agent extends Base { private _config: AgentConfig; @@ -26,21 +27,17 @@ export class Agent extends Base { } async generate(args: AgentGenerateParameters): Promise { + const _messages = [ + systemMessage(this.description), + ...(this.instructions?.map(systemMessage) ?? []), + ...(args.messages ?? []), + ] as CoreMessage[]; + const response = await this._llm.generate( { ...args, tools: this._config.tools, - messages: [ - systemMessage(this.description), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - ...(this.instructions?.map((instruction) => - systemMessage(instruction) - ) ?? []), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - ...(args.messages ?? []), - ], + messages: _messages, temperature: this._config.temperature, }, true diff --git a/packages/ai-agent-sdk/src/core/zee/zee.ts b/packages/ai-agent-sdk/src/core/zee/zee.ts index 8b2ddc4..10f3754 100644 --- a/packages/ai-agent-sdk/src/core/zee/zee.ts +++ b/packages/ai-agent-sdk/src/core/zee/zee.ts @@ -1,12 +1,12 @@ import type { AgentAction, ContextItem, + RawTask, ZEETask, ZeeWorkflowOptions, ZEEWorkflowResponse, } from "."; -import { ZEEActionResponseType } from "."; -import { systemMessage, Tool, userMessage } from "../.."; +import { systemMessage, Tool, userMessage, ZEEActionResponseType } from "../.."; import { Agent } from "../agent"; import { Base } from "../base/base"; import { type CoreMessage, type FilePart, type ImagePart } from "ai"; @@ -14,6 +14,8 @@ import { z } from "zod"; export class ZeeWorkflow extends Base { private agents: Record = {}; + private defaultAgents: Record = {}; + private addedAgents: Record = {}; private context: ContextItem[] = []; private actionQueue: AgentAction[] = []; private maxIterations: number = 50; @@ -22,8 +24,10 @@ export class ZeeWorkflow extends Base { constructor({ agents, model, goal, config }: ZeeWorkflowOptions) { super("zee"); - console.log("\nšŸš€ Initializing ZeeWorkflow"); - console.log("Goal:", goal); + console.log("\n╭────────────────────────────────────────"); + console.log("│ šŸš€ Initializing ZeeWorkflow"); + console.log(`│ šŸŽÆ Goal: ${goal}`); + console.log("╰────────────────────────────────────────"); if (config?.maxIterations) { this.maxIterations = config.maxIterations; @@ -43,84 +47,56 @@ export class ZeeWorkflow extends Base { this.context.push(userMessage(goal)); - const breakdownAgent = new Agent({ - name: "breakdown", - description: `You are a task breakdown agent that wants to complete the user's goal - "${goal}".`, + const plannerAgent = new Agent({ + name: "planner", + description: `You are a task planner that wants to complete the user's goal - "${goal}".`, instructions: [ - "Break down the user's goal into smaller sequential tasks", - "For every smaller task, select the best agent that can handle the task", - "If the task involves analyzing images or files, include them in the attachments", - `The available agents are: ${JSON.stringify( - Object.values(agents).map( - ({ name, description, instructions }) => ({ - name, - description, - instructions, - }) - ) - )}`, - "Return a JSON array of tasks, where each task has:", - "- agentName: the name of the agent to handle the task", - "- instructions: array of instructions for the agent", - "- attachments: array of attachments items, each being an array of objects with {type: 'image', image: url} or {type: 'file', data: url, mimeType: mimeType}", - "- dependencies: object mapping agent names to why they are needed", - "Example response format:", - JSON.stringify( - [ - { - agentName: "image analyzer", - instructions: ["Analyze the logo design"], - attachments: [ - [ - { - type: "image", - image: "https://example.com/logo.png", - }, + "Plan the user's goal into smaller sequential tasks.", + "Do NOT create a task that is not directly related to the user's goal.", + "Do NOT create a final compilation task.", + `Return a JSON array of tasks, where each task has: + - instructions: array of instructions for completing the task + - attachments: array of attachments items, each being an array of objects with {type: 'image', image: url} or {type: 'file', data: url, mimeType: mimeType} + - dependencies: array of strings describing what this task needs from other tasks + Example response format: + ${JSON.stringify( + [ + { + instructions: ["Analyze the logo design"], + attachments: [ + [ + { + type: "image", + image: "https://example.com/logo.png", + }, + ], + ], + dependencies: [], + }, + { + instructions: [ + "Write brand guidelines based on logo analysis", ], - ], - dependencies: {}, - }, - { - agentName: "writer", - instructions: [ - "Write brand guidelines based on logo analysis", - ], - attachments: [], - dependencies: { - "image analyzer": + attachments: [], + dependencies: [ "Needs logo analysis to write guidelines", + ], }, - }, - ], - null, - 2 - ), + ], + null, + 2 + )}`, "Return ONLY the JSON array, no other text", ], model, temperature: this.temperature, }); - const mastermindAgent = new Agent({ - name: "mastermind", + const routerAgent = new Agent({ + name: "router", description: - "You coordinate information flow between agents to achieve the user's goal.", - instructions: [ - `The available agents are: ${JSON.stringify( - Object.values(agents).map( - ({ name, description, instructions }) => ({ - name, - description, - instructions, - }) - ) - )}`, - "Your ONLY task is to identify and call the right agent to get requested information.", - "1. Identify which agent has the information", - "2. Call that agent ONCE using executeAgent", - "3. Return their response without modification", - "Do not try to process, validate, or get additional information.", - ], + "You coordinate information flow between agents and assign tasks to achieve the user's goal.", + instructions: [], model, tools: { executeAgent: new Tool({ @@ -152,7 +128,7 @@ export class ZeeWorkflow extends Base { const agent = this.getAgent(agentName); if (!agent) { throw new Error( - `Agent '${agentName}' not found. Available agents: '${Object.keys(this.agents).join("', '")}'.` + `Agent '${agentName}' not found. Available agents: '${Object.keys(this.addedAgents).join("', '")}'.` ); } @@ -180,15 +156,32 @@ export class ZeeWorkflow extends Base { temperature: this.temperature, }); - [breakdownAgent, mastermindAgent, endgameAgent, ...agents].forEach( - (agent) => { - if (!this.agents[agent.name]) { - this.agents[agent.name] = agent; - } else { - throw new Error(`Agent '${agent.name}' already exists`); - } + [plannerAgent, routerAgent, endgameAgent].forEach((agent) => { + if (!this.defaultAgents[agent.name]) { + this.defaultAgents[agent.name] = agent; + } else { + throw new Error(`Agent '${agent.name}' already exists`); } - ); + }); + + agents.forEach((agent) => { + if (!this.addedAgents[agent.name]) { + this.addedAgents[agent.name] = agent; + } else { + throw new Error(`Agent '${agent.name}' already exists`); + } + }); + + [ + ...Object.values(this.defaultAgents), + ...Object.values(this.addedAgents), + ].forEach((agent) => { + if (!this.agents[agent.name]) { + this.agents[agent.name] = agent; + } else { + throw new Error(`Agent '${agent.name}' already exists`); + } + }); } private getAgent(agentName: string): Agent { @@ -202,39 +195,63 @@ export class ZeeWorkflow extends Base { ); } - private parseBreakdownResponse(response: string): ZEETask[] { - console.log("\nšŸ“ Parsing 'breakdown' response"); + private parseTasks(response: string): ZEETask[] { + console.log("\nšŸ“ Parsed Tasks"); try { const tasks = JSON.parse(response) as ZEETask[]; if (!Array.isArray(tasks)) { - throw new Error("'breakdown' response must be an array"); + throw new Error("'planner' response must be an array"); } + console.log(`\nšŸ” Found ${tasks.length} tasks to process\n`); + tasks.forEach((task, index) => { if (!task.agentName || !Array.isArray(task.instructions)) { throw new Error(`Invalid task format at index ${index}`); } + console.log(`\n╭────────────────────────────────────────`); console.log( - `\nšŸ“Œ Task for '${task.agentName}':`, - task.instructions, - Object.keys(task.dependencies).length - ? `\nDependent on: ${Object.entries(task.dependencies) - .map(([key, value]) => `${key}: ${value}`) - .join(", ")}` - : "", - task.attachments.length - ? `\nAttachments provided: ${task.attachments.map( - (item) => - item.map( - (i) => - `${i.type}: ${(i as ImagePart).image || (i as FilePart).data}` - ) - )}` - : "" + `│ šŸ“‹ TASK ${index + 1} of ${tasks.length}: Assigned to '${task.agentName}'` ); + console.log(`ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€`); + + console.log(`│ šŸ“ Instructions:`); + task.instructions.forEach((instruction, i) => { + console.log(`│ ${i + 1}. ${instruction}`); + }); + + if (task.dependencies.length) { + console.log(`│ šŸ”„ Dependencies:`); + task.dependencies.forEach((dep, i) => { + console.log( + `│ ${i + 1}. Needs input from '${dep.agentName}': "${dep.task}"` + ); + }); + } + + if (task.attachments.length) { + console.log(`│ šŸ“Ž Attachments:`); + task.attachments.forEach((items, i) => { + items.forEach((item, j) => { + const typeStr = item.type; + const contentStr = + (item as ImagePart).image || + (item as FilePart).data; + const contentPreview = String(contentStr).substring( + 0, + 60 + ); + console.log( + `│ ${i + 1}.${j + 1} ${typeStr}: ${contentPreview}${String(contentStr).length > 60 ? "..." : ""}` + ); + }); + }); + } + + console.log(`╰────────────────────────────────────────`); if (task.attachments && !Array.isArray(task.attachments)) { throw new Error( @@ -245,10 +262,10 @@ export class ZeeWorkflow extends Base { return tasks; } catch (error) { - console.error("\nāŒ Error parsing 'breakdown' response:", error); + console.error("\nāŒ Error parsing 'planner' response:", error); console.log("Raw response:", response); throw new Error( - `Failed to parse 'breakdown' response: ${error instanceof Error ? error.message : String(error)}` + `Failed to parse 'planner' response: ${error instanceof Error ? error.message : String(error)}` ); } } @@ -282,124 +299,251 @@ export class ZeeWorkflow extends Base { return; } - const targetAgent = this.getAgent(action.to); - - console.log("\nšŸ“¦ Current context:", this.context); - - const relevantContext: string | null = - (action.to === "mastermind" - ? this.context - .filter((ctx) => ctx.role !== "user") - .map((ctx) => `${ctx.role}: ${ctx.content}`) - .join("\n") - : this.context - .filter( - (ctx) => - Object.keys( - action.metadata?.dependencies || {} - ).includes(ctx.role as string) || - ctx.role === "user" - ) - .map((ctx) => `${ctx.role}: ${ctx.content}`) - .join("\n")) || null; - - console.log(`\nšŸ” Filtered relevant context for '${action.to}'`); - console.log("\nšŸ“¤ Sending information:", { - relevantContext: relevantContext, - content: action.content, - }); - console.log(`\nšŸ’­ '${action.to}' thinking...`); + try { + const targetAgent = this.getAgent(action.to); + + console.log("\nšŸ“¦ Current context:", this.context.length); + + const relevantContext: string | null = + (action.to === "router" + ? action.type === "followup" + ? this.context + .map((ctx) => `${ctx.role}: ${ctx.content}`) + .join("\n") + : this.context + .filter((ctx) => ctx.role !== "user") + .map((ctx) => `${ctx.role}: ${ctx.content}`) + .join("\n") + : this.context + .filter( + (ctx) => + (action.metadata?.dependencies || []).some( + (dep) => dep.agentName === ctx.role + ) || ctx.role === "user" + ) + .map((ctx) => `${ctx.role}: ${ctx.content}`) + .join("\n")) || null; + + console.log(`\nšŸ” Filtered relevant context for '${action.to}'`); + console.log("\nšŸ“¤ Sending information:", { + relevantContext, + content: action.content, + }); + console.log(`\nšŸ’­ '${action.to}' thinking...`); - const messages: CoreMessage[] = []; + const messages: CoreMessage[] = []; + + if (action.to !== "router") { + messages.push( + systemMessage( + `You have to: + 1. Complete your task by providing an answer ONLY for the 'Current task' from the context. + 2. If the answer in not in the context, try to avoid asking for more information. + 3. If you ABSOLUTELY need additional information to complete your task, request more information by asking a question + + Instructions for responding: + - If you need more information, start with "${ZEEActionResponseType.FOLLOWUP}" followed by your question + - If this is your answer, start with "${ZEEActionResponseType.COMPLETE}" followed by your response.` + ) + ); + } else if (action.type === "followup") { + messages.push( + systemMessage( + `You're handling a followup question from an agent who needs more information to complete their task. + + ${action.metadata?.originalFrom ? `Question from: '${action.metadata.originalFrom}'` : ""} + ${action.metadata?.originalTask ? `\nOriginal task: ${action.metadata.originalTask}` : ""} + + You have access to the COMPLETE context of all previous communications between agents. + Use this full context to provide the most accurate and helpful answer. + + Your job is to provide a direct, helpful answer based on the complete context and your knowledge. + Be specific and thorough in your response, as the agent is relying on your expertise. + + Start your response with "${ZEEActionResponseType.ANSWER}" followed by your answer. + Example: "${ZEEActionResponseType.ANSWER} The script should use standard screenplay format." + ` + ) + ); + } - if (action.to !== "mastermind") { - messages.push( - systemMessage( - `You have to: - 1. Complete your task by providing an answer for the current task from the context. - 2. If the answer in not in the context, try to avoid asking for more information. - 3. If you ABSOLUTELY need additional information to complete your task, request more information by asking a question - - Instructions for responding: - - If you need more information, start with "${ZEEActionResponseType.NEED_INFO}" followed by your question - - If this is your answer, start with "${ZEEActionResponseType.COMPLETE}" followed by your response.` - ) - ); - } else if (action.type === "followup") { messages.push( - systemMessage( - `start your response with "${ZEEActionResponseType.FOLLOWUP_COMPLETE}[agent name]:" followed by the response from the agent. Replace 'agent name' with the name of the agent that is responding.` + userMessage( + `${relevantContext ? `Relevant context -> ${relevantContext}` : ""} + \nCurrent task -> ${action.content}` ) ); - } - messages.push( - userMessage( - `${relevantContext ? `Relevant context -> ${relevantContext}` : ""} - \nCurrent task -> ${action.content}` - ) - ); + if (action.metadata?.attachments?.length) { + messages.push(...action.metadata.attachments.map(userMessage)); + } + + const response = await targetAgent.generate({ messages }); + + const responseContent = response.value; + + this.processAgentResponse(responseContent, action); + } catch (error) { + console.error(`\nāŒ Error processing action:`, error); + + if (error instanceof Error && error.message.includes("not found")) { + console.error( + `\nāŒ Agent '${action.to}' not found. Available agents: ${Object.keys(this.agents).join(", ")}` + ); + + if (action.type === "followup" && action.to !== "router") { + console.log( + `\nāš ļø Redirecting followup to router instead of invalid agent '${action.to}'` + ); + const redirectAction: AgentAction = { + ...action, + to: "router", + content: `${action.content}\n\nNOTE: This was originally directed to '${action.to}' but that agent doesn't exist. Please handle this followup request.`, + }; + this.actionQueue.unshift(redirectAction); + return; + } + } - if (action.metadata?.attachments?.length) { - messages.push(...action.metadata.attachments.map(userMessage)); + this.context.push({ + role: "error", + content: `Error in communication between ${action.from} -> ${action.to}: ${error instanceof Error ? error.message : String(error)}`, + }); } + } + + private processAgentResponse(responseContent: string, action: AgentAction) { + // * INFO: 1. Agent needs more information + if (responseContent.startsWith(ZEEActionResponseType.FOLLOWUP)) { + const infoContent = responseContent + .replace(ZEEActionResponseType.FOLLOWUP, "") + .trim(); - const response = await targetAgent.generate({ messages }); + console.log(`\n╭────────────────────────────────────────`); + console.log(`│ ā“ '${action.to}' asked a followup:`); + console.log(`│ šŸ” "${infoContent}"`); + console.log(`╰────────────────────────────────────────`); - const responseContent = response.value; + const dependencyInfo = action.metadata?.dependencies + ? `\n\nContext: Agent has dependencies on: ${action.metadata.dependencies.map((d) => d.agentName).join(", ")}` + : "\n\nContext: Agent has no explicit dependencies"; + + const enrichedContent = `${infoContent}${dependencyInfo}`; - if (responseContent.startsWith(ZEEActionResponseType.NEED_INFO)) { const infoResponse: AgentAction = { type: "followup", from: action.to!, - to: "mastermind", - content: responseContent - .replace(ZEEActionResponseType.NEED_INFO, "") - .trim(), + to: "router", + content: enrichedContent, + metadata: { + originalTask: action.content, + originalFrom: action.from, + }, }; - this.actionQueue.unshift(action); + this.actionQueue.unshift(infoResponse); - console.log( - `\nā“ '${action.to}' needs more information`, - infoResponse.content - ); - } else if ( - responseContent.startsWith(ZEEActionResponseType.FOLLOWUP_COMPLETE) - ) { - const followupCompletePattern = `${ZEEActionResponseType.FOLLOWUP_COMPLETE}\\[(.*?)\\]:\\s*`; - const match = responseContent.match(followupCompletePattern); - const agentName = match?.[1]?.trim(); console.log( - `\nāš™ļø Handling followup response from '${agentName}'`, - action.to + `\nšŸ”„ Followup chain: '${action.to}' → router → '${action.to}'` ); + } - if (!agentName) { - console.error( - `\nāŒ No agent name - '${agentName}' found in response from '${action.to}'` + // * INFO: 2. 'Router' providing an answer to a followup + else if (action.to === "router" && action.type === "followup") { + let answerContent = responseContent; + + if (!responseContent.startsWith(ZEEActionResponseType.ANSWER)) { + console.log( + `\nāš ļø 'Router' response missing ${ZEEActionResponseType.ANSWER} prefix, treating as direct answer` ); - return; + } else { + answerContent = responseContent + .replace(ZEEActionResponseType.ANSWER, "") + .trim(); } - const followupResponse: AgentAction = { + console.log(`\n╭────────────────────────────────────────`); + console.log(`│ šŸ“ 'Router' answered:`); + console.log( + `│ šŸ’¬ "${answerContent.substring(0, 100)}${answerContent.length > 100 ? "..." : ""}"` + ); + console.log(`╰────────────────────────────────────────`); + + const answerResponse: AgentAction = { type: "response", - from: agentName, + from: "router", + to: action.from, + content: answerContent, + metadata: { + isTaskComplete: true, + }, + }; + + if ( + action.metadata?.originalFrom && + action.metadata?.originalTask + ) { + const originalQuestion = + action.content?.split("\n\nContext:")?.[0]?.trim() || + "details about characters"; + + const originalTask: AgentAction = { + type: "request", + from: "router", + to: action.from, + content: `${action.metadata.originalTask}\n\nYou previously asked: "${originalQuestion}"\n\nAnswer from router: ${answerContent}\n\nPlease complete your task with this information.`, + metadata: { + dependencies: action.metadata.dependencies, + attachments: action.metadata.attachments, + }, + }; + this.actionQueue.unshift(originalTask); + } + + this.actionQueue.unshift(answerResponse); + + console.log(`\nšŸ”„ Answer being sent: 'router' → '${action.from}'`); + } + + // * INFO 3. Agent completed its task + else if (responseContent.startsWith(ZEEActionResponseType.COMPLETE)) { + const completeContent = responseContent + .replace(ZEEActionResponseType.COMPLETE, "") + .trim(); + + console.log(`\n╭────────────────────────────────────────`); + console.log(`│ āœ… '${action.to}' completed task:`); + console.log(`╰────────────────────────────────────────`); + + const completeAction: AgentAction = { + type: "complete", + from: action.to!, to: action.from, - content: responseContent.replace(match?.[0] || "", "").trim(), + content: completeContent, metadata: { isTaskComplete: true, }, }; - this.actionQueue.unshift(followupResponse); - } else if (responseContent.startsWith(ZEEActionResponseType.COMPLETE)) { + this.actionQueue.unshift(completeAction); + } + + // * INFO 4. Handle unformatted responses gracefully + else { + console.log(`\n╭────────────────────────────────────────`); + console.log( + `│ āš ļø Response from '${action.to}' doesn't use expected format:` + ); + console.log( + `│ šŸ” "${responseContent.substring(0, 100)}${responseContent.length > 100 ? "..." : ""}"` + ); + console.log(`│ šŸ“Œ Treating as complete response`); + console.log(`╰────────────────────────────────────────`); + const completeAction: AgentAction = { type: "complete", from: action.to!, to: action.from, - content: responseContent - .replace(ZEEActionResponseType.COMPLETE, "") - .trim(), + content: responseContent, metadata: { isTaskComplete: true, }, @@ -409,19 +553,53 @@ export class ZeeWorkflow extends Base { } public async run(): Promise { - console.log("\nšŸŽ¬ Starting workflow execution"); + console.log("\n╭────────────────────────────────────────"); + console.log("│ šŸŽ¬ Starting workflow execution"); + console.log("╰────────────────────────────────────────"); - console.log("\nšŸ“‹ Getting task breakdown from 'breakdown'..."); - const breakdownResponse = await this.getAgent("breakdown").generate({ + console.log("\nšŸ“‹ Getting tasks from 'planner'..."); + const plannerResponse = await this.getAgent("planner").generate({ messages: [userMessage(this.goal)], }); - const tasks = this.parseBreakdownResponse(breakdownResponse.value); + const rawTasks = JSON.parse(plannerResponse.value) as RawTask[]; + + console.log("\nšŸ“‹ Assigning agents to tasks via 'router'..."); + const routerResponse = await this.getAgent("router").generate({ + messages: [ + systemMessage( + `The available agents are: ${JSON.stringify( + Object.values(this.addedAgents).map( + ({ name, description, instructions }) => ({ + name, + description, + instructions, + }) + ) + )} + For each task: + 1. Analyze the task requirements + 2. Select the most suitable agent based on their name, description, and instructions + 3. Convert the dependencies from string[] to {agentName: string, task: string}[]: + - For each dependency, determine which agent should handle it + - Create objects with "agentName" and "task" fields instead of string dependencies + 4. Return a JSON array where each item includes the original task data plus: + - agentName: string (the name of the chosen agent) + - dependencies: the restructured dependencies array with objects + 5. Reorder the tasks based on the dependencies for easier processing + + IMPORTANT: Return ONLY the JSON array, no other text` + ), + userMessage(JSON.stringify(rawTasks)), + ], + }); + + const tasks = this.parseTasks(routerResponse.value); tasks.forEach((task) => { this.actionQueue.push({ type: "request", - from: "mastermind", + from: "router", to: task.agentName, content: task.instructions.join("\n"), metadata: { @@ -441,10 +619,17 @@ export class ZeeWorkflow extends Base { } iterationCount++; + const nextAction = this.actionQueue[0]; + + console.log("\n╭────────────────────────────────────────"); console.log( - `\nšŸ”„ Iteration ${iterationCount}\nQueue size: ${this.actionQueue.length}`, - `Next action: ${this.actionQueue[0]?.type} from ${this.actionQueue[0]?.from} to ${this.actionQueue[0]?.to}` + `│ šŸ”„ ITERATION ${iterationCount} of max ${this.maxIterations}` ); + console.log(`│ šŸ“Š Queue size: ${this.actionQueue.length} actions`); + console.log( + `│ šŸ“‘ Next action: ${nextAction?.type} from '${nextAction?.from}' to '${nextAction?.to}'` + ); + console.log("╰────────────────────────────────────────"); const action = this.actionQueue.shift()!; @@ -465,17 +650,19 @@ export class ZeeWorkflow extends Base { if (iterationCount >= this.maxIterations) { console.warn("\nāš ļø Reached maximum iterations limit"); } else { - console.log("\n✨ All agents have completed their tasks"); + console.log("\n╭────────────────────────────────────────"); + console.log("│ ✨ All agents have completed their tasks"); + console.log("╰────────────────────────────────────────"); } - console.log("\nšŸŽ­ Getting final compilation from endgame agent..."); + console.log("\nšŸ“‹ Getting final compilation from endgame agent..."); const endgameResponse = await this.getAgent("endgame").generate({ messages: [userMessage(JSON.stringify(this.context))], }); - console.log( - `\n 🟢 Workflow completed in ${iterationCount} iterations!` - ); + console.log("\n╭────────────────────────────────────────"); + console.log(`│ 🟢 Workflow completed in ${iterationCount} iterations!`); + console.log("╰────────────────────────────────────────"); return { content: endgameResponse.value, diff --git a/packages/ai-agent-sdk/src/core/zee/zee.types.ts b/packages/ai-agent-sdk/src/core/zee/zee.types.ts index 25dea5d..b53d9bf 100644 --- a/packages/ai-agent-sdk/src/core/zee/zee.types.ts +++ b/packages/ai-agent-sdk/src/core/zee/zee.types.ts @@ -17,9 +17,14 @@ export interface AgentAction { to: string; content: string; metadata?: { - dependencies?: Record; + dependencies?: { + agentName: string; + task: string; + }[]; isTaskComplete?: boolean; attachments?: UserContentAttachments[]; + originalTask?: string; + originalFrom?: string; }; } @@ -34,14 +39,21 @@ export interface ZEEWorkflowResponse { } export enum ZEEActionResponseType { - NEED_INFO = "NEED_INFO:", - FOLLOWUP_COMPLETE = "FOLLOWUP_COMPLETE:", + FOLLOWUP = "FOLLOWUP:", + ANSWER = "ANSWER:", COMPLETE = "COMPLETE:", } -export interface ZEETask { - agentName: string; +export interface RawTask { instructions: string[]; attachments: UserContentAttachments[]; - dependencies: Record; + dependencies: string[]; +} + +export interface ZEETask extends Omit { + agentName: string; + dependencies: { + agentName: string; + task: string; + }[]; } diff --git a/packages/ai-agent-sdk/src/tests/zee.test.ts b/packages/ai-agent-sdk/src/tests/zee.test.ts index e53605e..ce4ea5b 100644 --- a/packages/ai-agent-sdk/src/tests/zee.test.ts +++ b/packages/ai-agent-sdk/src/tests/zee.test.ts @@ -29,8 +29,8 @@ describe("@ai-agent-sdk/zee", () => { providers.forEach((model) => { describe(`${model.provider}::${model.id}`, () => { test("workflow with two agents", async () => { - const scriptWriter = new Agent({ - name: "script writer", + const screenplayWriter = new Agent({ + name: "screenplay writer", description: "You are an expert screenplay writer", instructions: [ "Write a script outline with 3-5 main characters and key plot points.", @@ -53,7 +53,7 @@ describe("@ai-agent-sdk/zee", () => { const zee = new ZeeWorkflow({ goal: "Plan a scene-by-scene script for a movie that is 10 minutes long and has a happy ending. Create a scene-by-scene budget for the provided script. Suggest a cast and crew for the movie.", - agents: [scriptWriter, producer], + agents: [screenplayWriter, producer], model, config: { temperature: 1, @@ -85,7 +85,7 @@ describe("@ai-agent-sdk/zee", () => { "Review the script outline.", "You MUST ask the script writer for detailed character descriptions before making casting decisions.", "Once you have character details, provide casting suggestions and budget breakdown.", - "Use 'NEED_INFO:' to ask for character details.", + "Use 'FOLLOWUP:' to ask for character details.", "Start your final plan with 'COMPLETE:'", ], model, diff --git a/packages/ai-agent-sdk/vitest.config.ts b/packages/ai-agent-sdk/vitest.config.ts index 360375c..5dbbf4e 100644 --- a/packages/ai-agent-sdk/vitest.config.ts +++ b/packages/ai-agent-sdk/vitest.config.ts @@ -3,6 +3,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { exclude: ["dist", "node_modules"], - testTimeout: 120_000, + testTimeout: 500_000, }, });