openai-swarmjs
Version:
Agentic framework inspired from OpenAI's swarm framework for TS, JS
240 lines (239 loc) • 10.7 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Swarm = void 0;
const openai_1 = __importDefault(require("openai"));
const debug_1 = require("../utils/debug");
const function_parser_1 = require("../utils/function-parser");
const merge_1 = require("../utils/merge");
const CTX_VARS_NAME = 'contextVariables';
const DEBUG = process.env.DEBUG === 'true';
class Swarm {
constructor(client) {
this.client = client || new openai_1.default();
}
async getChatCompletion(agent, history, contextVariables, modelOverride, stream, debug) {
const instructions = typeof agent.instructions === 'function'
? agent.instructions(contextVariables)
: agent.instructions;
const messages = [
{ role: 'system', content: instructions },
...history
];
(0, debug_1.debugPrint)(debug, 'Getting chat completion for...:', messages);
const tools = agent.functions.map(f => {
// Get the tool with guaranteed parameter structure
const rawTool = (0, function_parser_1.functionToJson)(f);
const tool = rawTool;
// Type-safe parameter manipulation
const properties = tool.function.parameters.properties;
// Remove context variables if they exist
if (CTX_VARS_NAME in properties) {
delete properties[CTX_VARS_NAME];
}
// Handle required parameters
if (Array.isArray(tool.function.parameters.required)) {
const required = tool.function.parameters.required;
const contextVarIndex = required.indexOf(CTX_VARS_NAME);
if (contextVarIndex !== -1) {
required.splice(contextVarIndex, 1);
if (required.length === 0) {
delete tool.function.parameters.required;
}
}
}
return tool;
});
const createParams = {
model: modelOverride || agent.model,
messages,
tools: tools.length > 0 ? tools : undefined,
stream
};
// Handle tool_choice based on OpenAI's expected types
if (tools.length > 0) {
createParams.tool_choice = (agent.toolChoice ?? 'auto');
}
else {
createParams.tool_choice = 'none';
}
return this.client.chat.completions.create(createParams);
}
handleFunctionResult(result, debug) {
// Case 1: Already a Result object
if (result && typeof result === 'object' && 'value' in result && 'agent' in result && 'contextVariables' in result) {
return result;
}
// Case 2: Agent object
if (result && typeof result === 'object' && 'name' in result && 'model' in result) {
return {
value: JSON.stringify({ assistant: result.name }),
agent: result,
contextVariables: {}
};
}
// Case 3: Handle other types with error checking
try {
return {
value: String(result),
agent: null,
contextVariables: {}
};
}
catch (e) {
const errorMessage = `Failed to cast response to string: ${result}. Make sure agent functions return a string or Result object. Error: ${e}`;
(0, debug_1.debugPrint)(debug, errorMessage);
throw new TypeError(errorMessage);
}
}
async handleToolCalls(toolCalls, functions, contextVariables, debug) {
const functionMap = new Map(functions.map(f => [f.name, f]));
const partialResponse = {
messages: [],
agent: null,
contextVariables: {}
};
for (const toolCall of toolCalls) {
toolCall.id = toolCall.id.substring(0, 40);
console.log(`Tool call: ${JSON.stringify(toolCall)}`);
// Add safe parsing of arguments
const funName = toolCall.function.name;
//debugPrint(debug, `Tool call name: ${funName}`);
console.log(`Tool call name: ${funName}`);
if (!functionMap.has(funName)) {
(0, debug_1.debugPrint)(debug, `Tool ${funName} not found in function map.`);
partialResponse.messages.push({
role: 'tool',
tool_call_id: toolCall.id.substring(0, 40),
name: funName,
content: `Error: Tool ${funName} not found.`
});
continue;
}
const args = JSON.parse(toolCall.function.arguments);
//debugPrint(debug, `Processing tool call: ${funName} with arguments`, args);
console.log(`Processing tool call: ${funName} with arguments`, args);
const func = functionMap.get(funName);
if (func.toString().includes(CTX_VARS_NAME)) {
args[CTX_VARS_NAME] = contextVariables;
}
console.log(`Calling function: ${func.name} with arguments: ${JSON.stringify(args)}`);
const rawResult = await Promise.resolve(func(...Object.values(args)));
const result = this.handleFunctionResult(rawResult, debug);
console.log(`Tool call result: ${JSON.stringify(result)}`);
partialResponse.messages.push({
role: 'tool',
tool_call_id: toolCall.id.substring(0, 40),
name: funName,
content: result.value,
});
partialResponse.contextVariables = {
...partialResponse.contextVariables,
...result.contextVariables
};
if (result.agent) {
partialResponse.agent = result.agent;
}
}
return partialResponse;
}
async *runAndStream(agent, messages, contextVariables = {}, modelOverride = null, debug = false, maxTurns = Infinity, executeTools = true) {
let activeAgent = agent;
const ctxVars = { ...contextVariables };
const history = [...messages];
const initLen = messages.length;
while (history.length - initLen < maxTurns) {
const message = {
content: '',
sender: agent.name,
role: 'assistant',
function_call: null,
tool_calls: {}
};
const completion = await this.getChatCompletion(activeAgent, history, ctxVars, modelOverride, true, debug);
yield { delim: 'start' };
for await (const chunk of completion) {
const delta = chunk.choices[0].delta;
if (delta.role === 'assistant') {
delta.sender = activeAgent.name;
}
yield delta;
(0, merge_1.mergeChunk)(message, delta);
}
yield { delim: 'end' };
message.tool_calls = Object.values(message.tool_calls);
if (!message.tool_calls.length) {
message.tool_calls = null;
}
(0, debug_1.debugPrint)(debug, 'Received completion:', message);
history.push(message);
if (!message.tool_calls || !executeTools) {
if ('shouldTransferManually' in activeAgent) {
const agent = activeAgent;
if (agent.updateLastResponse) {
agent.updateLastResponse(message.content || '');
}
if (agent.shouldTransferManually()) {
(0, debug_1.debugPrint)(debug, 'No tool calls, but manual transfer is required');
const nextAgent = await agent.nextAgent();
if (nextAgent) {
(0, debug_1.debugPrint)(debug, 'Transferring to next agent manually');
activeAgent = nextAgent;
continue;
}
}
}
(0, debug_1.debugPrint)(debug, 'Ending turn.');
break;
}
console.log(`Raw tool calls>>>>>>>: ${JSON.stringify(message.tool_calls)}`);
const partialResponse = await this.handleToolCalls(message.tool_calls, activeAgent.functions, ctxVars, debug);
history.push(...partialResponse.messages);
Object.assign(ctxVars, partialResponse.contextVariables);
if (partialResponse.agent) {
activeAgent = partialResponse.agent;
}
}
yield {
response: {
messages: history.slice(initLen),
agent: activeAgent,
contextVariables: ctxVars
}
};
}
async run(agent, messages, contextVariables = {}, modelOverride = null, stream = false, debug = false, maxTurns = Infinity, executeTools = true) {
if (stream) {
return this.runAndStream(agent, messages, contextVariables, modelOverride, debug, maxTurns, executeTools);
}
let activeAgent = agent;
const ctxVars = { ...contextVariables };
const history = [...messages];
const initLen = messages.length;
while (history.length - initLen < maxTurns && activeAgent) {
const completion = await this.getChatCompletion(activeAgent, history, ctxVars, modelOverride, false, debug);
const message = completion.choices[0].message;
(0, debug_1.debugPrint)(debug, 'Received completion:', message);
message.sender = activeAgent.name;
history.push(JSON.parse(JSON.stringify(message)));
if (!message.tool_calls || !executeTools) {
(0, debug_1.debugPrint)(debug, 'Ending turn.');
break;
}
const partialResponse = await this.handleToolCalls(message.tool_calls, activeAgent.functions, ctxVars, debug);
history.push(...partialResponse.messages);
Object.assign(ctxVars, partialResponse.contextVariables);
if (partialResponse.agent) {
activeAgent = partialResponse.agent;
}
}
return {
messages: history.slice(initLen),
agent: activeAgent,
contextVariables: ctxVars
};
}
}
exports.Swarm = Swarm;
;