UNPKG

node-agency

Version:
445 lines (387 loc) 12.8 kB
import OpenAI from "openai"; import { callFunction, readableStreamAsyncIterable } from "../utils"; import { Logger } from "../logger"; function isParseableJson(str: string) { try { return JSON.parse(str); } catch (e) { return null; } } type OpenAIParams = OpenAI.Chat.Completions.ChatCompletionCreateParams.ChatCompletionCreateParamsNonStreaming; type Messages = OpenAIParams["messages"]; type Message = Messages[0]; export class Model { history: Messages = []; openai: OpenAI; selfReflected: number = 0; parallelToolCalls = false; isManager = false; model: OpenAI.Chat.Completions.ChatCompletionCreateParams["model"] = "gpt-3.5-turbo"; selfReflect: boolean = true; constructor(options?: { parallelToolCalls?: boolean; OPENAI_API_KEY?: string; model?: OpenAI.Chat.Completions.ChatCompletionCreateParams["model"]; selfReflect?: boolean; }) { const { parallelToolCalls, OPENAI_API_KEY, model, selfReflect } = options || {}; const openai = new OpenAI({ apiKey: OPENAI_API_KEY || process.env.OPENAI_API_KEY, }); this.openai = openai; this.parallelToolCalls = parallelToolCalls || false; this.model = model || this.model; this.selfReflect = selfReflect ?? true; } async call( systemMessage: string, prompt: Message, tools?: OpenAI.Chat.Completions.ChatCompletionTool[], context?: string ): Promise<string> { prompt.content = prompt.content + (context ? "\n\n## This is results from your coworkers to help you with your task:\n" + context : ""); // console.log("-----------------"); // console.log("Prompt: ", prompt.content); // console.log("-----------------"); // debugger; this.history.push(prompt); const messages: Messages = [ { role: "system", content: systemMessage, }, ...this.history, ]; try { const message = await this.callGPT(messages, tools); this.history.push({ role: "assistant", content: message.content, tool_calls: message.tool_calls, }); if (message.tool_calls) { if (message.content) { Logger({ type: "info", payload: "\n\n" + message.content + "\n\n", }); } const { tool_calls } = message; const toolMessagesResolved: OpenAI.Chat.Completions.ChatCompletionToolMessageParam[] = []; const coWorkerCalls = tool_calls.filter((tool_call) => { return ( tool_call.function.name === "delegate_task" || tool_call.function.name === "ask_question" ); }); if (this.parallelToolCalls && !this.isManager) { const filteredCalls = tool_calls.filter((tool_call) => { return ( tool_call.function.name !== "delegate_task" && tool_call.function.name !== "ask_question" ); }); const toolMessagePromises = filteredCalls.map(async (tool_call) => { return this.processingToolCall(tool_call); }); const toolMessagesSettled = await Promise.allSettled( toolMessagePromises ); for (const toolMessage of toolMessagesSettled) { if (toolMessage.status === "fulfilled") { toolMessagesResolved.push(toolMessage.value); } } for (const tool_call of coWorkerCalls) { const toolMessage = await this.processingToolCall(tool_call); toolMessagesResolved.push(toolMessage); } } else { for (const tool_call of tool_calls) { const toolMessage = await this.processingToolCall(tool_call); toolMessagesResolved.push(toolMessage); } } const allMessagesHasResvoled = tool_calls.every((message) => { return toolMessagesResolved.find( (toolMessage) => toolMessage.tool_call_id === message.id ); }); if (!allMessagesHasResvoled) { const missingToolCalls = tool_calls.filter((message) => { return !toolMessagesResolved.find((toolMessage) => { toolMessage.tool_call_id === message.id; }); }); throw new Error( "Failed to resolve all tool calls Missing: " + missingToolCalls .map( (message) => `Name: '${message.function.name}', ID:${message.id}` ) .join(", ") ); } const lastMessage = toolMessagesResolved[toolMessagesResolved.length - 1]; const allButLastMessage = toolMessagesResolved.slice( 0, toolMessagesResolved.length - 1 ); this.history.push(...allButLastMessage); return this.call(systemMessage, lastMessage, tools); } // if (message.content && !message.content.includes("<CONTINUE>")) { // const maxRuntime = new Date().getTime() + 1000 * 60 * 5; // let currentTime = new Date().getTime(); // let currentStep = "plan"; // while (currentStep === "plan" && currentTime < maxRuntime) { // const plan = await this.call( // systemMessage, // { // role: "user", // content: // "Plan your next steps, when you are ready, if there are no more steps to take then indicate you are done with <CONTINUE> at the very end of your response.", // }, // tools // ); // if (!plan.includes("<CONTINUE>")) { // message.content = plan; // currentTime = new Date().getTime(); // } else { // message.content = plan.replace("<CONTINUE>", ""); // currentStep = "execute"; // } // } // } return message.content || "Unknown Error Occurred, Please try again."; } catch (error) { console.error(error); throw new Error("Failed to call GPT-3"); } } async callStream( systemMessage: string, prompt: Message, callback: (message: string) => void, tools?: OpenAI.Chat.Completions.ChatCompletionTool[], context?: string ): Promise<AsyncIterableIterator<string>> { prompt.content = prompt.content + (context ? "\n\nHere is further context to help you with your task:\n" + context : ""); this.history.push(prompt); const messages: Messages = [ { role: "system", content: systemMessage, }, ...this.history, ]; try { const message = await this.callGPTStream(messages, callback, tools); return message; } catch (error) { console.error(error); throw new Error("Failed to call GPT-3"); } } async processingToolCall( tool_call: OpenAI.Chat.Completions.ChatCompletionMessageToolCall ) { const { name, arguments: args } = tool_call.function; Logger({ type: "function", payload: JSON.stringify({ name, params: args, }), }); const result = await callFunction(name, args); const toolMessage: Message = { role: "tool", tool_call_id: tool_call.id, content: JSON.stringify({ result }), }; return toolMessage; } async callGPT( messages: Messages, tools?: OpenAI.Chat.Completions.ChatCompletionTool[], reflected: boolean = false ): Promise<OpenAI.Chat.Completions.ChatCompletionMessage> { try { const gptResponse = await this.openai.chat.completions.create({ model: this.model, messages, tools: tools, stream: false, }); const { choices: [reply], usage, } = gptResponse; const { message } = reply; if (this.selfReflect && reflected && this.selfReflected >= 3) { Logger({ type: "warn", payload: "Self-Reflection Limit Reached\n\n" }); } if ( this.selfReflect && !reflected && message.content && this.selfReflected < 3 && !message.tool_calls ) { Logger({ type: "info", payload: `Self-Reflecting On Output (${this.selfReflected})...\n\n`, }); this.selfReflected++; return this.callGPT( [ ...messages, message, { role: "user", content: "Reflect on your response, find ways to improve it, respond with only the improved version, with no mention of the reflection process, or changes made.", }, ], tools, true ); } return message; } catch (error) { console.error(error); console.debug("History: ", this.history); throw new Error("Failed to call GPT-3"); } } async callGPTStream( messages: Messages, callback: (message: string) => void, tools?: OpenAI.Chat.Completions.ChatCompletionTool[] ) { const gptResponse = await this.openai.chat.completions.create({ model: this.model, messages, tools: tools, stream: true, }); const _this = this; const toolCalls: Array<{ id: string; type: "function"; function: { name: string; arguments: string; }; }> = []; const toolMessages: Message[] = []; const stream: ReadableStream<any> = new ReadableStream({ async start(controller) { let currentMessage = ""; for await (const value of gptResponse) { const choice = value.choices[0]; const delta = choice.delta; if (delta.tool_calls != null) { for (const toolCallDelta of delta.tool_calls) { const index = toolCallDelta.index; if (toolCalls[index] == null) { if (toolCallDelta.type !== "function") { continue; } if (toolCallDelta.id == null) { continue; } if (toolCallDelta.function?.name == null) { continue; } if ( toolCallDelta.function && toolCallDelta.id && toolCallDelta.function.name ) { toolCalls[index] = { id: toolCallDelta.id, type: "function", function: { name: toolCallDelta.function.name, arguments: toolCallDelta.function.arguments ?? "", }, }; } continue; } const toolCall = toolCalls[index]; if (toolCallDelta.function?.arguments != null) { toolCall.function!.arguments += toolCallDelta.function?.arguments ?? ""; } // check if tool call is complete if ( toolCall.function?.name == null || toolCall.function?.arguments == null || !isParseableJson(toolCall.function.arguments) ) { continue; } Logger({ type: "function", payload: JSON.stringify({ name: toolCall.function.name, params: toolCall.function.arguments, }), }); const toolMessage = await _this.processingToolCall(toolCall); const toolRequestMessage: Message = { role: "assistant", content: null, tool_calls: [toolCall], }; toolMessages.push(toolRequestMessage, toolMessage); continue; } } else if (delta.content != null) { controller.enqueue(value.choices[0].delta.content); currentMessage += value.choices[0].delta.content; } } if (currentMessage && !toolMessages.length) { _this.history.push({ role: "assistant", content: currentMessage, }); callback(currentMessage); } if (toolMessages.length) { _this.history.push(...toolMessages); const newStream = await _this.callGPTStream( _this.history, callback, tools ); for await (const newPart of newStream) { controller.enqueue(newPart); } } controller.close(); }, }); return readableStreamAsyncIterable<string>(stream); } }