@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 • 13.4 kB
Source Map (JSON)
{"version":3,"file":"openai-adapter.mjs","names":[],"sources":["../../../src/service-adapters/openai/openai-adapter.ts"],"sourcesContent":["/**\n * Copilot Runtime adapter for OpenAI.\n *\n * ## Example\n *\n * ```ts\n * import { CopilotRuntime, OpenAIAdapter } 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>\", // optional\n * apiKey: \"<your-api-key>\",\n * });\n *\n * return new OpenAIAdapter({ openai });\n * ```\n *\n * ## Example with Azure OpenAI\n *\n * ```ts\n * import { CopilotRuntime, OpenAIAdapter } from \"@copilotkit/runtime\";\n * import OpenAI from \"openai\";\n *\n * // The name of your Azure OpenAI Instance.\n * // https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource\n * const instance = \"<your instance name>\";\n *\n * // Corresponds to your Model deployment within your OpenAI resource, e.g. my-gpt35-16k-deployment\n * // Navigate to the Azure OpenAI Studio to deploy a model.\n * const model = \"<your model>\";\n *\n * const apiKey = process.env[\"AZURE_OPENAI_API_KEY\"];\n * if (!apiKey) {\n * throw new Error(\"The AZURE_OPENAI_API_KEY environment variable is missing or empty.\");\n * }\n *\n * const copilotKit = new CopilotRuntime();\n *\n * const openai = new OpenAI({\n * apiKey,\n * baseURL: `https://${instance}.openai.azure.com/openai/deployments/${model}`,\n * defaultQuery: { \"api-version\": \"2024-04-01-preview\" },\n * defaultHeaders: { \"api-key\": apiKey },\n * });\n *\n * return new OpenAIAdapter({ openai });\n * ```\n */\nimport type { LanguageModel } from \"ai\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport type OpenAI from \"openai\";\nimport Openai from \"openai\";\nimport {\n CopilotServiceAdapter,\n CopilotRuntimeChatCompletionRequest,\n CopilotRuntimeChatCompletionResponse,\n} from \"../service-adapter\";\nimport {\n convertActionInputToOpenAITool,\n convertMessageToOpenAIMessage,\n limitMessagesToTokenCount,\n getChatCompletionsForStreaming,\n} from \"./utils\";\nimport { randomUUID } from \"@copilotkit/shared\";\nimport { convertServiceAdapterError, getSdkClientOptions } from \"../shared\";\n\nconst DEFAULT_MODEL = \"gpt-4o\";\n\nexport interface OpenAIAdapterParams {\n /**\n * An optional OpenAI instance to use. If not provided, a new instance will be\n * created.\n */\n openai?: OpenAI;\n\n /**\n * The model to use.\n */\n model?: string;\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 /**\n * Optional maximum input token limit. Overrides the default model-based limit\n * used when trimming messages to fit the context window.\n */\n maxInputTokens?: number;\n}\n\nexport class OpenAIAdapter implements CopilotServiceAdapter {\n public model: string = DEFAULT_MODEL;\n public provider = \"openai\";\n\n private disableParallelToolCalls: boolean = false;\n private _openai: OpenAI;\n private keepSystemRole: boolean = false;\n private maxInputTokens?: number;\n\n public get openai(): OpenAI {\n return this._openai;\n }\n public get name() {\n return \"OpenAIAdapter\";\n }\n\n constructor(params?: OpenAIAdapterParams) {\n if (params?.openai) {\n this._openai = params.openai;\n }\n // If no instance provided, we'll lazy-load in ensureOpenAI()\n\n if (params?.model) {\n this.model = params.model;\n }\n this.disableParallelToolCalls = params?.disableParallelToolCalls || false;\n this.keepSystemRole = params?.keepSystemRole ?? false;\n this.maxInputTokens = params?.maxInputTokens;\n }\n\n getLanguageModel(): LanguageModel {\n const openai = this.ensureOpenAI();\n const options = getSdkClientOptions(openai);\n const provider = createOpenAI({\n baseURL: openai.baseURL,\n apiKey: openai.apiKey,\n organization: openai.organization ?? undefined,\n project: openai.project ?? undefined,\n headers: options.defaultHeaders,\n fetch: options.fetch,\n });\n return provider(this.model);\n }\n\n private ensureOpenAI(): OpenAI {\n if (!this._openai) {\n this._openai = new Openai();\n }\n return this._openai;\n }\n\n async process(\n request: CopilotRuntimeChatCompletionRequest,\n ): Promise<CopilotRuntimeChatCompletionResponse> {\n const {\n threadId: threadIdFromRequest,\n model = this.model,\n messages,\n actions,\n eventSource,\n forwardedParameters,\n } = request;\n const tools = actions.map(convertActionInputToOpenAITool);\n const threadId = threadIdFromRequest ?? randomUUID();\n\n // ALLOWLIST APPROACH: Only include tool_result messages that correspond to valid tool_calls\n // Step 1: Extract valid tool_call IDs\n const validToolUseIds = new Set<string>();\n\n for (const message of messages) {\n if (message.isActionExecutionMessage()) {\n validToolUseIds.add(message.id);\n }\n }\n\n // Step 2: Filter messages, keeping only those with valid tool_call IDs\n const filteredMessages = messages.filter((message) => {\n if (message.isResultMessage()) {\n // Skip if there's no corresponding tool_call\n if (!validToolUseIds.has(message.actionExecutionId)) {\n return false;\n }\n\n // Remove this ID from valid IDs so we don't process duplicates\n validToolUseIds.delete(message.actionExecutionId);\n return true;\n }\n\n // Keep all non-tool-result messages\n return true;\n });\n\n let openaiMessages = filteredMessages.map((m) =>\n convertMessageToOpenAIMessage(m, { keepSystemRole: this.keepSystemRole }),\n );\n openaiMessages = limitMessagesToTokenCount(\n openaiMessages,\n tools,\n model,\n this.maxInputTokens,\n );\n\n let toolChoice: any = forwardedParameters?.toolChoice;\n if (forwardedParameters?.toolChoice === \"function\") {\n toolChoice = {\n type: \"function\",\n function: { name: forwardedParameters.toolChoiceFunctionName },\n };\n }\n\n try {\n const openai = this.ensureOpenAI();\n const completions = getChatCompletionsForStreaming(openai);\n const stream = completions.stream({\n model: model,\n stream: true,\n messages: openaiMessages,\n ...(tools.length > 0 && { tools }),\n ...(forwardedParameters?.maxTokens && {\n max_completion_tokens: forwardedParameters.maxTokens,\n }),\n ...(forwardedParameters?.stop && { stop: forwardedParameters.stop }),\n ...(toolChoice && { tool_choice: toolChoice }),\n ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),\n ...(forwardedParameters?.temperature && {\n temperature: forwardedParameters.temperature,\n }),\n });\n\n eventSource.stream(async (eventStream$) => {\n let mode: \"function\" | \"message\" | null = null;\n let currentMessageId: string;\n let currentToolCallId: string;\n\n try {\n for await (const chunk of stream) {\n if (chunk.choices.length === 0) {\n continue;\n }\n\n const toolCall = chunk.choices[0].delta.tool_calls?.[0];\n const content = chunk.choices[0].delta.content;\n\n // When switching from message to function or vice versa,\n // send the respective end event.\n // If toolCall?.id is defined, it means a new tool call starts.\n if (mode === \"message\" && toolCall?.id) {\n mode = null;\n eventStream$.sendTextMessageEnd({ messageId: currentMessageId });\n } else if (\n mode === \"function\" &&\n (toolCall === undefined || toolCall?.id)\n ) {\n mode = null;\n eventStream$.sendActionExecutionEnd({\n actionExecutionId: currentToolCallId,\n });\n }\n\n // If we send a new message type, send the appropriate start event.\n if (mode === null) {\n if (toolCall?.id) {\n mode = \"function\";\n currentToolCallId = toolCall!.id;\n eventStream$.sendActionExecutionStart({\n actionExecutionId: currentToolCallId,\n parentMessageId: chunk.id,\n actionName: toolCall!.function!.name,\n });\n } else if (content) {\n mode = \"message\";\n currentMessageId = chunk.id;\n eventStream$.sendTextMessageStart({\n messageId: currentMessageId,\n });\n }\n }\n\n // send the content events\n if (mode === \"message\" && content) {\n eventStream$.sendTextMessageContent({\n messageId: currentMessageId,\n content: content,\n });\n } else if (mode === \"function\" && toolCall?.function?.arguments) {\n eventStream$.sendActionExecutionArgs({\n actionExecutionId: currentToolCallId,\n args: toolCall.function.arguments,\n });\n }\n }\n\n // send the end events\n if (mode === \"message\") {\n eventStream$.sendTextMessageEnd({ messageId: currentMessageId });\n } else if (mode === \"function\") {\n eventStream$.sendActionExecutionEnd({\n actionExecutionId: currentToolCallId,\n });\n }\n } catch (error) {\n console.error(\"[OpenAI] Error during API call:\", error);\n throw convertServiceAdapterError(error, \"OpenAI\");\n }\n\n eventStream$.complete();\n });\n } catch (error) {\n console.error(\"[OpenAI] Error during API call:\", error);\n throw convertServiceAdapterError(error, \"OpenAI\");\n }\n\n return {\n threadId,\n };\n }\n}\n"],"mappings":"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"}