UNPKG

node-agency

Version:
276 lines (242 loc) 7.48 kB
import { Agent } from "./agent"; import { Task } from "./task"; import { getManagerTools, getCoworkerTools, VectorStore, getEmbeddings, getContent, groupIntoNChunks, generateOutput, } from "./utils"; import { Model as OpenAIModel } from "./models/openai"; import { Model as ClaudeModel } from "./models/claude"; import colors from "colors"; import fs from "fs"; type MemoryTrue = { memory: true }; type MemoryFalse = { memory: false }; type MemoryNotSet = { memory?: undefined }; type ProcessHierarchical = { process: "hierarchical" }; type ProcessSequential = { process: "sequential" }; type ProcessNotSet = { process?: undefined }; type AgencyProps = { agents: ReturnType<typeof Agent>[]; tasks: ReturnType<typeof Task>[]; llm?: OpenAIModel | ClaudeModel; process?: "sequential" | "hierarchical"; memory?: boolean; humanFeedback?: boolean; outFile?: string; selfReflect?: boolean; } & ( | (MemoryTrue & { resources?: string[] }) | (MemoryFalse & { resources?: never }) | (MemoryNotSet & { resources?: never }) ) & ( | (ProcessHierarchical & { llm: OpenAIModel }) | (ProcessSequential & { llm?: never }) | (ProcessNotSet & { llm: OpenAIModel }) | (ProcessHierarchical & { llm: ClaudeModel }) | (ProcessNotSet & { llm: ClaudeModel }) ); type UserMessage = { role: "user" | "assistant"; content: string; }; type HistoryItem = UserMessage; export type History = HistoryItem[]; export const Agency = function ({ agents, tasks, llm, process = "hierarchical", memory = false, resources, humanFeedback = true, outFile, selfReflect = true, }: AgencyProps) { let manager: ReturnType<typeof Agent> | undefined; let store: ReturnType<typeof VectorStore> | undefined; if (memory) { store = VectorStore(); } if (llm && process === "hierarchical") { llm.isManager = true; llm.selfReflect = selfReflect; manager = Agent({ role: "Supervising Manager", goal: "Complete the task with the of agents, delegating tasks as needed. Please use the content from tool calls to come up with your final response.", tools: getManagerTools(agents, humanFeedback), model: llm, }); if (store) { manager.memory(store); } } if (store) { agents.forEach((agent) => { agent.memory(store); if (agent.model) { agent.model.selfReflect = selfReflect; } }); } else { agents.forEach((agent) => { if (agent.model) { agent.model.selfReflect = selfReflect; } }); } if (resources && !store) { throw new Error( "Resources can only be used with memory enabled. Please enable memory to use resources." ); } if (resources && store) { resources.forEach(async (resource) => { if (store) { const resourceContent = await getContent(resource); for (const content of resourceContent) { const embeddings = await getEmbeddings(content); const emb = embeddings.data.map((e) => e.embedding); store.addVectors(emb, [ { pageContent: content, metadata: { type: "resource", tags: [resource], }, }, ]); } } }); } const kickoff = async () => { console.log(colors.green("Starting Agency...\n\n")); let context = ""; let finalOutput = ""; const startTime = new Date().getTime(); for (const task of tasks) { const coworkerTools = getCoworkerTools( agents.filter((agent) => agent.role !== task.agent?.role), humanFeedback ); const out = await task.execute({ agent: manager, context, tools: process !== "hierarchical" ? coworkerTools : undefined, }); if (memory) { // TODO: store in longterm memory using sqlite } context += `${out}\n-----------------\n`; finalOutput = out; } const endTime = new Date().getTime(); const runTime = endTime - startTime; const formattedRunTime = `${Math.floor( runTime / 60000 )} minutes and ${Math.floor((runTime % 60000) / 1000)} seconds`; console.log(colors.green("\nAgency Completed!\n\n")); if (outFile) { console.log(colors.green(`Writing results to file: ${outFile}`)); // delete file if exists if (fs.existsSync(outFile)) { fs.unlinkSync(outFile); } // create directory if not exists const paths = outFile.split("/"); if (paths.length > 1) { const dir = paths.slice(0, paths.length - 1).join("/"); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } // write to file fs.writeFileSync(outFile, finalOutput, "utf-8"); } return generateOutput(finalOutput, formattedRunTime); }; const run = async <T extends boolean>( prompt: string, stream: T, history?: History ): Promise< T extends true ? Awaited<ReturnType<OpenAIModel["callStream"]>> : string > => { if (!manager) { throw new Error( "Manager is not defined. Please provide a manager model to run the agency in chatbot mode." ); } const executeMethod = stream ? "executeStream" : "execute"; if (history) { const newHistory = history.map((item) => { return { role: item.role, content: item.content }; }) as OpenAIModel["history"]; // const currentToolCalls = manager.model.history.filter( // (item) => // // For OpenAI // (item.role === "assistant" && // "tool_calls" in item && // item.tool_calls && // item.tool_calls.length > 0) || // // For Claude // (item.role === "assistant" && // item.content && // typeof item.content === "object" && // item.content.filter((c) => c.type === "tool_use").length > 0) // ); // const [firstHistoryItems, middleHistoryItems, lastHistoryItems] = // groupIntoNChunks(newHistory, 3); // manager.model.history = [ // ...firstHistoryItems, // ...currentToolCalls, // ...middleHistoryItems, // ...lastHistoryItems, // ]; manager.model.history = newHistory; } const result = (await manager[executeMethod](prompt)) as T extends true ? Awaited<ReturnType<OpenAIModel["callStream"]>> : string; // if history is passed, clear the history after execution if (history) { for (const agent of agents) { if (agent.model) { agent.model.history = []; } } } else { // pune if history too large if (manager.model.history.length > 100) { manager.model.history = manager.model.history.slice( manager.model.history.length - 50 ); } // prune each agent's history for (const agent of agents) { if (agent.model && agent.model.history.length > 100) { agent.model.history = agent.model.history.slice( agent.model.history.length - 50 ); } } } return result; }; const execute = async (prompt: string, history?: History) => { return run(prompt, false, history); }; const executeStream = async (prompt: string, history?: History) => { return run(prompt, true, history); }; return { kickoff, execute, executeStream, }; };