feat: Adds portkey plugin (#10337)

* feat: Adds portkey plugin

* Removes unused logs, safe parse json, refactor types

* Update title and desc of icon svg for accesibility

* Fixes variable overload causing crash

* Adds model to chat operation
This commit is contained in:
Sarath Kumar Somana 2024-09-18 11:27:58 +05:30 committed by GitHub
parent 0dd4cc9211
commit 69d25d9210
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 3189 additions and 1138 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
node_modules
lib/*.d.*
lib/*.js
lib/*.js.map
dist/*
.env
npm-debug.log*
coverage/

View file

@ -0,0 +1,4 @@
# Portkey
Documentation on: https://docs.tooljet.com/docs/data-sources/portkey

View file

@ -0,0 +1,8 @@
'use strict';
const portkey = require('../lib');
jest.mock('../lib');
describe('portkey', () => {
it.todo('needs tests');
});

View file

@ -0,0 +1,11 @@
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>Portkey Icon</title>
<desc>An icon representing the Portkey plugin</desc>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.5004 2.435C18.8792 2.435 13.7158 5.48676 10.9352 10.3901L2.08076 26.0036C-0.682154 30.8756 -0.694254 36.8659 2.04873 41.7492L10.9258 57.5532C13.6977 62.4882 18.8785 65.565 24.523 65.565H41.9664C47.5706 65.565 52.7212 62.5315 55.5081 57.6513L64.5329 41.8476C67.3502 36.9141 67.3379 30.8273 64.5002 25.9055L55.4981 10.2917L52.9864 11.7398L55.4981 10.2917C52.7028 5.44346 47.5699 2.435 41.9891 2.435H24.5004ZM17.0368 13.8503C18.5919 11.1081 21.4453 9.44944 24.5004 9.44944H41.9891C45.0224 9.44944 47.8584 11.0846 49.4213 13.7953L58.4234 29.4091C60.0159 32.1712 60.023 35.6 58.4417 38.3692L49.4169 54.1729C47.8585 56.9018 45.0124 58.5506 41.9664 58.5506H24.523C21.4553 58.5506 18.5919 56.8783 17.0415 54.118L8.16444 38.314C6.62469 35.5728 6.63161 32.1983 8.18236 29.4638L17.0368 13.8503ZM39.5946 19.2959C38.322 17.8356 36.1066 17.6834 34.6463 18.956C33.186 20.2285 33.0338 22.444 34.3063 23.9043L43.1041 34L34.3063 44.0957C33.0338 45.556 33.186 47.7715 34.6463 49.044C36.1066 50.3166 38.322 50.1644 39.5946 48.7041L49.3963 37.4563C51.1225 35.4754 51.1225 32.5246 49.3963 30.5437L39.5946 19.2959Z" fill="url(#paint0_linear_1035_155)"/>
<defs>
<linearGradient id="paint0_linear_1035_155" x1="-23.0034" y1="-2.89125" x2="73" y2="81.935" gradientUnits="userSpaceOnUse">
<stop offset="0.250068" stop-color="#39ACE6"/>
<stop offset="1" stop-color="#FF0000"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,69 @@
import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-marketplace/common';
import { SourceOptions, QueryOptions, Operation, TextCompletionQueryOptions, ChatCompletionQueryOptions, PromptCompletionQueryOptions, EmbeddingQueryOptions } from './types';
import * as PortKeyAi from 'portkey-ai';
import { createEmbedding, getChatCompletion, getCompletion, getPromptCompletion } from './portkey_operations';
export default class Portkey implements QueryService {
async run(sourceOptions: SourceOptions, queryOptions: QueryOptions, dataSourceId: string): Promise<QueryResult> {
const operation: Operation = queryOptions.operation;
const { virtualKey, config } = queryOptions;
if (virtualKey) {
sourceOptions.virtualKey = virtualKey;
}
if (config) {
sourceOptions.config = config;
}
const portkey: PortKeyAi.Portkey = await this.getConnection(sourceOptions);
let result = {};
try {
switch (operation) {
case Operation.Completion:
result = await getCompletion(portkey, queryOptions as TextCompletionQueryOptions);
break;
case Operation.Chat:
result = await getChatCompletion(portkey, queryOptions as ChatCompletionQueryOptions);
break;
case Operation.PromptCompletion:
result = await getPromptCompletion(portkey, queryOptions as PromptCompletionQueryOptions);
break;
case Operation.CreateEmbedding:
result = await createEmbedding(portkey, queryOptions as EmbeddingQueryOptions);
break;
default:
throw new QueryError('Query could not be completed', 'Invalid operation', {});
break;
}
} catch (error) {
throw new QueryError('Query could not be completed', error?.message, {});
}
return {
status: 'ok',
data: result,
};
}
async testConnection(sourceOptions: SourceOptions): Promise<ConnectionTestResult> {
const portkey: PortKeyAi.Portkey = await this.getConnection(sourceOptions);
try {
const response = await portkey.models.list();
console.log('response', response);
console.log('response.status', response.status);
if (response.data !== undefined) {
return {
status: 'ok',
};
}
} catch (error) {
throw new QueryError('Connection could not be established', error?.message, {});
}
}
async getConnection(sourceOptions: SourceOptions): Promise<PortKeyAi.Portkey> {
const { apiKey, virtualKey, config } = sourceOptions;
const creds = { apiKey, virtualKey };
if (config) {
creds['config'] = typeof config === 'string' ? JSON.parse(config) : null;
}
console.log('creds', creds);
return new PortKeyAi.Portkey(creds);
}
}

View file

@ -0,0 +1,64 @@
{
"$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json",
"title": "Portkey datasource",
"description": "A schema defining Portkey datasource",
"type": "api",
"source": {
"name": "Portkey",
"kind": "portkey",
"exposedVariables": {
"isLoading": false,
"data": {},
"rawData": {}
},
"options": {
"apiKey": {
"type": "string",
"encrypted": true
},
"virtualKey": {
"type": "string",
"encrypted": true
},
"config": {
"type": "string"
},
"gatewayUrl": {
"type": "string"
}
}
},
"defaults": {},
"properties": {
"apiKey": {
"label": "API Key",
"key": "apiKey",
"type": "password",
"description": "Enter your Portkey API Key",
"helpText": "How to obtain API Key: <a href='https://docs.portkey.ai/docs/api-reference/authentication#obtaining-your-api-key' target='_blank' rel='noreferrer'>See here</a>"
},
"virtualKey": {
"label": "Default Virtual Key",
"key": "virtualKey",
"type": "password",
"description": "Enter your default Portkey Virtual Key",
"helpText": "How to create Virtual Key: <a href='https://docs.portkey.ai/docs/product/ai-gateway-streamline-llm-integrations/virtual-keys#creating-virtual-keys' target='_blank' rel='noreferrer'>See here</a>"
},
"config": {
"label": "Config",
"key": "config",
"type": "text",
"description": "Enter your default Portkey config"
},
"gatewayUrl": {
"label": "Gateway URL",
"key": "gatewayUrl",
"type": "text",
"description": "Enter your default Portkey Gateway URL",
"helpText": "How to obtain Gateway URL: <a href='https://docs.portkey.ai/docs/api-reference/authentication#obtaining-your-api-key' target='_blank' rel='noreferrer'>See here</a>"
}
},
"required": [
"apiKey"
]
}

View file

@ -0,0 +1,324 @@
{
"$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json",
"title": "Portkey datasource",
"description": "A schema defining Portkey datasource",
"type": "api",
"defaults": {},
"properties": {
"operation": {
"label": "Operation",
"key": "operation",
"type": "dropdown-component-flip",
"description": "Single select dropdown for operation",
"list": [
{
"value": "completion",
"name": "Completion"
},
{
"value": "chat",
"name": "Chat"
},
{
"value": "prompt_completion",
"name": "Prompt Completion"
},
{
"value": "create_embedding",
"name": "Create Embedding"
}
]
},
"completion": {
"prompt": {
"label": "Prompt",
"key": "prompt",
"type": "codehinter",
"description": "Enter prompt",
"height": "150px"
},
"model": {
"label": "Model",
"key": "model",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter model",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"max_tokens": {
"label": "Max Tokens",
"key": "max_tokens",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter from 1 to 2048",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"temperature": {
"label": "Temperature",
"key": "temperature",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter from 0 to 1",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"stop_sequences": {
"label": "Stop Sequence",
"key": "stop_sequences",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter stop sequence",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"metadata": {
"label": "Metadata",
"key": "metadata",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter metadata",
"width": "320px",
"height": "120px",
"className": "codehinter-plugins"
},
"other_parameters": {
"label": "Other Parameters",
"key": "other_parameters",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter other parameters",
"width": "320px",
"height": "120px",
"className": "codehinter-plugins"
},
"config": {
"label": "Config",
"key": "config",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter config",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"virtualKey": {
"label": "Virtual Key",
"key": "virtualKey",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter virtual key",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
}
},
"chat": {
"messages": {
"label": "Message",
"key": "messages",
"type": "codehinter",
"description": "Enter messages",
"placeholder": "[{\"role\": \"user\", \"content\": \"Hello\"}, {\"role\": \"assistant\", \"content\": \"Hi\"]}",
"height": "150px"
},
"model": {
"label": "Model",
"key": "model",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter model",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"max_tokens": {
"label": "Max Tokens",
"key": "max_tokens",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter from 1 to 2048",
"placeholder": "256",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"temperature": {
"label": "Temperature",
"key": "temperature",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter from 0 to 1",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"stop_sequence": {
"label": "Stop Sequence",
"key": "stop_sequence",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter stop sequence",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"metadata": {
"label": "Metadata",
"key": "metadata",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter metadata",
"width": "320px",
"height": "120px",
"className": "codehinter-plugins"
},
"other_parameters": {
"label": "Other Parameters",
"key": "other_parameters",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter other parameters",
"width": "320px",
"height": "120px",
"className": "codehinter-plugins"
},
"config": {
"label": "Config",
"key": "config",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter config",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"virtualKey": {
"label": "Virtual Key",
"key": "virtualKey",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter virtual key",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
}
},
"prompt_completion": {
"promptId": {
"label": "Prompt ID",
"key": "promptId",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter prompt ID",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"variables": {
"label": "Variables",
"key": "variables",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter variables",
"width": "320px",
"height": "120px",
"className": "codehinter-plugins"
},
"metadata": {
"label": "Metadata",
"key": "metadata",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter metadata",
"width": "320px",
"height": "120px",
"className": "codehinter-plugins"
},
"parameters": {
"label": "Parameters",
"key": "prompt_parameters",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter parameters",
"height": "120px",
"width": "320px",
"className": "codehinter-plugins"
},
"config": {
"label": "Config",
"key": "config",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter config",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"virtualKey": {
"label": "Virtual Key",
"key": "virtualKey",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter virtual key",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
}
},
"create_embedding": {
"input": {
"label": "Input",
"key": "input",
"type": "codehinter",
"description": "Enter input",
"height": "150px"
},
"model": {
"label": "Model",
"key": "model",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter model",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"metadata": {
"label": "Metadata",
"key": "metadata",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter metadata",
"width": "320px",
"height": "120px",
"className": "codehinter-plugins"
},
"config": {
"label": "Config",
"key": "config",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter config",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
},
"virtualKey": {
"label": "Virtual Key",
"key": "virtualKey",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter virtual key",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins"
}
}
}
}

View file

@ -0,0 +1,93 @@
import * as PortkeyAi from 'portkey-ai';
import { ChatCompletionQueryOptions, EmbeddingQueryOptions, PromptCompletionQueryOptions, TextCompletionQueryOptions } from './types';
import { safeParseJSON } from './utils';
export async function getCompletion(
portkey: PortkeyAi.Portkey,
options: TextCompletionQueryOptions
): Promise<Record<string, any> | { error: string; statusCode: number }> {
const { model, prompt, max_tokens, temperature, stop_sequences, metadata, other_parameters } = options;
try {
const data = await portkey.completions.create({
model: typeof model === 'string' ? model : 'davinci-002',
prompt: prompt,
temperature: typeof temperature === 'string' ? parseFloat(temperature) : temperature || 1,
max_tokens: typeof max_tokens === 'string' ? parseInt(max_tokens) : max_tokens || 256,
stop_sequences: typeof stop_sequences === 'string' ? stop_sequences.split(',') : [],
...safeParseJSON(metadata, {}),
}, safeParseJSON(metadata, {}));
return data;
} catch (error) {
return {
error: error?.message,
statusCode: error?.response?.status,
};
}
}
export async function getChatCompletion(
portkey: PortkeyAi.Portkey,
options: ChatCompletionQueryOptions
): Promise<Record<string, any> | { error: string; statusCode: number }> {
const { model, messages, max_tokens, temperature, stop_sequences, metadata, other_parameters } = options;
try {
const data = await portkey.chat.completions.create({
model: typeof model === 'string' ? model : 'gpt-3.5-turbo',
temperature: typeof temperature === 'string' ? parseFloat(temperature) : temperature || 1,
max_tokens: typeof max_tokens === 'string' ? parseInt(max_tokens) : max_tokens || 256,
stop_sequences: typeof stop_sequences === 'string' ? stop_sequences.split(',') : [],
messages: typeof messages === 'string' ? JSON.parse(messages) : messages,
...safeParseJSON(other_parameters, {}),
}, safeParseJSON(metadata, {}));
return data;
} catch (error) {
return {
error: error?.message,
statusCode: error?.response?.status,
};
}
}
export async function getPromptCompletion(
portkey: PortkeyAi.Portkey,
options: PromptCompletionQueryOptions
): Promise<Record<string, any> | { error: string; statusCode: number }> {
const { promptId, variables } = options;
try {
const data = await portkey.prompts.completions.create({
promptID: promptId,
variables: typeof variables === 'string' ? JSON.parse(variables) : variables || null,
...JSON.parse(typeof options.prompt_parameters === 'string' ? options.prompt_parameters : '{}'),
...JSON.parse(typeof options.metadata === 'string' ? options.metadata : '{}'),
}, JSON.parse(typeof options.metadata === 'string' ? options.metadata : '{}'));
return data;
} catch (error) {
return {
error: error?.message,
statusCode: error?.response?.status,
};
}
}
export async function createEmbedding(
portkey: PortkeyAi.Portkey,
options: EmbeddingQueryOptions
): Promise<Record<string, any> | { error: string; statusCode: number }> {
const { input, model, metadata } = options;
try {
const data = await portkey.embeddings.create({
model: model,
input: input,
...JSON.parse(typeof options.metadata === 'string' ? options.metadata : '{}'),
});
return data;
} catch (error) {
return {
error: error?.message,
statusCode: error?.response?.status,
};
}
}

View file

@ -0,0 +1,62 @@
export type SourceOptions = {
apiKey: string;
virtualKey: string;
config: Record<string, any>;
};
// export type QueryOptions = {
// operation: Operation;
// prompt?: string;
// max_tokens?: number | string;
// temperature?: number | string;
// stop_sequence?: [string];
// suffix?: string | null;
// };
interface CredentialsBase {
config?: Record<string, any> | null;
virtualKey?: string | null;
}
export enum Operation {
Completion = 'completion',
Chat = 'chat',
PromptCompletion = 'prompt_completion',
CreateEmbedding = 'create_embedding',
}
interface CompletionQueryBase extends CredentialsBase {
operation: Operation;
model: string;
temperature?: number | string;
max_tokens?: number | string;
stop_sequences?: string;
metadata?: string | null;
other_parameters?: string | null;
}
interface Message {
role: string;
content: string;
}
export interface ChatCompletionQueryOptions extends CompletionQueryBase {
messages: Array<Message>
}
export interface TextCompletionQueryOptions extends CompletionQueryBase {
prompt: string
}
export interface PromptCompletionQueryOptions extends CredentialsBase {
operation: Operation;
promptId: string;
variables?: string | null;
prompt_parameters?: string | null;
metadata?: string | null;
}
export interface EmbeddingQueryOptions extends CredentialsBase {
operation: Operation;
input: string;
model: string;
metadata?: Record<string, any>;
}
export type QueryOptions = TextCompletionQueryOptions | ChatCompletionQueryOptions | PromptCompletionQueryOptions | EmbeddingQueryOptions;

View file

@ -0,0 +1,7 @@
export function safeParseJSON(json: string, fallback: any): any {
try {
return JSON.parse(json);
} catch (e) {
return fallback;
}
}

View file

@ -0,0 +1,27 @@
{
"name": "@tooljet-marketplace/portkey",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"build": "ncc build lib/index.ts -o dist",
"watch": "ncc build lib/index.ts -o dist --watch"
},
"homepage": "https://github.com/tooljet/tooljet#readme",
"dependencies": {
"@tooljet-marketplace/common": "^1.0.0",
"portkey-ai": "^1.3.1"
},
"devDependencies": {
"@vercel/ncc": "^0.34.0",
"typescript": "^4.7.4"
}
}

View file

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "lib",
"target": "ES2019"
},
"exclude": [
"node_modules",
"dist"
]
}

View file

@ -14,5 +14,10 @@
"composite": true,
"resolveJsonModule": true
},
"exclude": ["plugins/*/lib/*.json", "<rootDir>/__tests__/*", "<rootDir>/scripts/*","dist"]
}
"exclude": [
"plugins/*/lib/*.json",
"<rootDir>/__tests__/*",
"<rootDir>/scripts/*",
"dist"
]
}

View file

@ -87,5 +87,13 @@
"id": "salesforce",
"author": "Tooljet",
"timestamp": "Wed, 06 Mar 2024 11:34:26 GMT"
}
},
{
"name": "portkey",
"description": "Plugin for Portkey APIs",
"version": "1.0.0",
"id": "portkey",
"author": "Portkey",
"timestamp": "Sat, 29 Jun 2024 09:40:13 GMT"
}
]