UNPKG

@mintplex-labs/openai-assistant-swarm

Version:

A simple library the extends the OpenAI NodeJS SDK so you can automatically delegate any task to any assistants you create in OpenAi through one united interface and manager. Now you can delegate work to a swarm of assistant all specialized with specific

687 lines (680 loc) 24.1 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); // src/manager/index.ts import EventEmitter from "events"; // src/utils/index.ts var DEFAULT_OPTS = { debug: false, managerAssistantOptions: { name: "[AUTOMATED] ___Swarm Manager", model: "gpt-3.5-turbo" } }; function toolsFromAssistants(assistants = []) { return [ { type: "function", function: { name: "delegate", description: "Delegates tasks with descriptive prompt to an autonomous assistant that will then go and execute the assignment based on the information provided.", parameters: { type: "object", properties: { instructions: { type: "array", description: "The information that will enable an assistant go an execute a provided task.", items: { type: "object", properties: { prompt: { type: "string" }, agent_id: { type: "array", description: "The list agent_id who will work", enum: [...assistants.map((a) => a.id), "<none>"] } } } } }, required: ["instructions"] } } } ]; } async function messageHistoryForThread(client, logger, threadId) { return await new Promise(async (resolve, _) => { var _a, _b, _c, _d, _e; try { let _messages = []; let nextPage = null; let hasMorePages = true; while (hasMorePages) { const response = await client.beta.threads.messages.list(threadId, __spreadValues({ limit: 100 }, nextPage ? { after: nextPage } : {})); for (const msg of response.data) { _messages.push({ role: "user", // Assistants only support user role right now. content: (_b = (_a = msg.content.find( (i) => i.type === "text" )) == null ? void 0 : _a.text) == null ? void 0 : _b.value }); } hasMorePages = response.hasNextPage(); nextPage = (_e = (_d = (_c = response.data.slice(-1)) == null ? void 0 : _c[0]) == null ? void 0 : _d.id) != null ? _e : null; } resolve(_messages.flat()); } catch (e) { logger(e.message); resolve([]); } }); } async function _allAssistants(client, logger) { return await new Promise(async (resolve, _) => { var _a, _b, _c; try { let _assistants = []; let nextPage = null; let hasMorePages = true; while (hasMorePages) { const response = await client.list(__spreadValues({ limit: 100 }, nextPage ? { after: nextPage } : {})); _assistants.push(response.data); hasMorePages = response.hasNextPage(); nextPage = (_c = (_b = (_a = response.data.slice(-1)) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) != null ? _c : null; } resolve(_assistants.flat()); } catch (e) { logger(e.message); resolve([]); } }); } async function _getAssistants(client, logger, assistantIds = []) { if (assistantIds.includes("<any>")) return await _allAssistants(client, logger); const assistantPromises = []; for (const assistantId of assistantIds) { assistantPromises.push( new Promise(async (resolve) => { try { await client.retrieve(assistantId).then((assistant) => resolve(assistant)).catch((e) => { logger(e.message); resolve(null); }); } catch (e) { logger(e.message); resolve(null); } }) ); } const assistants = (await Promise.all(assistantPromises)).filter( (assistant) => assistant !== null ); return assistants; } async function pollRun(client, logGroup, logger, runItem) { if (!["queued", "in_progress"].includes(runItem.status)) return runItem; logGroup(`Polling status of ${runItem.id}...`); let count = 1; let polling = true; const pollInterval = 5e3; while (polling) { if (count > 5) { logGroup(null, true); return runItem; } const run = await client.beta.threads.runs.retrieve( runItem.thread_id, runItem.id ); logger({ status: run.status }); if (!["queued", "in_progress"].includes(run.status)) { logGroup(null, true); return run; } count++; await new Promise((r) => setTimeout(r, pollInterval * count)); } logGroup(null, true); return runItem; } async function runMessage(client, logger, runItem) { try { const lastMessageText = client.beta.threads.messages.list(runItem.thread_id, { limit: 10, order: "asc" }).then(({ data }) => data.find((msg) => msg.role === "assistant")).then((firstAssistantMsg) => { if (!firstAssistantMsg) throw new Error("Assistant never responded."); return firstAssistantMsg.content.find( (content) => content.type === "text" ); }).then((textContent) => { var _a; return (_a = textContent == null ? void 0 : textContent.text) == null ? void 0 : _a.value; }).then((text) => { if (!text) throw new Error("No response was created."); return text; }); return lastMessageText; } catch (e) { logger(e.message); return "Failed to get a written response from the thread."; } } function deDupeToolOutputs(toolOutputs) { const compressed = []; const uniqueCallIds = /* @__PURE__ */ new Set(); for (const output of toolOutputs) { if (uniqueCallIds.has(output.tool_call_id)) { const existing = compressed.find((call) => call.tool_call_id); if (!existing) continue; existing.output = [...existing.output, output.output].flat(); continue; } compressed.push(output); uniqueCallIds.add(output.tool_call_id); } return compressed.map((res) => { return { tool_call_id: res.tool_call_id, output: JSON.stringify(res.output) }; }); } // src/manager/child.ts var SwarmAssistant = class { constructor(parent) { this.super = parent; } async runDelegatedTask(parentRun, toolCall, messageHistory) { var _a, _b, _c, _d; const lastMsg = ((_b = (_a = messageHistory == null ? void 0 : messageHistory.slice(-1)) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) || null; const delegationMessage = `You are being assigned a task from the system! Use all available information and your instructions to complete it with 100% satisfaction. System: ${toolCall.args.prompt} The last user message was the following: ${lastMsg} `; const thread = await this.super.client.beta.threads.create({ messages: [{ role: "user", content: delegationMessage }], metadata: { delegatedBy: parentRun.assistant_id, viaFunc: toolCall.function, toAssistant: toolCall.agentId, originatingToolCallId: toolCall.id, createdBy: "@mintplex-labs/openai-assistant-swarm" } }); if (!thread) throw new Error("Failed to create thread for sub task."); if (!((_c = this.super.knownAssistantIds) == null ? void 0 : _c.includes(toolCall.agentId))) throw new Error( `Assistant ${toolCall.agentId} is not a known assistant! Must have been hallucinated.` ); const assistant = (_d = await _getAssistants(this.super.assistants, this.super.log, [ toolCall.agentId ])) == null ? void 0 : _d[0]; const run = await this.super.client.beta.threads.runs.create(thread.id, { assistant_id: assistant.id }); if (!run) throw new Error( `Failed to create run for thread of sub task for assistant ${toolCall.agentId}.` ); this.super.emitEvent("poll_event", { data: { status: "child_run_created", prompt: delegationMessage, playground: this.super.playgroundLink(run), run } }); this.super.log( `Running sub-child task for ${assistant.id} (${assistant.name})` ); this.super.log({ childThreadPlaygroundLink: this.super.playgroundLink(run) }); const settledRun = await pollRun( this.super.client, this.super.logGroup, this.super.log, run ); const textResponse = settledRun.status === "completed" ? await runMessage(this.super.client, this.super.log, settledRun) : null; this.super.emitEvent("poll_event", { data: { status: "child_run_concluded", playground: this.super.playgroundLink(run), textResponse, run: settledRun } }); return { abortReason: null, textResponse: settledRun.status === "completed" ? await runMessage(this.super.client, this.super.log, settledRun) : null, parentToolCallId: toolCall.id, status: "success", run: settledRun, playground: this.super.playgroundLink(settledRun), assistant, parentRun, thread }; } }; // src/utils/toolCalls.ts function compressToolCalls(swarmManager, primaryRun) { var _a, _b, _c, _d, _e; return ((_e = (_d = (_c = (_b = (_a = primaryRun.required_action) == null ? void 0 : _a.submit_tool_outputs) == null ? void 0 : _b.tool_calls) == null ? void 0 : _c.map((toolCall) => { try { const isParallelCall = toolCall.function.name = "multi_tool_use.parallel"; if (isParallelCall) { swarmManager.log( `Parallel function call instruction set found - parsing` ); return parseParallelToolCall(toolCall); } swarmManager.log( `Single function call instruction set found - parsing` ); return parseSingleToolCall(toolCall); } catch (e) { } return null; })) == null ? void 0 : _d.flat()) == null ? void 0 : _e.filter((call) => !!call)) || []; } function parseParallelToolCall(toolCall) { let instructions; const callId = toolCall.id; const data = JSON.parse(toolCall.function.arguments); if (data.hasOwnProperty("tool_uses")) { instructions = data.tool_uses.map(({ parameters }) => parameters.instructions).flat(); } else { instructions = data.instructions; } return instructions.map((instruction) => { return { id: callId, function: "delegate", agentId: instruction.agent_id, args: instruction }; }); } function parseSingleToolCall(toolCall) { const { instructions } = JSON.parse(toolCall.function.arguments); return instructions.map((instruction) => { return { id: toolCall.id, function: toolCall.function, agentId: instruction.agent_id, args: instruction }; }); } // src/manager/index.ts var SwarmManager = class { constructor(client, options) { var _a; this.emitter = new EventEmitter(); this.ready = false; this.mgrAssistant; this.knownAssistantIds = []; this.client = client; this.assistants = client.beta.assistants; this.options = options != null ? options : DEFAULT_OPTS; this._mgrBotName = ((_a = this.options.managerAssistantOptions) == null ? void 0 : _a.name) || DEFAULT_OPTS.managerAssistantOptions.name; this.log = (options == null ? void 0 : options.debug) ? (arg) => { console.log(`\x1B[44mDBG: Assistant-Swarm\x1B[0m`, arg); } : (_) => null; this.logGroup = (options == null ? void 0 : options.debug) ? (title, end) => { if (end) { console.groupEnd(); return; } console.group(title); } : (_, __) => null; } isReady() { if (!this.ready) throw new Error(`SwarmManager requires .init() to be called before use.`); return true; } async findOrUpsertManager() { var _a, _b, _c; const assistants = await _allAssistants(this.assistants, this.log); const mgrAssistant = assistants.find((a) => a.name === this._mgrBotName); const availableAssistants = assistants.filter( (a) => a.name !== this._mgrBotName ); const newOpts = { name: ((_a = this.options.managerAssistantOptions) == null ? void 0 : _a.name) || DEFAULT_OPTS.managerAssistantOptions.name, model: ((_b = this.options.managerAssistantOptions) == null ? void 0 : _b.model) || DEFAULT_OPTS.managerAssistantOptions.model, instructions: ((_c = this.options.managerAssistantOptions) == null ? void 0 : _c.instructions) || `You are a manager of assistants that all perform various functions. Your job is to select the best or top assistants under your direction to complete a job. If you do not have the assistant or ability to fulfill the required request by the user respond with the agent_id of '<none>'. If you respond with the agent_id of '<none>' tell the user that you are sorry and cannot process their request as the required assistant to do so is not available currently. It is very important that you get this right and always have the end user satisfied with your choice of assistant calls and end result. Each time you delegate you should reword the task to the assistant in terms and actions that is specific to that assistant's details and role. You will NEVER directly respond to the user input and will instead acknowledge their needs and announce that you will relay this request to another assistant if not <none>, who can best handle their needs.`, tools: toolsFromAssistants(availableAssistants) }; if (!!mgrAssistant) { this.log( "Primary Swarm Manager already exists - upserting new options..." ); const updatedMgr = await this.assistants.update(mgrAssistant.id, newOpts); this.mgrAssistant = updatedMgr; this.knownAssistantIds = availableAssistants.map((a) => a.id); return !!mgrAssistant; } const newAssistant = await this.assistants.create(newOpts); this.log("New Primary Swarm Manager was created!"); this.mgrAssistant = newAssistant; this.knownAssistantIds = availableAssistants.map((a) => a.id); return !!newAssistant; } async delegateTaskToChildren(primaryRun) { if (primaryRun.status !== "requires_action") { const textResponse = await runMessage(this.client, this.log, primaryRun); this.emitEvent("poll_event", { data: { status: "parent_run_concluded", textResponse, playground: this.playgroundLink(primaryRun), run: primaryRun } }); this.emitEvent("parent_assistant_complete", { data: { parentRun: __spreadProps(__spreadValues({}, primaryRun), { playground: this.playgroundLink(primaryRun), textResponse }) } }); this.emitEvent("child_assistants_complete", { data: { subRuns: [] } }); this.emitEvent("poll_event", { data: { status: "DONE" } }); return { concludedPrimaryRun: __spreadProps(__spreadValues({}, primaryRun), { playground: this.playgroundLink(primaryRun), textResponse }), subRuns: [] }; } const toolCalls = compressToolCalls(this, primaryRun); const uniqueAgents = /* @__PURE__ */ new Set(); const toolOutputs = []; const subRunRequests = []; const messageHistory = await messageHistoryForThread( this.client, this.log, primaryRun.thread_id ); for (const toolCall of toolCalls) { if (toolCall.agentId === "<none>") { toolOutputs.push({ tool_call_id: toolCall.id, output: [ { originatingToolCallId: toolCall.id, delegatedTo: "nobody", viaFunc: toolCall.function } ] }); continue; } uniqueAgents.add(toolCall.agentId); toolOutputs.push({ tool_call_id: toolCall.id, output: [ { originatingToolCallId: toolCall.id, delegatedTo: toolCall.agentId, viaFunc: toolCall.function } ] }); subRunRequests.push(() => { return new Promise(async (resolve) => { try { const swarmAssistant = new SwarmAssistant(this); await swarmAssistant.runDelegatedTask(primaryRun, toolCall, messageHistory).then((runInfo) => { resolve(runInfo); }).catch((e) => { this.log(e); resolve({ status: "failed", parentRun: primaryRun, parentToolCallId: toolCall.id, abortReason: e.message, playground: null, textResponse: null, assistant: null, thread: null, run: null }); }); } catch (e) { this.log(e); resolve({ status: "failed", parentRun: primaryRun, parentToolCallId: toolCall.id, abortReason: e.message, playground: null, textResponse: null, assistant: null, thread: null, run: null }); } }); }); } const subRunsPromise = () => { return new Promise(async (resolve) => { this.log( `Fanning out ${subRunRequests.length} tool executions for ${uniqueAgents.size} agents across swarm.` ); await Promise.all(subRunRequests.flat().map((fn) => fn())).then( (results) => { this.emitEvent("child_assistants_complete", { data: { subRuns: results } }); resolve(results); } ); }); }; const concludedPrimaryRunPromise = () => { return new Promise(async (resolve) => { const _runClosedRun = await this.client.beta.threads.runs.submitToolOutputs( primaryRun.thread_id, primaryRun.id, { tool_outputs: deDupeToolOutputs(toolOutputs) } ); const concludedPrimaryRun2 = await pollRun( this.client, this.logGroup, this.log, _runClosedRun ); const textResponse = await runMessage( this.client, this.log, concludedPrimaryRun2 ); this.emitEvent("poll_event", { data: { status: "parent_assistant_complete", playground: this.playgroundLink(concludedPrimaryRun2), textResponse, run: concludedPrimaryRun2 } }); this.emitEvent("parent_assistant_complete", { data: { parentRun: __spreadProps(__spreadValues({}, concludedPrimaryRun2), { playground: this.playgroundLink(concludedPrimaryRun2), textResponse }) } }); resolve(__spreadProps(__spreadValues({}, concludedPrimaryRun2), { playground: this.playgroundLink(concludedPrimaryRun2), textResponse })); }); }; const [concludedPrimaryRun, subRuns] = await Promise.all([ concludedPrimaryRunPromise(), subRunsPromise() ]); this.log("Swarm Complete."); return { concludedPrimaryRun, subRuns }; } /** * Initializes your account with the primary assistant to execute and delegate to existing assistants */ async init() { this.log("Initialization of Swarm Manager is running."); const _ready = await this.findOrUpsertManager(); this.ready = _ready; } /** * Returns a list of all assistants connected to this OpenAI apiKey. */ async allAssistants() { this.isReady(); return (await _allAssistants(this.assistants, this.log)).filter( (a) => { var _a; return a.id !== ((_a = this.mgrAssistant) == null ? void 0 : _a.id); } ); } /** * Returns multiple assistants at once from multiple ids. */ async getAssistants(assistantIds = []) { this.isReady(); return (await _getAssistants(this.assistants, this.log, assistantIds)).filter((a) => { var _a; return a.id !== ((_a = this.mgrAssistant) == null ? void 0 : _a.id); }); } /** * Cleanup and remove primary swarm assistant manager from your account. * Only removes the one created with this script using the params defined during creation of class. */ async cleanup() { this.isReady(); if (!this.mgrAssistant) return; await this.assistants.del(this.mgrAssistant.id); this.log( `Primary swarm assistant manager was deleted from OpenAI account.` ); return; } /** * Emit informative event from the swarm process running. */ emitEvent(event, args) { this.emitter.emit(event, args); } /** * Generate the Playground link for an assistant and thread for visualization in the browser. */ playgroundLink(run) { if (!run) return null; return `https://platform.openai.com/playground?assistant=${run.assistant_id}&mode=assistant&thread=${run.thread_id}`; } /** * Given a single prompt we will create a thread and then find the best option * for assistant execution to complete or fulfill the task. */ async delegateWithPrompt(prompt, assistantIds = ["<any>"], runOpts) { this.isReady(); const thread = await this.client.beta.threads.create({ messages: [{ role: "user", content: prompt }], metadata: { createdBy: "@mintplex-labs/openai-assistant-swarm" } }); if (!thread) throw new Error("Failed to create thread."); const mgrAssistant = this.mgrAssistant; const assistants = await this.getAssistants(assistantIds); this.knownAssistantIds = assistants.map((a) => a.id); const run = await this.client.beta.threads.runs.create(thread.id, __spreadValues({ assistant_id: mgrAssistant.id, tools: toolsFromAssistants(assistants), instructions: `${mgrAssistant.instructions} Your available assistants and their descriptions are presented between <assistant></assistant> tags with their id and descriptions. Only select assistants that are explicitly listed here. ${assistants.map((assistant) => { var _a; return `<assistant> <agent_id>${assistant.id}</agent_id> <name>${assistant.name}</name> <description>${(_a = assistant.description) != null ? _a : assistant.instructions}</description> </assistant>`; })},` }, !!runOpts ? __spreadValues({}, runOpts) : {})); this.log(`Run created: ${run.id}`); this.log({ parentThreadPlaygroundLink: this.playgroundLink(run) }); this.emitEvent("poll_event", { data: { status: "parent_run_created", prompt, playground: this.playgroundLink(run), run } }); const settledManagerRun = await pollRun( this.client, this.logGroup, this.log, run ); return await this.delegateTaskToChildren(settledManagerRun); } }; // src/index.ts function EnableSwarmAbilities(client, options) { if (!client.hasOwnProperty("beta")) throw new Error("Beta submodule does not exist on client!"); if (!client.beta.hasOwnProperty("assistants")) throw new Error("Beta Assistant submodule does not exist on client!"); client.beta.assistants.swarm = new SwarmManager(client, options); return client; } export { EnableSwarmAbilities }; //# sourceMappingURL=index.mjs.map