Add custom baseURL and idempotency key support to OpenAI integration (#682)
This commit introduces the ability to set custom base URLs, which allows for the use of different APIs such as Perplexity. It also ensures that any OpenAI-related task now includes an idempotency key when making requests. Works for background tasks as well.
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@trigger.dev/openai": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add ability to set baseURL to use different APIs (like Perplexity)
|
||||||
@@ -14,7 +14,9 @@ export class Chat {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
const response = await client.chat.completions.create(params);
|
const response = await client.chat.completions.create(params, {
|
||||||
|
idempotencyKey: task.idempotencyKey,
|
||||||
|
});
|
||||||
task.outputProperties = createTaskUsageProperties(response.usage);
|
task.outputProperties = createTaskUsageProperties(response.usage);
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
@@ -38,15 +40,23 @@ export class Chat {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task, io) => {
|
async (client, task, io) => {
|
||||||
|
let baseURL = client.baseURL ?? "https://api.openai.com/v1";
|
||||||
|
if (baseURL.endsWith("/")) {
|
||||||
|
baseURL = baseURL.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatCompletionsURL = `${baseURL}/chat/completions`;
|
||||||
|
|
||||||
const response = await io.backgroundFetch<OpenAI.Chat.ChatCompletion>(
|
const response = await io.backgroundFetch<OpenAI.Chat.ChatCompletion>(
|
||||||
"background",
|
"background",
|
||||||
"https://api.openai.com/v1/chat/completions",
|
chatCompletionsURL,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: redactString`Bearer ${client.apiKey}`,
|
Authorization: redactString`Bearer ${client.apiKey}`,
|
||||||
...(client.organization ? { "OpenAI-Organization": client.organization } : {}),
|
...(client.organization ? { "OpenAI-Organization": client.organization } : {}),
|
||||||
|
"Idempotency-Key": task.idempotencyKey,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ export class Completions {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
const response = await client.completions.create(params);
|
const response = await client.completions.create(params, {
|
||||||
|
idempotencyKey: task.idempotencyKey,
|
||||||
|
});
|
||||||
task.outputProperties = createTaskUsageProperties(response.usage);
|
task.outputProperties = createTaskUsageProperties(response.usage);
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
@@ -41,15 +43,23 @@ export class Completions {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task, io) => {
|
async (client, task, io) => {
|
||||||
|
let baseURL = client.baseURL ?? "https://api.openai.com/v1";
|
||||||
|
if (baseURL.endsWith("/")) {
|
||||||
|
baseURL = baseURL.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatCompletionsURL = `${baseURL}/chat/completions`;
|
||||||
|
|
||||||
const response = await io.backgroundFetch<OpenAI.Completion>(
|
const response = await io.backgroundFetch<OpenAI.Completion>(
|
||||||
"background",
|
"background",
|
||||||
"https://api.openai.com/v1/completions",
|
chatCompletionsURL,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: redactString`Bearer ${client.apiKey}`,
|
Authorization: redactString`Bearer ${client.apiKey}`,
|
||||||
...(client.organization ? { "OpenAI-Organization": client.organization } : {}),
|
...(client.organization ? { "OpenAI-Organization": client.organization } : {}),
|
||||||
|
"Idempotency-Key": task.idempotencyKey,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export class Edits {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
const response = await client.edits.create(params);
|
const response = await client.edits.create(params, { idempotencyKey: task.idempotencyKey });
|
||||||
task.outputProperties = createTaskUsageProperties(response.usage);
|
task.outputProperties = createTaskUsageProperties(response.usage);
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ export class Embeddings {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
const response = await client.embeddings.create(params);
|
const response = await client.embeddings.create(params, {
|
||||||
|
idempotencyKey: task.idempotencyKey,
|
||||||
|
});
|
||||||
task.outputProperties = createTaskUsageProperties(response.usage);
|
task.outputProperties = createTaskUsageProperties(response.usage);
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ export class Files {
|
|||||||
file = params.file;
|
file = params.file;
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.files.create({ file, purpose: params.purpose });
|
return client.files.create(
|
||||||
|
{ file, purpose: params.purpose },
|
||||||
|
{ idempotencyKey: task.idempotencyKey }
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Create file",
|
name: "Create file",
|
||||||
@@ -81,7 +84,10 @@ export class Files {
|
|||||||
params.fileName
|
params.fileName
|
||||||
);
|
);
|
||||||
|
|
||||||
return client.files.create({ file, purpose: "fine-tune" });
|
return client.files.create(
|
||||||
|
{ file, purpose: "fine-tune" },
|
||||||
|
{ idempotencyKey: task.idempotencyKey }
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Create fine tune file",
|
name: "Create fine tune file",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export class FineTunes {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
return client.fineTunes.create(params);
|
return client.fineTunes.create(params, { idempotencyKey: task.idempotencyKey });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Create fine tune",
|
name: "Create fine tune",
|
||||||
@@ -94,7 +94,7 @@ export class FineTunes {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
return client.fineTunes.cancel(params.fineTuneId);
|
return client.fineTunes.cancel(params.fineTuneId, { idempotencyKey: task.idempotencyKey });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cancel fine tune",
|
name: "Cancel fine tune",
|
||||||
@@ -168,7 +168,7 @@ export class FineTunes {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
return client.fineTuning.jobs.create(params);
|
return client.fineTuning.jobs.create(params, { idempotencyKey: task.idempotencyKey });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Create Fine Tuning Job",
|
name: "Create Fine Tuning Job",
|
||||||
@@ -207,7 +207,7 @@ export class FineTunes {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
return client.fineTuning.jobs.cancel(params.id);
|
return client.fineTuning.jobs.cancel(params.id, { idempotencyKey: task.idempotencyKey });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cancel Fine Tuning Job",
|
name: "Cancel Fine Tuning Job",
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class Images {
|
|||||||
return this.runTask(
|
return this.runTask(
|
||||||
key,
|
key,
|
||||||
async (client, task) => {
|
async (client, task) => {
|
||||||
return client.images.generate(params);
|
return client.images.generate(params, { idempotencyKey: task.idempotencyKey });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Create image",
|
name: "Create image",
|
||||||
@@ -121,15 +121,18 @@ export class Images {
|
|||||||
typeof params.image === "string" ? await fileFromUrl(params.image) : params.image;
|
typeof params.image === "string" ? await fileFromUrl(params.image) : params.image;
|
||||||
const mask = typeof params.mask === "string" ? await fileFromUrl(params.mask) : params.mask;
|
const mask = typeof params.mask === "string" ? await fileFromUrl(params.mask) : params.mask;
|
||||||
|
|
||||||
const response = await client.images.edit({
|
const response = await client.images.edit(
|
||||||
image: file,
|
{
|
||||||
prompt: params.prompt,
|
image: file,
|
||||||
mask: mask,
|
prompt: params.prompt,
|
||||||
n: params.n,
|
mask: mask,
|
||||||
size: params.size,
|
n: params.n,
|
||||||
response_format: params.response_format,
|
size: params.size,
|
||||||
user: params.user,
|
response_format: params.response_format,
|
||||||
});
|
user: params.user,
|
||||||
|
},
|
||||||
|
{ idempotencyKey: task.idempotencyKey }
|
||||||
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
@@ -182,13 +185,16 @@ export class Images {
|
|||||||
const file =
|
const file =
|
||||||
typeof params.image === "string" ? await fileFromUrl(params.image) : params.image;
|
typeof params.image === "string" ? await fileFromUrl(params.image) : params.image;
|
||||||
|
|
||||||
const response = await client.images.createVariation({
|
const response = await client.images.createVariation(
|
||||||
image: file,
|
{
|
||||||
n: params.n,
|
image: file,
|
||||||
size: params.size,
|
n: params.n,
|
||||||
response_format: params.response_format,
|
size: params.size,
|
||||||
user: params.user,
|
response_format: params.response_format,
|
||||||
});
|
user: params.user,
|
||||||
|
},
|
||||||
|
{ idempotencyKey: task.idempotencyKey }
|
||||||
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export class OpenAI implements TriggerIntegration {
|
|||||||
this.native = new OpenAIApi({
|
this.native = new OpenAIApi({
|
||||||
apiKey: options.apiKey,
|
apiKey: options.apiKey,
|
||||||
organization: options.organization,
|
organization: options.organization,
|
||||||
|
baseURL: options.baseURL,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ export class OpenAI implements TriggerIntegration {
|
|||||||
openai._client = new OpenAIApi({
|
openai._client = new OpenAIApi({
|
||||||
apiKey,
|
apiKey,
|
||||||
organization: this._options.organization,
|
organization: this._options.organization,
|
||||||
|
baseURL: this._options.baseURL ?? "https://api.openai.com/v1",
|
||||||
});
|
});
|
||||||
return openai;
|
return openai;
|
||||||
}
|
}
|
||||||
@@ -104,7 +106,7 @@ export class OpenAI implements TriggerIntegration {
|
|||||||
return callback(this._client, task, io);
|
return callback(this._client, task, io);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "openai",
|
icon: this._options.icon ?? "openai",
|
||||||
retry: retry.standardBackoff,
|
retry: retry.standardBackoff,
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
connectionKey: this._connectionKey,
|
connectionKey: this._connectionKey,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ export type OpenAIIntegrationOptions = {
|
|||||||
id: string;
|
id: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
baseURL?: string;
|
||||||
|
icon?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenAIIntegrationAuth = Omit<OpenAIIntegrationOptions, "id">;
|
export type OpenAIIntegrationAuth = Omit<OpenAIIntegrationOptions, "id">;
|
||||||
|
|||||||
@@ -54,11 +54,6 @@ client.defineJob({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await io.openai.backgroundCreateCompletion("background-completion", {
|
|
||||||
model: "text-davinci-003",
|
|
||||||
prompt: "Create a good programming joke about Tasks",
|
|
||||||
});
|
|
||||||
|
|
||||||
await io.openai.createCompletion("completion", {
|
await io.openai.createCompletion("completion", {
|
||||||
model: "text-davinci-003",
|
model: "text-davinci-003",
|
||||||
prompt: "Create a good programming joke about Tasks",
|
prompt: "Create a good programming joke about Tasks",
|
||||||
@@ -77,4 +72,44 @@ client.defineJob({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const perplexity = new OpenAI({
|
||||||
|
id: "perplexity",
|
||||||
|
apiKey: process.env["PERPLEXITY_API_KEY"]!,
|
||||||
|
baseURL: "https://api.perplexity.ai",
|
||||||
|
icon: "brand-open-source",
|
||||||
|
});
|
||||||
|
|
||||||
|
client.defineJob({
|
||||||
|
id: "perplexity-tasks",
|
||||||
|
name: "Perplexity Tasks",
|
||||||
|
version: "0.0.1",
|
||||||
|
trigger: eventTrigger({
|
||||||
|
name: "perplexity.tasks",
|
||||||
|
}),
|
||||||
|
integrations: {
|
||||||
|
perplexity,
|
||||||
|
},
|
||||||
|
run: async (payload, io, ctx) => {
|
||||||
|
await io.perplexity.createChatCompletion("chat-completion", {
|
||||||
|
model: "mistral-7b-instruct",
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: "Create a good programming joke about background jobs",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await io.perplexity.backgroundCreateChatCompletion("background-chat-completion", {
|
||||||
|
model: "mistral-7b-instruct",
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: "If you were a programming language, what would you be and why?",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
createExpressServer(client);
|
createExpressServer(client);
|
||||||
|
|||||||
Reference in New Issue
Block a user