Skip to content

Commit 1559b5c

Browse files
committed
Merge branch 'main' of github.com:Portkey-AI/gateway into fix/webhook-plugin-errors
2 parents f5c6e65 + cccd150 commit 1559b5c

File tree

21 files changed

+1168
-96
lines changed

21 files changed

+1168
-96
lines changed

cookbook/integrations/Sutra_with_Portkey.ipynb

Lines changed: 435 additions & 0 deletions
Large diffs are not rendered by default.

plugins/default/manifest.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,49 @@
706706
},
707707
"required": ["jwksUri", "headerKey"]
708708
}
709+
},
710+
{
711+
"name": "Required Metadata Keys",
712+
"id": "requiredMetadataKeys",
713+
"type": "guardrail",
714+
"supportedHooks": ["beforeRequestHook"],
715+
"description": [
716+
{
717+
"type": "subHeading",
718+
"text": "Checks if the metadata contains all the required keys"
719+
}
720+
],
721+
"parameters": {
722+
"type": "object",
723+
"properties": {
724+
"metadataKeys": {
725+
"type": "array",
726+
"label": "Metadata Keys",
727+
"description": [
728+
{
729+
"type": "subHeading",
730+
"text": "Enter the metadata keys to check for."
731+
}
732+
],
733+
"items": {
734+
"type": "string"
735+
}
736+
},
737+
"operator": {
738+
"type": "string",
739+
"label": "Operator",
740+
"description": [
741+
{
742+
"type": "subHeading",
743+
"text": "Select the operator to use."
744+
}
745+
],
746+
"enum": ["all", "any", "none"],
747+
"default": "all"
748+
}
749+
},
750+
"required": ["metadataKeys", "operator"]
751+
}
709752
}
710753
]
711754
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {
2+
HookEventType,
3+
PluginContext,
4+
PluginHandler,
5+
PluginHandlerResponse,
6+
PluginParameters,
7+
} from '../types';
8+
9+
export const handler: PluginHandler = async (
10+
context: PluginContext,
11+
parameters: PluginParameters,
12+
eventType: HookEventType
13+
) => {
14+
const pluginResponse: PluginHandlerResponse = {
15+
error: null,
16+
verdict: true,
17+
data: null,
18+
transformedData: {
19+
request: {
20+
json: null,
21+
},
22+
response: {
23+
json: null,
24+
},
25+
},
26+
transformed: false,
27+
};
28+
29+
try {
30+
// Only process before request hooks
31+
if (eventType !== 'beforeRequestHook') {
32+
pluginResponse.error = {
33+
message: 'This plugin only works for before_request_hooks',
34+
};
35+
return pluginResponse;
36+
}
37+
const metadataKeys = parameters.metadataKeys;
38+
if (!(Array.isArray(metadataKeys) && metadataKeys.length > 0)) {
39+
pluginResponse.error = {
40+
message: 'metadataKeys must be an array and not empty',
41+
};
42+
return pluginResponse;
43+
}
44+
if (metadataKeys.some((key: string) => typeof key !== 'string')) {
45+
pluginResponse.error = {
46+
message: 'metadataKeys must be an array of strings',
47+
};
48+
return pluginResponse;
49+
}
50+
if (!['all', 'any', 'none'].includes(parameters.operator)) {
51+
pluginResponse.error = {
52+
message: 'operator must be one of: all, any, none',
53+
};
54+
return pluginResponse;
55+
}
56+
57+
const foundMetadataKeys: string[] = [];
58+
const missingMetadataKeys: string[] = [];
59+
60+
const metadata = context.metadata || {};
61+
62+
metadataKeys.forEach((key: string) => {
63+
if (key in metadata) {
64+
foundMetadataKeys.push(key);
65+
} else {
66+
missingMetadataKeys.push(key);
67+
}
68+
});
69+
70+
switch (parameters.operator) {
71+
case 'any':
72+
pluginResponse.verdict = foundMetadataKeys.length > 0;
73+
break;
74+
case 'all':
75+
pluginResponse.verdict = missingMetadataKeys.length === 0;
76+
break;
77+
case 'none':
78+
pluginResponse.verdict = foundMetadataKeys.length === 0;
79+
break;
80+
}
81+
82+
pluginResponse.data = {
83+
explanation: `Check ${pluginResponse.verdict ? 'passed' : 'failed'} for '${parameters.operator}' metadata keys.`,
84+
missing_metadata_keys: missingMetadataKeys,
85+
found_metadata_keys: foundMetadataKeys,
86+
operator: parameters.operator,
87+
};
88+
89+
if (pluginResponse.verdict) {
90+
switch (parameters.operator) {
91+
case 'any':
92+
pluginResponse.data.explanation += ` At least one metadata key was found.`;
93+
break;
94+
case 'all':
95+
pluginResponse.data.explanation += ` All metadata keys were found.`;
96+
break;
97+
case 'none':
98+
pluginResponse.data.explanation += ` No metadata keys were found.`;
99+
break;
100+
}
101+
} else {
102+
switch (parameters.operator) {
103+
case 'any':
104+
pluginResponse.data.explanation += ` No metadata keys were found.`;
105+
break;
106+
case 'all':
107+
pluginResponse.data.explanation += ` Some metadata keys were missing.`;
108+
break;
109+
case 'none':
110+
pluginResponse.data.explanation += ` Some metadata keys were found.`;
111+
break;
112+
}
113+
}
114+
} catch (e: any) {
115+
pluginResponse.error = e;
116+
pluginResponse.data = null;
117+
}
118+
119+
return pluginResponse;
120+
};

plugins/exa/online.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ const performExaSearch = async (
3737
query,
3838
numResults,
3939
useAutoprompt: true,
40-
contents: {
41-
text: true,
40+
text: {
41+
includeHtmlTags: false,
4242
},
4343
};
4444

plugins/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { handler as promptSecurityProtectPrompt } from './promptsecurity/protect
4848
import { handler as promptSecurityProtectResponse } from './promptsecurity/protectResponse';
4949
import { handler as panwPrismaAirsintercept } from './panw-prisma-airs/intercept';
5050
import { handler as defaultjwt } from './default/jwt';
51+
import { handler as defaultrequiredMetadataKeys } from './default/requiredMetadataKeys';
5152

5253
export const plugins = {
5354
default: {
@@ -67,6 +68,7 @@ export const plugins = {
6768
endsWith: defaultendsWith,
6869
modelWhitelist: defaultmodelWhitelist,
6970
jwt: defaultjwt,
71+
requiredMetadataKeys: defaultrequiredMetadataKeys,
7072
},
7173
portkey: {
7274
moderateContent: portkeymoderateContent,

plugins/portkey/globals.ts

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,36 @@ export interface PIIResult {
2727
redactedText: string;
2828
}
2929

30+
export interface LogObjectRequest {
31+
url: string;
32+
method: string;
33+
headers: Record<string, any>;
34+
body: any;
35+
provider?: string;
36+
}
37+
38+
export interface LogObjectResponse {
39+
status: number;
40+
headers: Record<string, any>;
41+
body: any;
42+
response_time: number;
43+
streamingMode: boolean;
44+
}
45+
46+
export interface LogObjectMetadata extends Record<string, any> {
47+
traceId?: string;
48+
spanId?: string;
49+
parentSpanId?: string;
50+
spanName?: string;
51+
}
52+
53+
export interface LogObject {
54+
request: LogObjectRequest;
55+
response: LogObjectResponse;
56+
metadata: LogObjectMetadata;
57+
createdAt: string;
58+
}
59+
3060
interface PIIParameters extends PluginParameters {
3161
categories: string[];
3262
credentials: Record<string, any>;
@@ -40,22 +70,70 @@ export const fetchPortkey = async (
4070
credentials: any,
4171
data: any,
4272
timeout?: number
43-
) => {
73+
): Promise<{ response: PIIResponse[]; log: LogObject }> => {
4474
const options = {
4575
headers: {
4676
'x-portkey-api-key': credentials.apiKey,
4777
},
4878
};
4979

50-
if (getRuntimeKey() === 'workerd' && env.portkeyGuardrails) {
51-
return postWithCloudflareServiceBinding(
52-
`${BASE_URL}${endpoint}`,
53-
data,
54-
env.portkeyGuardrails,
55-
options,
56-
timeout
57-
);
80+
const url = `${BASE_URL}${endpoint}`;
81+
const startTime = Date.now();
82+
let responseData: any;
83+
let responseStatus: number = 200;
84+
let responseHeaders: Record<string, any> = {};
85+
let error: any = null;
86+
87+
try {
88+
if (getRuntimeKey() === 'workerd' && env.portkeyGuardrails) {
89+
responseData = await postWithCloudflareServiceBinding(
90+
url,
91+
data,
92+
env.portkeyGuardrails,
93+
options,
94+
timeout
95+
);
96+
} else {
97+
responseData = await post(url, data, options, timeout);
98+
}
99+
} catch (e: any) {
100+
error = e;
101+
responseStatus = e.response?.status || 500;
102+
responseHeaders = {};
103+
responseData = e.response?.body || { error: e.message };
58104
}
59105

60-
return post(`${BASE_URL}${endpoint}`, data, options, timeout);
106+
const endTime = Date.now();
107+
const responseTime = endTime - startTime;
108+
109+
const log: LogObject = {
110+
request: {
111+
url,
112+
method: 'POST',
113+
headers: options.headers,
114+
body: data,
115+
},
116+
response: {
117+
status: responseStatus,
118+
headers: responseHeaders,
119+
body: responseData,
120+
response_time: responseTime,
121+
streamingMode: false,
122+
},
123+
metadata: {
124+
spanId: generateSpanId(),
125+
spanName: 'Portkey PII Check',
126+
},
127+
createdAt: new Date().toISOString(),
128+
};
129+
130+
if (error) {
131+
throw { ...error, log };
132+
}
133+
134+
return { response: responseData, log };
61135
};
136+
137+
function generateSpanId(): string {
138+
return Math.random().toString(36).substring(2, 10);
139+
}

0 commit comments

Comments
 (0)