Skip to content

Commit 3c1f16d

Browse files
authored
Merge branch 'main' into allowed-models-plugin
2 parents 2c4e320 + 5a10a72 commit 3c1f16d

38 files changed

+1865
-1360
lines changed

.github/workflows/run_tests.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Gateway Tests
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
jobs:
8+
gateway-tests:
9+
if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'run tests') }}
10+
runs-on: ubuntu-latest
11+
environment: production
12+
steps:
13+
- name: Checkout head
14+
uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 0
17+
18+
- name: Install dependencies
19+
run: npm ci
20+
21+
- name: Build
22+
run: npm run build
23+
24+
- name: Start gateway and run tests
25+
id: run-tests
26+
continue-on-error: true
27+
run: |
28+
npm run build/start-server.js &
29+
echo "Waiting for gateway to start..."
30+
while ! curl -s http://localhost:8787 > /dev/null; do
31+
sleep 1
32+
done
33+
echo "Gateway is ready. Running tests..."
34+
npm run test:gateway
35+
36+
- name: Update PR Check
37+
uses: actions/github-script@v6
38+
with:
39+
github-token: ${{secrets.GITHUB_TOKEN}}
40+
script: |
41+
const { owner, repo } = context.repo;
42+
const issue_number = context.issue.number;
43+
44+
try {
45+
const { data: pull_request } = await github.rest.pulls.get({
46+
owner,
47+
repo,
48+
pull_number: issue_number,
49+
});
50+
51+
const sha = pull_request.head.sha;
52+
53+
await github.rest.checks.create({
54+
owner,
55+
repo,
56+
name: 'Gateway Tests (Comment Triggered)',
57+
head_sha: sha,
58+
status: 'completed',
59+
conclusion: '${{ steps.run-tests.outcome }}',
60+
output: {
61+
title: 'Gateway Test Results',
62+
summary: 'Gateway tests have completed.',
63+
text: 'These tests were triggered by a comment on the PR.'
64+
},
65+
});
66+
67+
console.log('Check run created successfully');
68+
} catch (error) {
69+
console.error('Error creating check run:', error);
70+
core.setFailed(`Action failed with error: ${error}`);
71+
}
72+
73+
env:
74+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
75+
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ The AI Gateway's enterprise version offers enterprise-ready capabilities for **o
6767

6868
The enterprise deployment architecture, supported platforms is available here - [**Enterprise Private Cloud Deployments**](https://docs.portkey.ai/docs/product/enterprise-offering/private-cloud-deployments)
6969

70-
<a href="https://app.portkey.ai/signup"><img src="https://portkey.ai/blog/content/images/2024/08/Get-API-Key--5-.png" height=50 alt="Book an enterprise AI gateway demo" /></a><br>
70+
<a href="https://portkey.sh/demo-22"><img src="https://portkey.ai/blog/content/images/2024/08/Get-API-Key--5-.png" height=50 alt="Book an enterprise AI gateway demo" /></a><br>
7171

7272
<br>
7373

@@ -264,11 +264,11 @@ Make your AI app more <ins>reliable</ins> and <ins>forward compatible</ins>, whi
264264
&nbsp; Secure Key Management - for role-based access control and tracking <br>
265265
&nbsp; Simple & Semantic Caching - to serve repeat queries faster & save costs <br>
266266
&nbsp; Access Control & Inbound Rules - to control which IPs and Geos can connect to your deployments <br>
267-
&nbsp; PII Redaction - to automatically remove sensitive data from your requests to prevent indavertent exposure <br>
267+
&nbsp; PII Redaction - to automatically remove sensitive data from your requests to prevent inadvertent exposure <br>
268268
&nbsp; SOC2, ISO, HIPAA, GDPR Compliances - for best security practices <br>
269269
&nbsp; Professional Support - along with feature prioritization <br>
270270

271-
[Schedule a call to discuss enterprise deployments](https://calendly.com/rohit-portkey/noam)
271+
[Schedule a call to discuss enterprise deployments](https://portkey.sh/demo-22)
272272

273273
<br>
274274

Lines changed: 0 additions & 1 deletion
This file was deleted.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@portkey-ai/gateway",
3-
"version": "1.7.7",
3+
"version": "1.8.0",
44
"description": "A fast AI gateway by Portkey",
55
"repository": {
66
"type": "git",
@@ -34,7 +34,8 @@
3434
"start:node": "node build/start-server.js",
3535
"format": "prettier --write \"./**/*.{js,jsx,ts,tsx,json}\"",
3636
"format:check": "prettier --check \"./**/*.{js,jsx,ts,tsx,json}\"",
37-
"test": "ts-jest",
37+
"test:gateway": "jest src/",
38+
"test:plugins": "jest plugins/",
3839
"pre-push": "npm run build && node start-test.js",
3940
"prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky"
4041
},

src/globals.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const HEADER_KEYS: Record<string, string> = {
1313
CUSTOM_HOST: `x-${POWERED_BY}-custom-host`,
1414
REQUEST_TIMEOUT: `x-${POWERED_BY}-request-timeout`,
1515
STRICT_OPEN_AI_COMPLIANCE: `x-${POWERED_BY}-strict-open-ai-compliance`,
16+
CONTENT_TYPE: `Content-Type`,
1617
};
1718

1819
export const RESPONSE_HEADER_KEYS: Record<string, string> = {
@@ -138,3 +139,52 @@ export const MULTIPART_FORM_DATA_ENDPOINTS: endpointStrings[] = [
138139
'createTranscription',
139140
'createTranslation',
140141
];
142+
143+
export const fileExtensionMimeTypeMap = {
144+
mp4: 'video/mp4',
145+
jpeg: 'image/jpeg',
146+
jpg: 'image/jpeg',
147+
png: 'image/png',
148+
bmp: 'image/bmp',
149+
tiff: 'image/tiff',
150+
webp: 'image/webp',
151+
pdf: 'application/pdf',
152+
csv: 'text/csv',
153+
doc: 'application/msword',
154+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
155+
xls: 'application/vnd.ms-excel',
156+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
157+
html: 'text/html',
158+
md: 'text/markdown',
159+
mp3: 'audio/mp3',
160+
wav: 'audio/wav',
161+
txt: 'text/plain',
162+
mov: 'video/mov',
163+
mpeg: 'video/mpeg',
164+
mpg: 'video/mpg',
165+
avi: 'video/avi',
166+
wmv: 'video/wmv',
167+
mpegps: 'video/mpegps',
168+
flv: 'video/flv',
169+
};
170+
171+
export const imagesMimeTypes = [
172+
fileExtensionMimeTypeMap.jpeg,
173+
fileExtensionMimeTypeMap.jpg,
174+
fileExtensionMimeTypeMap.png,
175+
fileExtensionMimeTypeMap.bmp,
176+
fileExtensionMimeTypeMap.tiff,
177+
fileExtensionMimeTypeMap.webp,
178+
];
179+
180+
export const documentMimeTypes = [
181+
fileExtensionMimeTypeMap.pdf,
182+
fileExtensionMimeTypeMap.csv,
183+
fileExtensionMimeTypeMap.doc,
184+
fileExtensionMimeTypeMap.docx,
185+
fileExtensionMimeTypeMap.xls,
186+
fileExtensionMimeTypeMap.xlsx,
187+
fileExtensionMimeTypeMap.html,
188+
fileExtensionMimeTypeMap.md,
189+
fileExtensionMimeTypeMap.txt,
190+
];

src/handlers/handlerUtils.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import {
1111
OPEN_AI,
1212
AZURE_AI_INFERENCE,
1313
ANTHROPIC,
14-
MULTIPART_FORM_DATA_ENDPOINTS,
1514
CONTENT_TYPES,
1615
HUGGING_FACE,
16+
STABILITY_AI,
1717
} from '../globals';
1818
import Providers from '../providers';
1919
import { ProviderAPIConfig, endpointStrings } from '../providers/types';
@@ -269,6 +269,7 @@ export async function tryPostProxy(
269269
: (providerOption.urlToFetch as string);
270270

271271
const headers = await apiConfig.headers({
272+
c,
272273
providerOptions: providerOption,
273274
fn,
274275
transformedRequestBody: params,
@@ -520,10 +521,12 @@ export async function tryPost(
520521
const url = `${baseUrl}${endpoint}`;
521522

522523
const headers = await apiConfig.headers({
524+
c,
523525
providerOptions: providerOption,
524526
fn,
525527
transformedRequestBody,
526528
transformedRequestUrl: url,
529+
gatewayRequestBody: params,
527530
});
528531

529532
// Construct the base object for the POST request
@@ -535,9 +538,10 @@ export async function tryPost(
535538
requestHeaders
536539
);
537540

538-
fetchOptions.body = MULTIPART_FORM_DATA_ENDPOINTS.includes(fn)
539-
? (transformedRequestBody as FormData)
540-
: JSON.stringify(transformedRequestBody);
541+
fetchOptions.body =
542+
headers[HEADER_KEYS.CONTENT_TYPE] === CONTENT_TYPES.MULTIPART_FORM_DATA
543+
? (transformedRequestBody as FormData)
544+
: JSON.stringify(transformedRequestBody);
541545

542546
providerOption.retry = {
543547
attempts: providerOption.retry?.attempts ?? 0,
@@ -638,8 +642,8 @@ export async function tryPost(
638642

639643
// Prerequest validator (For virtual key budgets)
640644
const preRequestValidator = c.get('preRequestValidator');
641-
let preRequestValidatorResponse = preRequestValidator
642-
? await preRequestValidator(env(c), providerOption, requestHeaders)
645+
const preRequestValidatorResponse = preRequestValidator
646+
? await preRequestValidator(c, providerOption, requestHeaders, params)
643647
: undefined;
644648
if (!!preRequestValidatorResponse) {
645649
return createResponse(preRequestValidatorResponse, undefined, false);
@@ -1009,9 +1013,24 @@ export function constructConfigFromRequestHeaders(
10091013
resourceName: requestHeaders[`x-${POWERED_BY}-azure-resource-name`],
10101014
deploymentId: requestHeaders[`x-${POWERED_BY}-azure-deployment-id`],
10111015
apiVersion: requestHeaders[`x-${POWERED_BY}-azure-api-version`],
1016+
azureAuthMode: requestHeaders[`x-${POWERED_BY}-azure-auth-mode`],
1017+
azureManagedClientId:
1018+
requestHeaders[`x-${POWERED_BY}-azure-managed-client-id`],
1019+
azureEntraClientId: requestHeaders[`x-${POWERED_BY}-azure-entra-client-id`],
1020+
azureEntraClientSecret:
1021+
requestHeaders[`x-${POWERED_BY}-azure-entra-client-secret`],
1022+
azureEntraTenantId: requestHeaders[`x-${POWERED_BY}-azure-entra-tenant-id`],
10121023
azureModelName: requestHeaders[`x-${POWERED_BY}-azure-model-name`],
10131024
};
10141025

1026+
const stabilityAiConfig = {
1027+
stabilityClientId: requestHeaders[`x-${POWERED_BY}-stability-client-id`],
1028+
stabilityClientUserId:
1029+
requestHeaders[`x-${POWERED_BY}-stability-client-user-id`],
1030+
stabilityClientVersion:
1031+
requestHeaders[`x-${POWERED_BY}-stability-client-version`],
1032+
};
1033+
10151034
const azureAiInferenceConfig = {
10161035
azureDeploymentName:
10171036
requestHeaders[`x-${POWERED_BY}-azure-deployment-name`],
@@ -1027,6 +1046,9 @@ export function constructConfigFromRequestHeaders(
10271046
awsSecretAccessKey: requestHeaders[`x-${POWERED_BY}-aws-secret-access-key`],
10281047
awsSessionToken: requestHeaders[`x-${POWERED_BY}-aws-session-token`],
10291048
awsRegion: requestHeaders[`x-${POWERED_BY}-aws-region`],
1049+
awsRoleArn: requestHeaders[`x-${POWERED_BY}-aws-role-arn`],
1050+
awsAuthType: requestHeaders[`x-${POWERED_BY}-aws-auth-type`],
1051+
awsExternalId: requestHeaders[`x-${POWERED_BY}-aws-external-id`],
10301052
};
10311053

10321054
const workersAiConfig = {
@@ -1128,6 +1150,12 @@ export function constructConfigFromRequestHeaders(
11281150
...anthropicConfig,
11291151
};
11301152
}
1153+
if (parsedConfigJson.provider === STABILITY_AI) {
1154+
parsedConfigJson = {
1155+
...parsedConfigJson,
1156+
...stabilityAiConfig,
1157+
};
1158+
}
11311159
}
11321160
return convertKeysToCamelCase(parsedConfigJson, [
11331161
'override_params',
@@ -1158,6 +1186,8 @@ export function constructConfigFromRequestHeaders(
11581186
huggingfaceConfig),
11591187
mistralFimCompletion:
11601188
requestHeaders[`x-${POWERED_BY}-mistral-fim-completion`],
1189+
...(requestHeaders[`x-${POWERED_BY}-provider`] === STABILITY_AI &&
1190+
stabilityAiConfig),
11611191
};
11621192
}
11631193

src/handlers/streamHandler.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ function getPayloadFromAWSChunk(chunk: Uint8Array): string {
3333

3434
const payloadLength = chunkLength - headersEnd - 4; // Subtracting 4 for the message crc
3535
const payload = chunk.slice(headersEnd, headersEnd + payloadLength);
36-
return decoder.decode(payload);
36+
const decodedJson = JSON.parse(decoder.decode(payload));
37+
return decodedJson.bytes
38+
? Buffer.from(decodedJson.bytes, 'base64').toString()
39+
: JSON.stringify(decodedJson);
3740
}
3841

3942
function concatenateUint8Arrays(a: Uint8Array, b: Uint8Array): Uint8Array {
@@ -60,10 +63,7 @@ export async function* readAWSStream(
6063
const data = buffer.subarray(0, expectedLength);
6164
buffer = buffer.subarray(expectedLength);
6265
expectedLength = readUInt32BE(buffer, 0);
63-
const payload = Buffer.from(
64-
JSON.parse(getPayloadFromAWSChunk(data)).bytes,
65-
'base64'
66-
).toString();
66+
const payload = getPayloadFromAWSChunk(data);
6767
if (transformFunction) {
6868
const transformedChunk = transformFunction(
6969
payload,
@@ -96,11 +96,7 @@ export async function* readAWSStream(
9696
buffer = buffer.subarray(expectedLength);
9797

9898
expectedLength = readUInt32BE(buffer, 0);
99-
const payload = Buffer.from(
100-
JSON.parse(getPayloadFromAWSChunk(data)).bytes,
101-
'base64'
102-
).toString();
103-
99+
const payload = getPayloadFromAWSChunk(data);
104100
if (transformFunction) {
105101
const transformedChunk = transformFunction(
106102
payload,

src/providers/anthropic/api.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@ import { ProviderAPIConfig } from '../types';
22

33
const AnthropicAPIConfig: ProviderAPIConfig = {
44
getBaseURL: () => 'https://api.anthropic.com/v1',
5-
headers: ({ providerOptions, fn }) => {
5+
headers: ({ providerOptions, fn, gatewayRequestBody }) => {
66
const headers: Record<string, string> = {
77
'X-API-Key': `${providerOptions.apiKey}`,
88
};
99

10+
// Accept anthropic_beta and anthropic_version in body to support enviroments which cannot send it in headers.
1011
const betaHeader =
11-
providerOptions?.['anthropicBeta'] ?? 'messages-2023-12-15';
12-
const version = providerOptions?.['anthropicVersion'] ?? '2023-06-01';
12+
providerOptions?.['anthropicBeta'] ??
13+
gatewayRequestBody?.['anthropic_beta'] ??
14+
'messages-2023-12-15';
15+
const version =
16+
providerOptions?.['anthropicVersion'] ??
17+
gatewayRequestBody?.['anthropic_version'] ??
18+
'2023-06-01';
1319

1420
if (fn === 'chatComplete') {
1521
headers['anthropic-beta'] = betaHeader;

0 commit comments

Comments
 (0)