UNPKG

@copilotkit/runtime

Version:

<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />

1 lines • 15.7 kB
{"version":3,"file":"openai-assistant-adapter.cjs","names":["retrieveThreadRun","submitToolOutputsStream","convertMessageToOpenAIMessage","convertSystemMessageToAssistantAPI","convertActionInputToOpenAITool"],"sources":["../../../src/service-adapters/openai/openai-assistant-adapter.ts"],"sourcesContent":["/**\n * Copilot Runtime adapter for the OpenAI Assistant API.\n *\n * ## Example\n *\n * ```ts\n * import { CopilotRuntime, OpenAIAssistantAdapter } from \"@copilotkit/runtime\";\n * import OpenAI from \"openai\";\n *\n * const copilotKit = new CopilotRuntime();\n *\n * const openai = new OpenAI({\n * organization: \"<your-organization-id>\",\n * apiKey: \"<your-api-key>\",\n * });\n *\n * return new OpenAIAssistantAdapter({\n * openai,\n * assistantId: \"<your-assistant-id>\",\n * codeInterpreterEnabled: true,\n * fileSearchEnabled: true,\n * });\n * ```\n */\nimport type OpenAI from \"openai\";\nimport type { RunSubmitToolOutputsStreamParams } from \"openai/resources/beta/threads/runs/runs\";\nimport type { AssistantStream } from \"openai/lib/AssistantStream\";\nimport type {\n AssistantStreamEvent,\n AssistantTool,\n} from \"openai/resources/beta/assistants\";\nimport {\n CopilotServiceAdapter,\n CopilotRuntimeChatCompletionRequest,\n CopilotRuntimeChatCompletionResponse,\n} from \"../service-adapter\";\nimport {\n Message,\n ResultMessage,\n TextMessage,\n} from \"../../graphql/types/converted\";\nimport {\n convertActionInputToOpenAITool,\n convertMessageToOpenAIMessage,\n convertSystemMessageToAssistantAPI,\n retrieveThreadRun,\n submitToolOutputsStream,\n} from \"./utils\";\nimport { RuntimeEventSource } from \"../events\";\nimport { ActionInput } from \"../../graphql/inputs/action.input\";\nimport { ForwardedParametersInput } from \"../../graphql/inputs/forwarded-parameters.input\";\n\nexport interface OpenAIAssistantAdapterParams {\n /**\n * The ID of the assistant to use.\n */\n assistantId: string;\n\n /**\n * An optional OpenAI instance to use. If not provided, a new instance will be created.\n */\n openai?: OpenAI;\n\n /**\n * Whether to enable code interpretation.\n * @default true\n */\n codeInterpreterEnabled?: boolean;\n\n /**\n * Whether to enable file search.\n * @default true\n */\n fileSearchEnabled?: boolean;\n\n /**\n * Whether to disable parallel tool calls.\n * You can disable parallel tool calls to force the model to execute tool calls sequentially.\n * This is useful if you want to execute tool calls in a specific order so that the state changes\n * introduced by one tool call are visible to the next tool call. (i.e. new actions or readables)\n *\n * @default false\n */\n disableParallelToolCalls?: boolean;\n\n /**\n * Whether to keep the role in system messages as \"System\".\n * By default, it is converted to \"developer\", which is used by newer OpenAI models\n *\n * @default false\n */\n keepSystemRole?: boolean;\n}\n\nexport class OpenAIAssistantAdapter implements CopilotServiceAdapter {\n private _openai: OpenAI;\n private codeInterpreterEnabled: boolean;\n private assistantId: string;\n private fileSearchEnabled: boolean;\n private disableParallelToolCalls: boolean;\n private keepSystemRole: boolean = false;\n\n public get name() {\n return \"OpenAIAssistantAdapter\";\n }\n\n constructor(params: OpenAIAssistantAdapterParams) {\n if (params.openai) {\n this._openai = params.openai;\n }\n // If no instance provided, we'll lazy-load in ensureOpenAI()\n this.codeInterpreterEnabled =\n params.codeInterpreterEnabled === false || true;\n this.fileSearchEnabled = params.fileSearchEnabled === false || true;\n this.assistantId = params.assistantId;\n this.disableParallelToolCalls = params?.disableParallelToolCalls || false;\n this.keepSystemRole = params?.keepSystemRole ?? false;\n }\n\n private ensureOpenAI(): OpenAI {\n if (!this._openai) {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const OpenAI = require(\"openai\").default;\n this._openai = new OpenAI({});\n }\n return this._openai;\n }\n\n async process(\n request: CopilotRuntimeChatCompletionRequest,\n ): Promise<CopilotRuntimeChatCompletionResponse> {\n const { messages, actions, eventSource, runId, forwardedParameters } =\n request;\n\n // if we don't have a threadId, create a new thread\n let threadId = request.extensions?.openaiAssistantAPI?.threadId;\n const openai = this.ensureOpenAI();\n\n if (!threadId) {\n threadId = (await openai.beta.threads.create()).id;\n }\n\n const lastMessage = messages.at(-1);\n\n let nextRunId: string | undefined = undefined;\n\n // submit function outputs\n if (lastMessage.isResultMessage() && runId) {\n nextRunId = await this.submitToolOutputs(\n threadId,\n runId,\n messages,\n eventSource,\n );\n }\n // submit user message\n else if (lastMessage.isTextMessage()) {\n nextRunId = await this.submitUserMessage(\n threadId,\n messages,\n actions,\n eventSource,\n forwardedParameters,\n );\n }\n // unsupported message\n else {\n throw new Error(\"No actionable message found in the messages\");\n }\n\n return {\n runId: nextRunId,\n threadId,\n extensions: {\n ...request.extensions,\n openaiAssistantAPI: {\n threadId: threadId,\n runId: nextRunId,\n },\n },\n };\n }\n\n private async submitToolOutputs(\n threadId: string,\n runId: string,\n messages: Message[],\n eventSource: RuntimeEventSource,\n ) {\n const openai = this.ensureOpenAI();\n let run = await retrieveThreadRun(openai, threadId, runId);\n\n if (!run.required_action) {\n throw new Error(\"No tool outputs required\");\n }\n\n // get the required tool call ids\n const toolCallsIds = run.required_action.submit_tool_outputs.tool_calls.map(\n (toolCall) => toolCall.id,\n );\n\n // search for these tool calls\n const resultMessages = messages.filter(\n (message) =>\n message.isResultMessage() &&\n toolCallsIds.includes(message.actionExecutionId),\n ) as ResultMessage[];\n\n if (toolCallsIds.length != resultMessages.length) {\n throw new Error(\n \"Number of function results does not match the number of tool calls\",\n );\n }\n\n // submit the tool outputs\n const toolOutputs: RunSubmitToolOutputsStreamParams.ToolOutput[] =\n resultMessages.map((message) => {\n return {\n tool_call_id: message.actionExecutionId,\n output: message.result,\n };\n });\n\n const stream = submitToolOutputsStream(openai, threadId, runId, {\n tool_outputs: toolOutputs,\n ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),\n });\n\n await this.streamResponse(stream, eventSource);\n return runId;\n }\n\n private async submitUserMessage(\n threadId: string,\n messages: Message[],\n actions: ActionInput[],\n eventSource: RuntimeEventSource,\n forwardedParameters: ForwardedParametersInput,\n ) {\n const openai = this.ensureOpenAI();\n messages = [...messages];\n\n // get the instruction message\n const instructionsMessage = messages.shift();\n const instructions = instructionsMessage.isTextMessage()\n ? instructionsMessage.content\n : \"\";\n\n // get the latest user message\n const userMessage = messages\n .map((m) =>\n convertMessageToOpenAIMessage(m, {\n keepSystemRole: this.keepSystemRole,\n }),\n )\n .map(convertSystemMessageToAssistantAPI)\n .at(-1);\n\n if (userMessage.role !== \"user\") {\n throw new Error(\"No user message found\");\n }\n\n await openai.beta.threads.messages.create(threadId, {\n role: \"user\",\n content: userMessage.content,\n });\n\n const openaiTools = actions.map(convertActionInputToOpenAITool);\n\n const tools = [\n ...openaiTools,\n ...(this.codeInterpreterEnabled\n ? [{ type: \"code_interpreter\" } as AssistantTool]\n : []),\n ...(this.fileSearchEnabled\n ? [{ type: \"file_search\" } as AssistantTool]\n : []),\n ];\n\n let stream = openai.beta.threads.runs.stream(threadId, {\n assistant_id: this.assistantId,\n instructions,\n tools: tools,\n ...(forwardedParameters?.maxTokens && {\n max_completion_tokens: forwardedParameters.maxTokens,\n }),\n ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),\n });\n\n await this.streamResponse(stream, eventSource);\n\n return getRunIdFromStream(stream);\n }\n\n private async streamResponse(\n stream: AssistantStream,\n eventSource: RuntimeEventSource,\n ) {\n eventSource.stream(async (eventStream$) => {\n let inFunctionCall = false;\n let currentMessageId: string;\n let currentToolCallId: string;\n\n for await (const chunk of stream) {\n switch (chunk.event) {\n case \"thread.message.created\":\n if (inFunctionCall) {\n eventStream$.sendActionExecutionEnd({\n actionExecutionId: currentToolCallId,\n });\n }\n currentMessageId = chunk.data.id;\n eventStream$.sendTextMessageStart({ messageId: currentMessageId });\n break;\n case \"thread.message.delta\":\n if (chunk.data.delta.content?.[0].type === \"text\") {\n eventStream$.sendTextMessageContent({\n messageId: currentMessageId,\n content: chunk.data.delta.content?.[0].text.value,\n });\n }\n break;\n case \"thread.message.completed\":\n eventStream$.sendTextMessageEnd({ messageId: currentMessageId });\n break;\n case \"thread.run.step.delta\":\n let toolCallId: string | undefined;\n let toolCallName: string | undefined;\n let toolCallArgs: string | undefined;\n if (\n chunk.data.delta.step_details.type === \"tool_calls\" &&\n chunk.data.delta.step_details.tool_calls?.[0].type === \"function\"\n ) {\n toolCallId = chunk.data.delta.step_details.tool_calls?.[0].id;\n toolCallName =\n chunk.data.delta.step_details.tool_calls?.[0].function.name;\n toolCallArgs =\n chunk.data.delta.step_details.tool_calls?.[0].function\n .arguments;\n }\n\n if (toolCallName && toolCallId) {\n if (inFunctionCall) {\n eventStream$.sendActionExecutionEnd({\n actionExecutionId: currentToolCallId,\n });\n }\n inFunctionCall = true;\n currentToolCallId = toolCallId;\n eventStream$.sendActionExecutionStart({\n actionExecutionId: currentToolCallId,\n parentMessageId: chunk.data.id,\n actionName: toolCallName,\n });\n } else if (toolCallArgs) {\n eventStream$.sendActionExecutionArgs({\n actionExecutionId: currentToolCallId,\n args: toolCallArgs,\n });\n }\n break;\n }\n }\n if (inFunctionCall) {\n eventStream$.sendActionExecutionEnd({\n actionExecutionId: currentToolCallId,\n });\n }\n eventStream$.complete();\n });\n }\n}\n\nfunction getRunIdFromStream(stream: AssistantStream): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n let runIdGetter = (event: AssistantStreamEvent) => {\n if (event.event === \"thread.run.created\") {\n const runId = event.data.id;\n stream.off(\"event\", runIdGetter);\n resolve(runId);\n }\n };\n stream.on(\"event\", runIdGetter);\n });\n}\n"],"mappings":"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"}