Skip to content

Commit

Permalink
Merge branch 'main' into deepseek-support
Browse files Browse the repository at this point in the history
  • Loading branch information
VisargD authored Aug 19, 2024
2 parents c96b517 + 9ab4280 commit 544043e
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 2 deletions.
91 changes: 91 additions & 0 deletions plugins/Contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## 🎉 Welcome

Hello and Thank you for your interest in contributing to Portkey Gateway Plugins! We're excited to welcome new developers to our community. This guide will help you get started with creating and submitting new guardrails plugins for the Portkey Gateway.

## 🚀 Quick Start

1. Fork the repository on GitHub: [https://github.com/Portkey-AI/gateway](https://github.com/Portkey-AI/gateway)
2. Clone your forked repository to your local machine:
```sh
git clone https://github.com/YOUR_USERNAME/gateway.git
3. Navigate to the plugins directory:
```sh
cd gateway/plugins
```

## 🔧 Creating a New Plugin

Create a new directory for your plugin in the `/plugins` folder:
```sh
Copy/plugins
/your-plugin-name
- manifest.json
- main-function.ts
- test-file.test.ts (recommended)
```

Create a `manifest.json` file defining your plugin's properties, credentials, and functions.
Implement your plugin logic in `main-function.ts.`
Write tests for your plugin in `test-file.test.ts`.
For detailed information on plugin structure and implementation, please refer to the [Plugins README](https://github.com/Portkey-AI/gateway/tree/main/plugins#readme).
## 📝 Plugin Guidelines
Focus on implementing guardrails as the primary function of your plugin.
Ensure your plugin works with the `beforeRequest` and `afterRequest` hooks.
Write clear and concise documentation within your code and in the `manifest.json` file.
Test your plugin thoroughly before submission.
## 🔄 Contributing a Plugin
There are two main ways to contribute a plugin to the Portkey Gateway:
### 1. Work on Existing Issues
1. Check the [Issues tab](https://github.com/Portkey-AI/gateway/issues) in the Portkey Gateway repository.
2. Look for issues labeled with `good-first-issue` and `plugin`.
3. Submit a pull request referencing the original issue number.
### 2. Propose and Develop a New Plugin
If you have an idea for a new plugin:
1. Check the [Issues tab](https://github.com/Portkey-AI/gateway/issues) to ensure a similar plugin hasn't been proposed.
2. Create a new issue with the following title format: [Feature] Your Plugin Name.
3. In the issue description, provide:
- **Plugin Name**: A clear, descriptive name for your plugin.
- **Description**: A brief overview of your plugin's functionality and its benefits.
3. Submit a pull request referencing your proposal issue number.
## 📤 Submitting Your Pull Request
When your plugin is ready for review:
1. Ensure your code follows the structure outlined in the [Plugins README](https://github.com/Portkey-AI/gateway/blob/main/plugins/README.md).
3. Run tests to ensure your plugin works as expected.
4. Create a pull request with the following title format: `[New Plugin] Your Plugin Name`.
5. In the pull request description, provide:
- A link to the original issue or proposal
- A summary of the changes and new features
The maintainers will review your submission and may suggest changes or improvements. Be prepared to engage in a constructive dialogue and make updates as needed.
## 🧪 Testing
Before submitting your pull request:
1. Write unit tests for your plugin in a `test-file.test.ts` file.
2. Ensure all tests pass by running:
## 🤔 Getting Help
If you have any questions or need assistance while developing your plugin, please join our [Discord](https://discord.gg/DD7vgKK299) community. It's the fastest way to get support and connect with other contributors.

## 🎊 Your Plugin is Accepted!
Once your plugin is reviewed and accepted, it will be merged into the main repository. We appreciate your contribution to making the Portkey Gateway more powerful and versatile!
Thank you for contributing to Portkey Gateway Plugins!
28 changes: 27 additions & 1 deletion plugins/default/default.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { handler as containsCodeHandler } from './containsCode';
import { handler as wordCountHandler } from './wordCount';
import { handler as sentenceCountHandler } from './sentenceCount';
import { handler as webhookHandler } from './webhook';
import { handler as logHandler } from './log';

import { z } from 'zod';
import { PluginContext, PluginParameters } from '../types';
Expand Down Expand Up @@ -421,7 +422,7 @@ describe('wordCount handler', () => {
});
});

describe.only('webhook handler', () => {
describe('webhook handler', () => {
it('should handle a postive result from a webhook', async () => {
const eventType = 'afterRequestHook';
const context: PluginContext = {
Expand Down Expand Up @@ -496,3 +497,28 @@ describe.only('webhook handler', () => {
expect(result.data).toBe(null);
});
});

describe('log handler', () => {
it('should log details to a URL', async () => {
const eventType = 'afterRequestHook';
const context: PluginContext = {
request: {
text: `adding some text before this \`\`\`json\n{"key1": "value"}\n\`\`\`\n and adding some text after {"key":"value"}`,
json: { key: 'value' },
},
response: {
text: `adding some text before this \`\`\`json\n{"key1": "value"}\n\`\`\`\n and adding some text after {"key":"value"}`,
json: { key: 'value' },
},
};
const parameters: PluginParameters = {
logURL: 'https://roh26it-upsetharlequinfrog.web.val.run',
headers: '{"Authorization": "this is some secret"}',
};

const result = await logHandler(context, parameters, eventType);

expect(result.error).toBe(null);
expect(result.verdict).toBe(true);
});
});
29 changes: 29 additions & 0 deletions plugins/default/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PluginContext, PluginHandler, PluginParameters } from '../types';
import { post } from '../utils';

export const handler: PluginHandler = async (
context: PluginContext,
parameters: PluginParameters
) => {
let error = null;
let verdict = false;
let data = null;

try {
let url = parameters.logURL;
let headers: Record<string, string> = parameters?.headers
? JSON.parse(parameters.headers)
: {};

// log the request
await post(url, context, { headers }, 3000);

verdict = true;
data = { message: `Logged the request to ${url}` };
} catch (e: any) {
delete e.stack;
error = e;
}

return { error, verdict, data };
};
38 changes: 38 additions & 0 deletions plugins/default/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,44 @@
}
}
},
{
"name": "Log",
"id": "log",
"type": "guardrail",
"supportedHooks": ["afterRequest"],
"description": [
{
"type": "subHeading",
"text": "Makes a request to a log URL and always gives `true` as the verdict."
}
],
"parameters": {
"type": "object",
"properties": {
"logURL": {
"type": "string",
"label": "Log URL",
"description": [
{
"type": "subHeading",
"text": "eg: https://logging.site/collector"
}
]
},
"headers": {
"type": "json",
"label": "Headers",
"description": [
{
"type": "subHeading",
"text": "Enter the headers to send with the request."
}
]
},
"required": ["logURL"]
}
}
},
{
"name": "Contains Code",
"id": "containsCode",
Expand Down
2 changes: 2 additions & 0 deletions plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { handler as defaultjsonKeys } from './default/jsonKeys';
import { handler as defaultcontains } from './default/contains';
import { handler as defaultvalidUrls } from './default/validUrls';
import { handler as defaultwebhook } from './default/webhook';
import { handler as defaultlog } from './default/log';
import { handler as defaultcontainsCode } from './default/containsCode';
import { handler as portkeymoderateContent } from './portkey/moderateContent';
import { handler as portkeylanguage } from './portkey/language';
Expand Down Expand Up @@ -39,6 +40,7 @@ export const plugins = {
contains: defaultcontains,
validUrls: defaultvalidUrls,
webhook: defaultwebhook,
log: defaultlog,
containsCode: defaultcontainsCode,
},
portkey: {
Expand Down
2 changes: 2 additions & 0 deletions src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const NOVITA_AI: string = 'novita-ai';
export const MONSTERAPI: string = 'monsterapi';
export const DEEPSEEK: string = 'deepseek';
export const PREDIBASE: string = 'predibase';
export const VOYAGE: string = 'voyage';

export const VALID_PROVIDERS = [
ANTHROPIC,
Expand Down Expand Up @@ -89,6 +90,7 @@ export const VALID_PROVIDERS = [
MONSTERAPI,
DEEPSEEK,
PREDIBASE,
VOYAGE,
];

export const CONTENT_TYPES = {
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/handlerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ export async function tryPost(
// Prerequest validator (For virtual key budgets)
const preRequestValidator = c.get('preRequestValidator');
let preRequestValidatorResponse = preRequestValidator
? preRequestValidator(providerOption, requestHeaders)
? await preRequestValidator(env(c), providerOption, requestHeaders)
: undefined;
if (!!preRequestValidatorResponse) {
return createResponse(preRequestValidatorResponse, undefined, false);
Expand Down
2 changes: 2 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import NovitaAIConfig from './novita-ai';
import MonsterAPIConfig from './monsterapi';
import DeepSeekAPIConfig from './deepseek';
import PredibaseConfig from './predibase';
import VoyageConfig from './voyage';

const Providers: { [key: string]: ProviderConfigs } = {
openai: OpenAIConfig,
Expand Down Expand Up @@ -63,6 +64,7 @@ const Providers: { [key: string]: ProviderConfigs } = {
monsterapi: MonsterAPIConfig,
deepseek: DeepSeekAPIConfig,
predibase: PredibaseConfig,
voyage: VoyageConfig,
};

export default Providers;
18 changes: 18 additions & 0 deletions src/providers/voyage/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ProviderAPIConfig } from '../types';

const VoyageAPIConfig: ProviderAPIConfig = {
getBaseURL: () => 'https://api.voyageai.com/v1',
headers: ({ providerOptions }) => {
return { Authorization: `Bearer ${providerOptions.apiKey}` };
},
getEndpoint: ({ fn }) => {
switch (fn) {
case 'embed':
return '/embeddings';
default:
return '';
}
},
};

export default VoyageAPIConfig;
96 changes: 96 additions & 0 deletions src/providers/voyage/embed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { VOYAGE } from '../../globals';
import { EmbedParams, EmbedResponse } from '../../types/embedRequestBody';
import { ErrorResponse, ProviderConfig } from '../types';
import {
generateErrorResponse,
generateInvalidProviderResponseError,
} from '../utils';

export const VoyageEmbedConfig: ProviderConfig = {
model: {
param: 'model',
required: true,
},
input: {
param: 'input',
required: true,
transform: (params: EmbedParams) => {
if (Array.isArray(params.input)) {
return params.input;
}

return [params.input];
},
},
input_type: {
param: 'input_type',
required: false,
default: null,
},
truncation: {
param: 'truncation',
required: false,
default: true,
},
encoding_format: {
param: 'encoding_format',
required: false,
default: null,
},
};

interface VoyageEmbedResponse {
object: 'list';
data: Array<{ object: 'embedding'; embedding: number[]; index: number }>;
model: string;
usage: {
total_tokens: number;
};
}

export interface VoyageValidationErrorResponse {
detail: string;
}

export const VoyageErrorResponseTransform: (
response: VoyageValidationErrorResponse | any
) => ErrorResponse = (response) => {
let errorField: string | null = null;

let errorMessage = response.detail;
let errorType = 'Invalid Request';

return generateErrorResponse(
{
message: `${errorField ? `${errorField}: ` : ''}${errorMessage}`,
type: errorType,
param: null,
code: null,
},
VOYAGE
);
};

export const VoyageEmbedResponseTransform: (
response: VoyageEmbedResponse | VoyageValidationErrorResponse | any,
responseStatus: number
) => EmbedResponse | ErrorResponse = (response, responseStatus) => {
if ('detail' in response && responseStatus !== 200) {
return VoyageErrorResponseTransform(response);
}

if ('data' in response) {
return {
object: 'list',
data: response.data,
model: response.model,
usage: {
prompt_tokens: response.usage.total_tokens,
total_tokens: response.usage.total_tokens,
},
provider: VOYAGE,
};
}

return generateInvalidProviderResponseError(response, VOYAGE);
};
13 changes: 13 additions & 0 deletions src/providers/voyage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ProviderConfigs } from '../types';
import VoyageAPIConfig from './api';
import { VoyageEmbedConfig, VoyageEmbedResponseTransform } from './embed';

const VoyageConfig: ProviderConfigs = {
embed: VoyageEmbedConfig,
api: VoyageAPIConfig,
responseTransforms: {
embed: VoyageEmbedResponseTransform,
},
};

export default VoyageConfig;

0 comments on commit 544043e

Please sign in to comment.