chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import rootConfig from '../../eslint.config.mjs'
|
||||
|
||||
export default [
|
||||
...rootConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
# OpenAI Integration
|
||||
|
||||
This integration allows your bot to choose from a curated list of models from [OpenAI](https://openai.com/) for content generation (LLM), image generation (text-to-image), and audio transcription (speech-to-text).
|
||||
|
||||
Usage is charged to the AI Spend of your workspace in Botpress Cloud at the [same pricing](https://openai.com/api/pricing/) (at cost) as directly with OpenAI.
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg"><title>OpenAI icon</title><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,111 @@
|
||||
import { IntegrationDefinition, z } from '@botpress/sdk'
|
||||
import llm from './bp_modules/llm'
|
||||
import stt from './bp_modules/speech-to-text'
|
||||
import tti from './bp_modules/text-to-image'
|
||||
import { languageModelId } from './src/schemas'
|
||||
|
||||
const TextToSpeechModels = ['tts-1', 'tts-1-hd'] as const
|
||||
type TextToSpeechModel = (typeof TextToSpeechModels)[number]
|
||||
export const TextToSpeechPricePer1MCharacters: Record<TextToSpeechModel, number> = {
|
||||
// Price is in U.S. dollars
|
||||
'tts-1': 15,
|
||||
'tts-1-hd': 30,
|
||||
}
|
||||
|
||||
export default new IntegrationDefinition({
|
||||
name: 'openai',
|
||||
title: 'OpenAI',
|
||||
description:
|
||||
'Gain access to OpenAI models for text generation, speech synthesis, audio transcription, and image generation.',
|
||||
version: '20.0.0',
|
||||
readme: 'hub.md',
|
||||
icon: 'icon.svg',
|
||||
entities: {
|
||||
modelRef: {
|
||||
schema: z.object({
|
||||
id: languageModelId,
|
||||
}),
|
||||
},
|
||||
imageModelRef: {
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
},
|
||||
speechToTextModelRef: {
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
},
|
||||
imageGenerationParams: {
|
||||
schema: z.object({
|
||||
style: z
|
||||
.enum(['natural', 'vivid'])
|
||||
.default('vivid')
|
||||
.describe('Image style - Only supported by DALL-E 3 models'),
|
||||
user: z.string().optional().describe('User ID to associate with the image, for abuse detection purposes'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
OPENAI_API_KEY: {
|
||||
description: 'OpenAI API key',
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
generateSpeech: {
|
||||
title: 'Generate Speech',
|
||||
description: 'Generate Speech',
|
||||
billable: true,
|
||||
cacheable: true,
|
||||
input: {
|
||||
schema: z.object({
|
||||
model: z.enum(TextToSpeechModels).optional().placeholder('tts-1').describe('The model used').title('Model'),
|
||||
input: z.string().describe('The input').title('Input'),
|
||||
voice: z
|
||||
.enum(['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'])
|
||||
.optional()
|
||||
.placeholder('alloy')
|
||||
.describe('The voice used')
|
||||
.title('Voice'),
|
||||
format: z
|
||||
.enum(['mp3', 'opus', 'aac', 'flac', 'wav', 'pcm'])
|
||||
.optional()
|
||||
.placeholder('mp3')
|
||||
.describe('The format used')
|
||||
.title('Format'),
|
||||
speed: z.number().min(0.25).max(4).optional().placeholder('1').describe('The speed used').title('Spedd'),
|
||||
expiration: z
|
||||
.number()
|
||||
.int()
|
||||
.min(30)
|
||||
.max(90)
|
||||
.optional()
|
||||
.describe(
|
||||
'Expiration of the generated audio file in days, after which the file will be automatically deleted to free up storage space in your account. The default is to keep the file indefinitely (no expiration). The minimum is 30 days and the maximum is 90 days.'
|
||||
)
|
||||
.title('Expiration'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
audioUrl: z.string().describe('URL to the audio file with the generated speech').title('Audio Url'),
|
||||
botpress: z
|
||||
.object({
|
||||
cost: z.number().describe('Cost of the speech generation, in U.S. dollars').title('Cost'),
|
||||
})
|
||||
.describe('Cost of the speech generation, in U.S. dollars')
|
||||
.title('Botpress'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
category: 'AI Models',
|
||||
repo: 'botpress',
|
||||
},
|
||||
})
|
||||
.extend(llm, ({ entities: { modelRef } }) => ({ entities: { modelRef } }))
|
||||
.extend(tti, ({ entities: { imageModelRef, imageGenerationParams } }) => ({
|
||||
entities: { imageModelRef, imageGenerationParams },
|
||||
}))
|
||||
.extend(stt, ({ entities: { speechToTextModelRef } }) => ({ entities: { speechToTextModelRef } }))
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@botpresshub/openai",
|
||||
"scripts": {
|
||||
"build": "bp add -y && bp build",
|
||||
"check:type": "tsc --noEmit",
|
||||
"check:bplint": "bp lint",
|
||||
"test": "vitest --run"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@botpress/client": "workspace:*",
|
||||
"@botpress/common": "workspace:*",
|
||||
"@botpress/sdk": "workspace:*",
|
||||
"openai": "^6.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@botpress/cli": "workspace:*",
|
||||
"@botpress/sdk": "workspace:*",
|
||||
"@botpresshub/llm": "workspace:*",
|
||||
"@botpresshub/speech-to-text": "workspace:*",
|
||||
"@botpresshub/text-to-image": "workspace:*"
|
||||
},
|
||||
"bpDependencies": {
|
||||
"llm": "../../interfaces/llm",
|
||||
"speech-to-text": "../../interfaces/speech-to-text",
|
||||
"text-to-image": "../../interfaces/text-to-image"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
import { InvalidPayloadError } from '@botpress/client'
|
||||
import { llm, speechToText, textToImage } from '@botpress/common'
|
||||
import { validateOpenAIReasoningEffort } from '@botpress/common/src/llm/openai'
|
||||
import crypto from 'crypto'
|
||||
import { TextToSpeechPricePer1MCharacters } from 'integration.definition'
|
||||
import OpenAI from 'openai'
|
||||
import { ImageGenerateParams, Images } from 'openai/resources'
|
||||
import { SpeechCreateParams } from 'openai/resources/audio/speech'
|
||||
import { ChatCompletionReasoningEffort } from 'openai/resources/chat/completions'
|
||||
import { LanguageModelId, ImageModelId, SpeechToTextModelId } from './schemas'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
const openAIClient = new OpenAI({
|
||||
apiKey: bp.secrets.OPENAI_API_KEY,
|
||||
})
|
||||
|
||||
const DEFAULT_LANGUAGE_MODEL_ID: LanguageModelId = 'gpt-4o-mini-2024-07-18'
|
||||
const DEFAULT_IMAGE_MODEL_ID: ImageModelId = 'dall-e-3-standard-1024'
|
||||
|
||||
// References:
|
||||
// https://platform.openai.com/docs/models
|
||||
// https://openai.com/api/pricing/
|
||||
const languageModels: Record<LanguageModelId, llm.ModelDetails> = {
|
||||
// IMPORTANT: Only full model names should be supported here, as the short model names can be pointed by OpenAI at any time to a newer model with different pricing.
|
||||
'gpt-5.4-2026-03-05': {
|
||||
name: 'GPT-5.4',
|
||||
description:
|
||||
'GPT-5.4 is the latest frontier model in the GPT-5 series, featuring a 1M+ context window and adaptive reasoning. It delivers state-of-the-art performance on professional knowledge work, coding, and agentic tasks with improved long-context understanding.',
|
||||
tags: ['recommended', 'reasoning', 'general-purpose', 'vision', 'coding', 'agents'],
|
||||
input: {
|
||||
costPer1MTokens: 2.5,
|
||||
maxTokens: 1_047_576,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 15,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
},
|
||||
'gpt-5.3-2026-02-06': {
|
||||
name: 'GPT-5.3',
|
||||
description:
|
||||
'GPT-5.3 is a frontier model in the GPT-5 series, featuring a 1M+ context window and adaptive reasoning. It offers improved coding, agentic, and long-context performance over GPT-5.2, with enhanced instruction-following and reduced hallucinations.',
|
||||
tags: ['recommended', 'reasoning', 'general-purpose', 'vision', 'coding', 'agents'],
|
||||
input: {
|
||||
costPer1MTokens: 2,
|
||||
maxTokens: 1_047_576,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 14,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
},
|
||||
'gpt-5.2-2025-12-11': {
|
||||
name: 'GPT-5.2',
|
||||
description:
|
||||
'GPT-5.2 is the latest frontier-grade model in the GPT-5 series, offering stronger agentic and long context perfomance compared to GPT-5.1. It uses adaptive reasoning to allocate computation dynamically, responding quickly to simple queries while spending more depth on complex tasks.',
|
||||
tags: ['recommended', 'reasoning', 'general-purpose', 'vision'],
|
||||
input: {
|
||||
costPer1MTokens: 1.75,
|
||||
maxTokens: 400_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 14,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
},
|
||||
'gpt-5.1-2025-11-13': {
|
||||
name: 'GPT-5.1',
|
||||
description:
|
||||
"GPT-5.1 is OpenAI's latest and most advanced AI model. It is a reasoning model that chooses the best way to respond based on task complexity and user intent. GPT-5.1 delivers expert-level performance across coding, math, writing, health, and visual perception, with improved accuracy, speed, and reduced hallucinations. It excels in complex tasks, long-context understanding, multimodal inputs (text and images), and safe, nuanced responses.",
|
||||
tags: ['recommended', 'reasoning', 'general-purpose', 'vision'],
|
||||
input: {
|
||||
costPer1MTokens: 1.25,
|
||||
maxTokens: 400_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 10,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
},
|
||||
'gpt-5-2025-08-07': {
|
||||
name: 'GPT-5',
|
||||
description:
|
||||
'GPT-5 is a reasoning model that chooses the best way to respond based on task complexity and user intent. GPT-5 delivers expert-level performance across coding, math, writing, health, and visual perception, with improved accuracy, speed, and reduced hallucinations. It excels in complex tasks, long-context understanding, multimodal inputs (text and images), and safe, nuanced responses.',
|
||||
tags: ['recommended', 'reasoning', 'general-purpose', 'vision'],
|
||||
input: {
|
||||
costPer1MTokens: 1.25,
|
||||
maxTokens: 400_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 10,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
},
|
||||
'gpt-5-mini-2025-08-07': {
|
||||
name: 'GPT-5 Mini',
|
||||
description:
|
||||
'GPT-5 Mini is a lightweight and cost-effective version of GPT-5, optimized for applications where speed and efficiency matter more than full advanced capabilities. It is designed for cost-sensitive use cases such as chatbots, content generation, and high-volume usage, striking a balance between performance and affordability, making it suitable for simpler tasks that do not require deep multi-step reasoning or the full reasoning power of GPT-5',
|
||||
tags: ['recommended', 'reasoning', 'general-purpose', 'vision'],
|
||||
input: {
|
||||
costPer1MTokens: 0.25,
|
||||
maxTokens: 400_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 2,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
},
|
||||
'gpt-5-nano-2025-08-07': {
|
||||
name: 'GPT-5 Nano',
|
||||
description:
|
||||
'GPT-5 Nano is an ultra-lightweight version of GPT-5 optimized for speed and very low latency, making it ideal for use cases like simple chatbots, basic content generation, summarization, and classification tasks.',
|
||||
tags: ['low-cost', 'reasoning', 'general-purpose', 'vision'],
|
||||
input: {
|
||||
costPer1MTokens: 0.05,
|
||||
maxTokens: 400_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 0.4,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
},
|
||||
'o4-mini-2025-04-16': {
|
||||
name: 'o4-mini',
|
||||
description:
|
||||
"o4-mini is OpenAI's latest small o-series model. It's optimized for fast, effective reasoning with exceptionally efficient performance in coding and visual tasks.",
|
||||
tags: ['reasoning', 'vision'],
|
||||
input: {
|
||||
costPer1MTokens: 1.1,
|
||||
maxTokens: 200_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 4.4,
|
||||
maxTokens: 100_000,
|
||||
},
|
||||
},
|
||||
'o3-2025-04-16': {
|
||||
name: 'o3',
|
||||
description:
|
||||
'o3 is a well-rounded and powerful model across domains. It sets a new standard for math, science, coding, and visual reasoning tasks. It also excels at technical writing and instruction-following. Use it to think through multi-step problems that involve analysis across text, code, and images.',
|
||||
tags: ['reasoning', 'vision'],
|
||||
input: {
|
||||
costPer1MTokens: 10,
|
||||
maxTokens: 200_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 40,
|
||||
maxTokens: 100_000,
|
||||
},
|
||||
},
|
||||
'gpt-4.1-2025-04-14': {
|
||||
name: 'GPT-4.1',
|
||||
description:
|
||||
'GPT 4.1 is a model suited for complex tasks and problem solving across domains. The knowledge cutoff is June 2024.',
|
||||
tags: ['recommended', 'vision', 'general-purpose'],
|
||||
input: {
|
||||
costPer1MTokens: 2,
|
||||
maxTokens: 1_047_576,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 8,
|
||||
maxTokens: 32768,
|
||||
},
|
||||
},
|
||||
'gpt-4.1-mini-2025-04-14': {
|
||||
name: 'GPT-4.1 Mini',
|
||||
description:
|
||||
'GPT 4.1 mini provides a balance between intelligence, speed, and cost that makes it an attractive model for many use cases. The knowledge cutoff is June 2024.',
|
||||
tags: ['recommended', 'vision', 'general-purpose'],
|
||||
input: {
|
||||
costPer1MTokens: 0.4,
|
||||
maxTokens: 1_047_576,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 1.6,
|
||||
maxTokens: 32768,
|
||||
},
|
||||
},
|
||||
'gpt-4.1-nano-2025-04-14': {
|
||||
name: 'GPT-4.1 Nano',
|
||||
description: 'GPT-4.1 nano is the fastest, most cost-effective GPT 4.1 model. The knowledge cutoff is June 2024.',
|
||||
tags: ['low-cost', 'vision', 'general-purpose'],
|
||||
input: {
|
||||
costPer1MTokens: 0.1,
|
||||
maxTokens: 1_047_576,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 0.4,
|
||||
maxTokens: 32768,
|
||||
},
|
||||
},
|
||||
'o3-mini-2025-01-31': {
|
||||
name: 'o3-mini',
|
||||
description:
|
||||
'o3-mini is a small reasoning model, providing high intelligence at the same cost and latency targets of o1-mini. o3-mini also supports key developer features, like Structured Outputs, function calling, Batch API, and more. Like other models in the o-series, it is designed to excel at science, math, and coding tasks. The knowledge cutoff for o3-mini models is October, 2023.',
|
||||
tags: ['reasoning', 'general-purpose'],
|
||||
input: {
|
||||
costPer1MTokens: 1.1,
|
||||
maxTokens: 200_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 4.4,
|
||||
maxTokens: 100_000,
|
||||
},
|
||||
},
|
||||
'o1-2024-12-17': {
|
||||
name: 'o1',
|
||||
description:
|
||||
'The o1 model is designed to solve hard problems across domains. The o1 series of models are trained with reinforcement learning to perform complex reasoning. o1 models think before they answer, producing a long internal chain of thought before responding to the user.',
|
||||
tags: ['reasoning', 'vision', 'general-purpose'],
|
||||
input: {
|
||||
costPer1MTokens: 15,
|
||||
maxTokens: 200_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 60,
|
||||
maxTokens: 100_000,
|
||||
},
|
||||
},
|
||||
'o1-mini-2024-09-12': {
|
||||
name: 'o1-mini',
|
||||
description:
|
||||
'The o1-mini model is a fast and affordable reasoning model for specialized tasks. The o1 series of models are trained with reinforcement learning to perform complex reasoning. o1 models think before they answer, producing a long internal chain of thought before responding to the user.',
|
||||
tags: ['reasoning', 'vision', 'general-purpose'],
|
||||
input: {
|
||||
costPer1MTokens: 3,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 12,
|
||||
maxTokens: 65_536,
|
||||
},
|
||||
},
|
||||
'gpt-4o-mini-2024-07-18': {
|
||||
name: 'GPT-4o Mini',
|
||||
description:
|
||||
"GPT-4o mini (“o” for “omni”) is an advanced model in the small models category, and their cheapest model yet. It is multimodal (accepting text or image inputs and outputting text), has higher intelligence than gpt-3.5-turbo but is just as fast. It is meant to be used for smaller tasks, including vision tasks. It's recommended to choose gpt-4o-mini where you would have previously used gpt-3.5-turbo as this model is more capable and cheaper.",
|
||||
tags: ['recommended', 'vision', 'low-cost', 'general-purpose', 'function-calling'],
|
||||
input: {
|
||||
costPer1MTokens: 0.15,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 0.6,
|
||||
maxTokens: 16_384,
|
||||
},
|
||||
},
|
||||
'gpt-4o-2024-11-20': {
|
||||
name: 'GPT-4o (November 2024)',
|
||||
description:
|
||||
'GPT-4o (“o” for “omni”) is a multimodal model (accepting text or image inputs and outputting text), and it has the same high intelligence as GPT-4 Turbo but is cheaper and more efficient.',
|
||||
tags: ['recommended', 'vision', 'general-purpose', 'coding', 'agents', 'function-calling'],
|
||||
input: {
|
||||
costPer1MTokens: 2.5,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 10,
|
||||
maxTokens: 16_384,
|
||||
},
|
||||
},
|
||||
'gpt-4o-2024-08-06': {
|
||||
name: 'GPT-4o (August 2024)',
|
||||
description:
|
||||
'GPT-4o (“o” for “omni”) is a multimodal model (accepting text or image inputs and outputting text), and it has the same high intelligence as GPT-4 Turbo but is cheaper and more efficient.',
|
||||
tags: ['deprecated', 'vision', 'general-purpose', 'coding', 'agents', 'function-calling'],
|
||||
input: {
|
||||
costPer1MTokens: 2.5,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 10,
|
||||
maxTokens: 16_384,
|
||||
},
|
||||
},
|
||||
'gpt-4o-2024-05-13': {
|
||||
name: 'GPT-4o (May 2024)',
|
||||
description:
|
||||
'GPT-4o (“o” for “omni”) is a multimodal model (accepting text or image inputs and outputting text), and it has the same high intelligence as GPT-4 Turbo but is cheaper and more efficient.',
|
||||
tags: ['deprecated', 'vision', 'general-purpose', 'coding', 'agents', 'function-calling'],
|
||||
input: {
|
||||
costPer1MTokens: 5,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 15,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'gpt-4-turbo-2024-04-09': {
|
||||
name: 'GPT-4 Turbo',
|
||||
description:
|
||||
'GPT-4 is a large multimodal model (accepting text or image inputs and outputting text) that can solve difficult problems with greater accuracy than any of our previous models, thanks to its broader general knowledge and advanced reasoning capabilities.',
|
||||
tags: ['deprecated', 'general-purpose', 'coding', 'agents', 'function-calling'],
|
||||
input: {
|
||||
costPer1MTokens: 10,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 30,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'gpt-3.5-turbo-0125': {
|
||||
name: 'GPT-3.5 Turbo',
|
||||
description:
|
||||
'GPT-3.5 Turbo can understand and generate natural language or code and has been optimized for chat but works well for non-chat tasks as well.',
|
||||
tags: ['deprecated', 'general-purpose', 'low-cost'],
|
||||
input: {
|
||||
costPer1MTokens: 0.5,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 1.5,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const imageModels: Record<ImageModelId, textToImage.ImageModelDetails> = {
|
||||
'dall-e-3-standard-1024': {
|
||||
name: 'DALL-E 3 Standard 1024',
|
||||
costPerImage: 0.04,
|
||||
sizes: ['1024x1024'],
|
||||
defaultSize: '1024x1024',
|
||||
},
|
||||
'dall-e-3-standard-1792': {
|
||||
name: 'DALL-E 3 Standard 1792',
|
||||
costPerImage: 0.08,
|
||||
sizes: ['1024x1792', '1792x1024'],
|
||||
defaultSize: '1024x1792',
|
||||
},
|
||||
'dall-e-3-hd-1024': {
|
||||
name: 'DALL-E 3 HD 1024',
|
||||
costPerImage: 0.08,
|
||||
sizes: ['1024x1024'],
|
||||
defaultSize: '1024x1024',
|
||||
},
|
||||
'dall-e-3-hd-1792': {
|
||||
name: 'DALL-E 3 HD 1792',
|
||||
costPerImage: 0.12,
|
||||
sizes: ['1024x1792', '1792x1024'],
|
||||
defaultSize: '1024x1792',
|
||||
},
|
||||
'dall-e-2-256': {
|
||||
name: 'DALL-E 2 256',
|
||||
costPerImage: 0.016,
|
||||
sizes: ['256x256'],
|
||||
defaultSize: '256x256',
|
||||
},
|
||||
'dall-e-2-512': {
|
||||
name: 'DALL-E 2 512',
|
||||
costPerImage: 0.018,
|
||||
sizes: ['512x512'],
|
||||
defaultSize: '512x512',
|
||||
},
|
||||
'dall-e-2-1024': {
|
||||
name: 'DALL-E 2 1024',
|
||||
costPerImage: 0.02,
|
||||
sizes: ['1024x1024'],
|
||||
defaultSize: '1024x1024',
|
||||
},
|
||||
}
|
||||
|
||||
const speechToTextModels: Record<SpeechToTextModelId, speechToText.SpeechToTextModelDetails> = {
|
||||
'whisper-1': {
|
||||
name: 'Whisper V2',
|
||||
costPerMinute: 0.006,
|
||||
},
|
||||
}
|
||||
|
||||
const SECONDS_IN_A_DAY = 24 * 60 * 60
|
||||
|
||||
const provider = 'OpenAI'
|
||||
|
||||
// oxlint-disable-next-line no-unused-vars
|
||||
const SupportedReasoningEfforts = ['minimal', 'low', 'medium', 'high'] as ChatCompletionReasoningEffort[]
|
||||
|
||||
export default new bp.Integration({
|
||||
register: async () => {},
|
||||
unregister: async () => {},
|
||||
actions: {
|
||||
generateContent: async ({ input, logger, metadata }) => {
|
||||
const output = await llm.openai.generateContent<LanguageModelId>(
|
||||
<llm.GenerateContentInput>input,
|
||||
openAIClient,
|
||||
logger,
|
||||
{
|
||||
provider,
|
||||
models: languageModels,
|
||||
defaultModel: DEFAULT_LANGUAGE_MODEL_ID,
|
||||
overrideRequest: (request) => {
|
||||
const isReasoningModel =
|
||||
input.model?.id.startsWith('gpt-5.4-') ||
|
||||
input.model?.id.startsWith('gpt-5.3-') ||
|
||||
input.model?.id.startsWith('gpt-5.2-') ||
|
||||
input.model?.id.startsWith('gpt-5.1-') ||
|
||||
input.model?.id.startsWith('gpt-5-') ||
|
||||
input.model?.id.startsWith('o4-') ||
|
||||
input.model?.id.startsWith('o3-') ||
|
||||
input.model?.id.startsWith('o1-')
|
||||
|
||||
if (isReasoningModel) {
|
||||
if (input.reasoningEffort) {
|
||||
request.reasoning_effort = validateOpenAIReasoningEffort(input, logger)
|
||||
} else {
|
||||
if (
|
||||
input.model?.id.startsWith('gpt-5.4-') ||
|
||||
input.model?.id.startsWith('gpt-5.3-') ||
|
||||
input.model?.id.startsWith('gpt-5.2-') ||
|
||||
input.model?.id.startsWith('gpt-5.1-')
|
||||
) {
|
||||
// GPT-5.1, GPT-5.2, and GPT-5.4 are hybrid reasoning models that supports optional reasoning, so if no reasoning effort is specified we assume the user doesn't want the model to do reasoning (to reduce cost/latency).
|
||||
request.reasoning_effort = 'none'
|
||||
} else if (input.model?.id.startsWith('gpt-5-')) {
|
||||
// GPT-5 is a hybrid model but it doesn't support optional reasoning, so if reasoning effort isn't specified we assume the user wants to use the least amount of reasoning possible (to reduce cost/latency).
|
||||
request.reasoning_effort = 'minimal'
|
||||
}
|
||||
// For other reasoning models we leave the reasoning effort undefined so it uses the default effort specified by the provider.
|
||||
}
|
||||
|
||||
// Reasoning models don't support stop sequences
|
||||
delete request.stop
|
||||
|
||||
if (request.reasoning_effort !== 'none') {
|
||||
// Temperature is not supported when using reasoning
|
||||
delete request.temperature
|
||||
}
|
||||
}
|
||||
return request
|
||||
},
|
||||
}
|
||||
)
|
||||
metadata.setCost(output.botpress.cost)
|
||||
return output
|
||||
},
|
||||
generateImage: async ({ input, client, metadata }) => {
|
||||
const imageModelId = (input.model?.id ?? DEFAULT_IMAGE_MODEL_ID) as ImageModelId
|
||||
const imageModel = imageModels[imageModelId]
|
||||
if (!imageModel) {
|
||||
throw new InvalidPayloadError(
|
||||
`Model ID "${imageModelId}" is not allowed by this integration, supported model IDs are: ${Object.keys(
|
||||
imageModels
|
||||
).join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
const size = (input.size || imageModel.defaultSize) as NonNullable<ImageGenerateParams['size']>
|
||||
|
||||
if (!imageModel.sizes.includes(size)) {
|
||||
throw new InvalidPayloadError(
|
||||
`Size "${
|
||||
input.size
|
||||
}" is not allowed by the "${imageModelId}" model, supported sizes are: ${imageModel.sizes.join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
const { model, quality } = getOpenAIImageGenerationParams(imageModelId)
|
||||
|
||||
const result = await openAIClient.images.generate({
|
||||
model,
|
||||
size,
|
||||
quality,
|
||||
prompt: input.prompt,
|
||||
style: input.params?.style,
|
||||
user: input.params?.user,
|
||||
response_format: 'url',
|
||||
})
|
||||
|
||||
const temporaryImageUrl = result.data?.[0]?.url
|
||||
if (!temporaryImageUrl) {
|
||||
throw new Error('No image was returned by OpenAI')
|
||||
}
|
||||
|
||||
const expiresAt: string | undefined = input.expiration
|
||||
? new Date(Date.now() + input.expiration * SECONDS_IN_A_DAY * 1000).toISOString()
|
||||
: undefined
|
||||
|
||||
// File storage is billed to the workspace of the bot that called this action.
|
||||
const { file } = await client.uploadFile({
|
||||
key: generateFileKey('openai-generateImage-', input, '.png'),
|
||||
url: temporaryImageUrl,
|
||||
contentType: 'image/png',
|
||||
accessPolicies: ['public_content'],
|
||||
tags: {
|
||||
source: 'integration',
|
||||
integration: 'openai',
|
||||
action: 'generateImage',
|
||||
},
|
||||
expiresAt,
|
||||
publicContentImmediatelyAccessible: true,
|
||||
})
|
||||
|
||||
const cost = imageModel.costPerImage
|
||||
metadata.setCost(cost)
|
||||
return {
|
||||
model: imageModelId,
|
||||
imageUrl: file.url,
|
||||
cost, // DEPRECATED
|
||||
botpress: {
|
||||
cost, // DEPRECATED
|
||||
},
|
||||
}
|
||||
},
|
||||
transcribeAudio: async ({ input, logger, metadata }) => {
|
||||
const output = await speechToText.openai.transcribeAudio(input, openAIClient, logger, {
|
||||
provider,
|
||||
models: speechToTextModels,
|
||||
defaultModel: 'whisper-1',
|
||||
})
|
||||
|
||||
metadata.setCost(output.botpress.cost)
|
||||
return output
|
||||
},
|
||||
generateSpeech: async ({ input, client, metadata }) => {
|
||||
const model = input.model ?? 'tts-1'
|
||||
|
||||
const params: SpeechCreateParams = {
|
||||
model,
|
||||
input: input.input,
|
||||
voice: input.voice ?? 'alloy',
|
||||
response_format: input.format ?? 'mp3',
|
||||
speed: input.speed ?? 1,
|
||||
}
|
||||
|
||||
let response: Response
|
||||
|
||||
try {
|
||||
response = await openAIClient.audio.speech.create(params)
|
||||
} catch (err: any) {
|
||||
throw llm.createUpstreamProviderFailedError(err)
|
||||
}
|
||||
|
||||
const key = generateFileKey('openai-generateSpeech-', input, `.${params.response_format}`)
|
||||
|
||||
const expiresAt = input.expiration
|
||||
? new Date(Date.now() + input.expiration * SECONDS_IN_A_DAY * 1000).toISOString()
|
||||
: undefined
|
||||
|
||||
const { file } = await client.uploadFile({
|
||||
key,
|
||||
content: await response.arrayBuffer(),
|
||||
accessPolicies: ['public_content'],
|
||||
publicContentImmediatelyAccessible: true,
|
||||
tags: {
|
||||
source: 'integration',
|
||||
integration: 'openai',
|
||||
action: 'generateSpeech',
|
||||
},
|
||||
expiresAt,
|
||||
})
|
||||
|
||||
const cost = (input.input.length / 1_000_000) * TextToSpeechPricePer1MCharacters[model]
|
||||
metadata.setCost(cost)
|
||||
return {
|
||||
audioUrl: file.url,
|
||||
botpress: {
|
||||
cost, // DEPRECATED
|
||||
},
|
||||
}
|
||||
},
|
||||
listLanguageModels: async ({}) => {
|
||||
return {
|
||||
models: Object.entries(languageModels).map(([id, model]) => ({ id: <LanguageModelId>id, ...model })),
|
||||
}
|
||||
},
|
||||
listImageModels: async ({}) => {
|
||||
return {
|
||||
models: Object.entries(imageModels).map(([id, model]) => ({ id: <ImageModelId>id, ...model })),
|
||||
}
|
||||
},
|
||||
listSpeechToTextModels: async ({}) => {
|
||||
return {
|
||||
models: Object.entries(speechToTextModels).map(([id, model]) => ({ id: <ImageModelId>id, ...model })),
|
||||
}
|
||||
},
|
||||
},
|
||||
channels: {},
|
||||
handler: async () => {},
|
||||
})
|
||||
|
||||
function generateFileKey(prefix: string, input: object, suffix?: string) {
|
||||
const json = JSON.stringify(input)
|
||||
const hash = crypto.createHash('sha1')
|
||||
|
||||
hash.update(json)
|
||||
const hexHash = hash.digest('hex')
|
||||
|
||||
return prefix + Date.now() + '_' + hexHash + suffix
|
||||
}
|
||||
|
||||
function getOpenAIImageGenerationParams(modelId: ImageModelId): {
|
||||
model: Images.ImageGenerateParams['model']
|
||||
quality?: Images.ImageGenerateParams['quality']
|
||||
} {
|
||||
switch (modelId) {
|
||||
case 'dall-e-3-standard-1024':
|
||||
return { model: 'dall-e-3', quality: 'standard' }
|
||||
case 'dall-e-3-standard-1792':
|
||||
return { model: 'dall-e-3', quality: 'standard' }
|
||||
case 'dall-e-3-hd-1024':
|
||||
return { model: 'dall-e-3', quality: 'hd' }
|
||||
case 'dall-e-3-hd-1792':
|
||||
return { model: 'dall-e-3', quality: 'hd' }
|
||||
case 'dall-e-2-256':
|
||||
return { model: 'dall-e-2' }
|
||||
case 'dall-e-2-512':
|
||||
return { model: 'dall-e-2' }
|
||||
case 'dall-e-2-1024':
|
||||
return { model: 'dall-e-2' }
|
||||
default:
|
||||
throw new Error(`Invalid model ID: ${modelId}`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { z } from '@botpress/sdk'
|
||||
|
||||
export const languageModelId = z
|
||||
.enum([
|
||||
'gpt-5.4-2026-03-05',
|
||||
'gpt-5.3-2026-02-06',
|
||||
'gpt-5.2-2025-12-11',
|
||||
'gpt-5.1-2025-11-13',
|
||||
'gpt-5-2025-08-07',
|
||||
'gpt-5-mini-2025-08-07',
|
||||
'gpt-5-nano-2025-08-07',
|
||||
'o4-mini-2025-04-16',
|
||||
'o3-2025-04-16',
|
||||
'gpt-4.1-2025-04-14',
|
||||
'gpt-4.1-mini-2025-04-14',
|
||||
'gpt-4.1-nano-2025-04-14',
|
||||
'o3-mini-2025-01-31',
|
||||
'o1-2024-12-17',
|
||||
'o1-mini-2024-09-12',
|
||||
'gpt-4o-mini-2024-07-18',
|
||||
'gpt-4o-2024-11-20',
|
||||
'gpt-4o-2024-08-06',
|
||||
'gpt-4o-2024-05-13',
|
||||
'gpt-4-turbo-2024-04-09',
|
||||
'gpt-3.5-turbo-0125',
|
||||
])
|
||||
.describe('Model to use for content generation')
|
||||
.placeholder('gpt-4o-mini-2024-07-18')
|
||||
export type LanguageModelId = z.infer<typeof languageModelId>
|
||||
|
||||
export const imageModelId = z.enum([
|
||||
'dall-e-3-standard-1024',
|
||||
'dall-e-3-standard-1792',
|
||||
'dall-e-3-hd-1024',
|
||||
'dall-e-3-hd-1792',
|
||||
'dall-e-2-256',
|
||||
'dall-e-2-512',
|
||||
'dall-e-2-1024',
|
||||
])
|
||||
export type ImageModelId = z.infer<typeof imageModelId>
|
||||
|
||||
export const speechToTextModelId = z.enum(['whisper-1'])
|
||||
export type SpeechToTextModelId = z.infer<typeof speechToTextModelId>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": { "*": ["./*"] },
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import config from '../../vitest.config'
|
||||
export default config
|
||||
Reference in New Issue
Block a user