chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# Mistral AI Integration
|
||||
|
||||
This integration allows your bot to choose from a curated list of [Mistral AI Models](https://docs.mistral.ai/getting-started/models) as the LLM of your choice for a node, workflow, or skill in your bot.
|
||||
|
||||
Usage is charged to the AI Spend of your workspace in Botpress Cloud at the [same pricing](https://mistral.ai/pricing#api-pricing) (at cost) as directly with Mistral.
|
||||
@@ -0,0 +1,19 @@
|
||||
<svg width="241" height="241" viewBox="0 0 191 135" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_134_208)">
|
||||
<path d="M54.3221 0H27.1531V27.0892H54.3221V0Z" fill="#FFD800"/>
|
||||
<path d="M162.984 0H135.815V27.0892H162.984V0Z" fill="#FFD800"/>
|
||||
<path d="M81.4823 27.0913H27.1531V54.1805H81.4823V27.0913Z" fill="#FFAF00"/>
|
||||
<path d="M162.99 27.0913H108.661V54.1805H162.99V27.0913Z" fill="#FFAF00"/>
|
||||
<path d="M162.972 54.168H27.1531V81.2572H162.972V54.168Z" fill="#FF8205"/>
|
||||
<path d="M54.3221 81.2593H27.1531V108.349H54.3221V81.2593Z" fill="#FA500F"/>
|
||||
<path d="M108.661 81.2593H81.4917V108.349H108.661V81.2593Z" fill="#FA500F"/>
|
||||
<path d="M162.984 81.2593H135.815V108.349H162.984V81.2593Z" fill="#FA500F"/>
|
||||
<path d="M81.4879 108.339H-0.00146484V135.429H81.4879V108.339Z" fill="#E10500"/>
|
||||
<path d="M190.159 108.339H108.661V135.429H190.159V108.339Z" fill="#E10500"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_134_208">
|
||||
<rect width="190.141" height="135" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1001 B |
@@ -0,0 +1,30 @@
|
||||
import { IntegrationDefinition, z } from '@botpress/sdk'
|
||||
import { ModelId } from 'src/schemas'
|
||||
import llm from './bp_modules/llm'
|
||||
|
||||
export default new IntegrationDefinition({
|
||||
name: 'mistral-ai',
|
||||
title: 'Mistral AI',
|
||||
description: 'Access a curated list of Mistral AI models to set as your chosen LLM.',
|
||||
version: '1.0.0',
|
||||
readme: 'hub.md',
|
||||
icon: 'icon.svg',
|
||||
entities: {
|
||||
modelRef: {
|
||||
schema: z.object({
|
||||
id: ModelId,
|
||||
}),
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
MISTRAL_API_KEY: {
|
||||
description: 'Mistral AI API key',
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
category: 'AI Models',
|
||||
repo: 'botpress',
|
||||
},
|
||||
}).extend(llm, ({ entities }) => ({
|
||||
entities: { modelRef: entities.modelRef },
|
||||
}))
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@botpresshub/mistral-ai",
|
||||
"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:*",
|
||||
"@mistralai/mistralai": "^1.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@botpress/cli": "workspace:*",
|
||||
"@botpress/sdk": "workspace:*",
|
||||
"@botpresshub/llm": "workspace:*"
|
||||
},
|
||||
"bpDependencies": {
|
||||
"llm": "../../interfaces/llm"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
import { InvalidPayloadError } from '@botpress/client'
|
||||
import { llm } from '@botpress/common'
|
||||
import { IntegrationLogger, z } from '@botpress/sdk'
|
||||
import { Mistral } from '@mistralai/mistralai'
|
||||
import type {
|
||||
Messages,
|
||||
ChatCompletionRequest,
|
||||
ChatCompletionResponse,
|
||||
Tool,
|
||||
ToolChoice,
|
||||
ToolChoiceEnum,
|
||||
ContentChunk,
|
||||
ToolCall,
|
||||
FinishReason,
|
||||
} from '@mistralai/mistralai/models/components'
|
||||
import {
|
||||
SDKError,
|
||||
HTTPValidationError,
|
||||
ResponseValidationError,
|
||||
HTTPClientError,
|
||||
} from '@mistralai/mistralai/models/errors'
|
||||
import { ModelId } from 'src/schemas'
|
||||
|
||||
const MistralAPIErrorSchema = z.object({
|
||||
error: z
|
||||
.object({
|
||||
message: z.string(),
|
||||
type: z.string().optional(),
|
||||
code: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
message: z.string().optional(), // Some errors might have message at root
|
||||
detail: z
|
||||
.array(
|
||||
z.object({
|
||||
loc: z.array(z.union([z.string(), z.number()])),
|
||||
msg: z.string(),
|
||||
type: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(), // For 422 validation errors
|
||||
})
|
||||
|
||||
export async function generateContent(
|
||||
input: llm.GenerateContentInput,
|
||||
mistral: Mistral,
|
||||
logger: IntegrationLogger,
|
||||
params: {
|
||||
models: Record<ModelId, llm.ModelDetails>
|
||||
defaultModel: ModelId
|
||||
}
|
||||
): Promise<llm.GenerateContentOutput> {
|
||||
const modelId = (input.model?.id || params.defaultModel) as ModelId
|
||||
|
||||
const model = params.models[modelId]
|
||||
|
||||
if (!model) {
|
||||
throw new InvalidPayloadError(
|
||||
`Model ID "${modelId}" is not allowed, supported model IDs are: ${Object.keys(params.models).join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
if (input.messages.length === 0 && !input.systemPrompt) {
|
||||
throw new InvalidPayloadError('At least one message or a system prompt is required')
|
||||
}
|
||||
|
||||
if (input.maxTokens && input.maxTokens > model.output.maxTokens) {
|
||||
throw new InvalidPayloadError(
|
||||
`maxTokens must be less than or equal to ${model.output.maxTokens} for model ID "${modelId}`
|
||||
)
|
||||
}
|
||||
|
||||
if (input.responseFormat === 'json_object') {
|
||||
input.systemPrompt =
|
||||
(input.systemPrompt || '') +
|
||||
'\n\nYour response must always be in valid JSON format and expressed as a JSON object.'
|
||||
}
|
||||
|
||||
const messages: Messages[] = []
|
||||
|
||||
// Add system prompt
|
||||
if (input.systemPrompt) {
|
||||
messages.unshift({
|
||||
role: 'system',
|
||||
content: input.systemPrompt,
|
||||
})
|
||||
}
|
||||
|
||||
for (const message of input.messages) {
|
||||
messages.push(mapToMistralMessage(message))
|
||||
}
|
||||
|
||||
const request: ChatCompletionRequest = {
|
||||
model: modelId,
|
||||
maxTokens: input.maxTokens ?? model.output.maxTokens,
|
||||
temperature: input.temperature,
|
||||
topP: input.topP,
|
||||
stop: input.stopSequences,
|
||||
metadata: {
|
||||
user_id: input.userId,
|
||||
},
|
||||
tools: mapToMistralTools(input),
|
||||
toolChoice: mapToMistralToolChoice(input.toolChoice),
|
||||
messages,
|
||||
}
|
||||
|
||||
if (input.debug) {
|
||||
logger.forBot().info('Request being sent to Mistral: ' + JSON.stringify(request, null, 2))
|
||||
}
|
||||
|
||||
let response: ChatCompletionResponse | undefined
|
||||
|
||||
try {
|
||||
response = await mistral.chat.complete(request)
|
||||
} catch (thrown: unknown) {
|
||||
// Validation errors (422)
|
||||
if (thrown instanceof HTTPValidationError) {
|
||||
// err has: statusCode, body, detail[]
|
||||
if (thrown.detail && thrown.detail.length > 0) {
|
||||
const validationMessages = thrown.detail.map((d) => `${d.loc.join('.')}: ${d.msg}`).join('; ')
|
||||
|
||||
if (input.debug) {
|
||||
logger.forBot().error(`Mistral validation errors: ${JSON.stringify(thrown.detail, null, 2)}`)
|
||||
}
|
||||
|
||||
throw llm.createUpstreamProviderFailedError(
|
||||
thrown,
|
||||
`Mistral validation error (${thrown.statusCode}): ${validationMessages}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// General SDK/API errors
|
||||
if (thrown instanceof SDKError) {
|
||||
let errorMessage = thrown.message
|
||||
|
||||
// parse body for more details
|
||||
try {
|
||||
const parsedBody = JSON.parse(thrown.body)
|
||||
const parsedError = MistralAPIErrorSchema.safeParse(parsedBody)
|
||||
|
||||
if (parsedError.success && parsedError.data.error) {
|
||||
errorMessage = parsedError.data.error.message
|
||||
|
||||
input.debug && logger.forBot().error(`Mistral API error: ${JSON.stringify(parsedError.data, null, 2)}`)
|
||||
|
||||
const errorType = parsedError.data.error.type ? ` (${parsedError.data.error.type})` : ''
|
||||
throw llm.createUpstreamProviderFailedError(
|
||||
thrown,
|
||||
`Mistral error ${thrown.statusCode}${errorType}: ${errorMessage}`
|
||||
)
|
||||
}
|
||||
} catch (parseErr) {
|
||||
const parseErrorMessage = parseErr instanceof Error ? parseErr.message : String(parseErr)
|
||||
// use basic info
|
||||
if (input.debug) {
|
||||
logger.forBot().warn(`Could not parse Mistral error body: ${thrown.body}, parse error: ${parseErrorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
throw llm.createUpstreamProviderFailedError(thrown, `Mistral error ${thrown.statusCode}: ${errorMessage}`)
|
||||
}
|
||||
|
||||
// Response validation errors
|
||||
if (thrown instanceof ResponseValidationError) {
|
||||
// Response from Mistral was invalid/unexpected format
|
||||
if (input.debug) {
|
||||
logger.forBot().error(`Mistral response validation error: ${thrown.message}`)
|
||||
}
|
||||
|
||||
throw llm.createUpstreamProviderFailedError(thrown, `Mistral response validation error: ${thrown.message}`)
|
||||
}
|
||||
|
||||
// Network/client errors
|
||||
if (thrown instanceof HTTPClientError) {
|
||||
if (input.debug) {
|
||||
logger.forBot().error(`Mistral client error (${thrown.name}): ${thrown.message}`)
|
||||
}
|
||||
|
||||
throw llm.createUpstreamProviderFailedError(thrown, `Mistral client error (${thrown.name}): ${thrown.message}`)
|
||||
}
|
||||
|
||||
// unknown errors
|
||||
if (input.debug) {
|
||||
logger.forBot().error(`Unexpected error calling Mistral: ${JSON.stringify(thrown, null, 2)}`)
|
||||
}
|
||||
|
||||
const error = thrown instanceof Error ? thrown : Error(String(thrown))
|
||||
throw llm.createUpstreamProviderFailedError(error, `Mistral error: ${error.message}`)
|
||||
} finally {
|
||||
if (input.debug && response) {
|
||||
logger.forBot().info('Response received from Mistral: ' + JSON.stringify(response, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to zero, as it's done in the OpenAI integration
|
||||
const inputTokens = response.usage?.promptTokens ?? 0
|
||||
const outputTokens = response.usage?.completionTokens ?? 0
|
||||
|
||||
const inputCost = calculateTokenCost(model.input.costPer1MTokens, inputTokens)
|
||||
const outputCost = calculateTokenCost(model.output.costPer1MTokens, outputTokens)
|
||||
const cost = inputCost + outputCost
|
||||
|
||||
return {
|
||||
id: response.id,
|
||||
provider: 'mistral-ai',
|
||||
model: response.model,
|
||||
choices: response.choices.map((choice) => ({
|
||||
role: 'assistant',
|
||||
// TODO: Investigate showing images, for now it's not supported by any other provider
|
||||
type: 'text', // Mistral can return multimodal content, but we extract text only,
|
||||
content: extractTextContent(choice.message.content),
|
||||
index: choice.index,
|
||||
stopReason: mapToStopReason(choice.finishReason),
|
||||
toolCalls: mapFromMistralToolCalls(choice.message.toolCalls, logger),
|
||||
})),
|
||||
usage: {
|
||||
inputTokens,
|
||||
inputCost,
|
||||
outputTokens,
|
||||
outputCost,
|
||||
},
|
||||
botpress: {
|
||||
cost, // DEPRECATED
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function mapToMistralMessage(message: llm.Message): Messages {
|
||||
// Handle special messages where the role is overridden (tool calls)
|
||||
if (message.type === 'tool_result') {
|
||||
if (!message.toolResultCallId) {
|
||||
throw new InvalidPayloadError('`toolResultCallId` is required when message type is "tool_result"')
|
||||
}
|
||||
|
||||
return {
|
||||
role: 'tool',
|
||||
toolCallId: message.toolResultCallId,
|
||||
content: message.content as string,
|
||||
}
|
||||
} else if (message.type === 'tool_calls') {
|
||||
if (!message.toolCalls || message.toolCalls.length === 0) {
|
||||
throw new InvalidPayloadError('`toolCalls` must contain at least one tool call when type is "tool_calls"')
|
||||
}
|
||||
|
||||
return {
|
||||
role: 'assistant',
|
||||
toolCalls: message.toolCalls.map(mapToMistralToolCall),
|
||||
// content can be omitted or null for tool call messages
|
||||
}
|
||||
}
|
||||
|
||||
// Handle regular messages by role
|
||||
switch (message.role) {
|
||||
case 'user':
|
||||
case 'assistant':
|
||||
return mapStandardMessage(message)
|
||||
default:
|
||||
throw new InvalidPayloadError(`Message role "${message.role}" is not supported`)
|
||||
}
|
||||
}
|
||||
|
||||
function mapStandardMessage(message: llm.Message): Messages {
|
||||
if (message.type === 'text') {
|
||||
if (typeof message.content !== 'string') {
|
||||
throw new InvalidPayloadError('`content` must be a string when message type is "text"')
|
||||
}
|
||||
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === 'multipart') {
|
||||
if (!Array.isArray(message.content)) {
|
||||
throw new InvalidPayloadError('`content` must be an array when message type is "multipart"')
|
||||
}
|
||||
|
||||
return {
|
||||
role: message.role,
|
||||
content: mapMultipartContent(message.content),
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidPayloadError(`Message type "${message.type}" is not supported for ${message.role} messages`)
|
||||
}
|
||||
|
||||
/** Map multipart content into Mistral (ContentChunk) format */
|
||||
function mapMultipartContent(content: NonNullable<llm.Message['content']>): ContentChunk[] {
|
||||
if (typeof content === 'string') {
|
||||
throw new InvalidPayloadError('Content must be an array for multipart messages')
|
||||
}
|
||||
|
||||
const mistralContent: ContentChunk[] = []
|
||||
|
||||
for (const part of content) {
|
||||
if (part.type === 'text') {
|
||||
if (!part.text) {
|
||||
throw new InvalidPayloadError('`text` is required when part type is "text"')
|
||||
}
|
||||
|
||||
mistralContent.push({
|
||||
type: 'text',
|
||||
text: part.text,
|
||||
})
|
||||
} else if (part.type === 'image') {
|
||||
if (!part.url) {
|
||||
throw new InvalidPayloadError('`url` is required when part type is "image"')
|
||||
}
|
||||
mistralContent.push({
|
||||
type: 'image_url',
|
||||
imageUrl: part.url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return mistralContent
|
||||
}
|
||||
|
||||
function mapToMistralTools(input: llm.GenerateContentInput): Tool[] | undefined {
|
||||
if (input.toolChoice?.type === 'none') {
|
||||
// Don't return any tools if tool choice was to not use any tools
|
||||
return []
|
||||
}
|
||||
|
||||
const mistralTools = input.tools as Tool[] | undefined
|
||||
|
||||
// note: don't send an empty tools array
|
||||
return mistralTools?.length ? mistralTools : undefined
|
||||
}
|
||||
|
||||
function mapToMistralToolCall(toolCall: llm.ToolCall): ToolCall {
|
||||
return {
|
||||
id: toolCall.id,
|
||||
type: 'function',
|
||||
function: {
|
||||
name: toolCall.function.name,
|
||||
// Mistral expects a JSON string, not an object
|
||||
arguments: JSON.stringify(toolCall.function.arguments),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function mapToMistralToolChoice(
|
||||
toolChoice: llm.GenerateContentInput['toolChoice']
|
||||
): ToolChoice | ToolChoiceEnum | undefined {
|
||||
if (!toolChoice) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
switch (toolChoice.type) {
|
||||
case 'any':
|
||||
case 'auto':
|
||||
case 'none':
|
||||
return <ToolChoiceEnum>toolChoice.type
|
||||
case 'specific':
|
||||
return <ToolChoice>{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: toolChoice.functionName,
|
||||
},
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function calculateTokenCost(costPer1MTokens: number, tokenCount: number) {
|
||||
return (costPer1MTokens / 1_000_000) * tokenCount
|
||||
}
|
||||
|
||||
function mapToStopReason(mistralFinishReason: FinishReason): llm.GenerateContentOutput['choices'][0]['stopReason'] {
|
||||
switch (mistralFinishReason) {
|
||||
case 'stop':
|
||||
return 'stop'
|
||||
case 'length':
|
||||
case 'model_length':
|
||||
return 'max_tokens'
|
||||
case 'tool_calls':
|
||||
return 'tool_calls'
|
||||
case 'error':
|
||||
return 'other'
|
||||
default:
|
||||
return 'other'
|
||||
}
|
||||
}
|
||||
|
||||
function mapFromMistralToolCalls(
|
||||
mistralToolCalls: ToolCall[] | null | undefined,
|
||||
logger: IntegrationLogger
|
||||
): llm.ToolCall[] | undefined {
|
||||
if (!mistralToolCalls || mistralToolCalls.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return mistralToolCalls.reduce((toolCalls, mistralToolCall) => {
|
||||
if (!mistralToolCall.id) {
|
||||
logger.forBot().warn('Mistral returned tool call without ID, skipping')
|
||||
return toolCalls
|
||||
}
|
||||
const toolType = mistralToolCall.type || 'function' // Default to 'function' if not provided
|
||||
if (toolType !== 'function') {
|
||||
logger.forBot().warn(`Unsupported tool call type "${toolType}" from Mistral, skipping`)
|
||||
return toolCalls
|
||||
}
|
||||
|
||||
let toolCallArguments: llm.ToolCall['function']['arguments']
|
||||
const rawArguments = mistralToolCall.function.arguments
|
||||
// arguments can be either string or json
|
||||
if (typeof rawArguments === 'string') {
|
||||
try {
|
||||
toolCallArguments = JSON.parse(rawArguments)
|
||||
} catch (err) {
|
||||
logger
|
||||
.forBot()
|
||||
.warn(
|
||||
`Mistral returned invalid JSON for tool call "${mistralToolCall.function.name}" arguments. ` +
|
||||
`Using null instead. Error: ${err}`
|
||||
)
|
||||
toolCallArguments = null
|
||||
}
|
||||
} else if (typeof rawArguments === 'object' && rawArguments !== null) {
|
||||
toolCallArguments = rawArguments
|
||||
} else {
|
||||
logger
|
||||
.forBot()
|
||||
.warn(
|
||||
`Mistral returned unexpected type for tool call "${mistralToolCall.function.name}" arguments: ${typeof rawArguments}. Using null instead.`
|
||||
)
|
||||
toolCallArguments = null
|
||||
}
|
||||
toolCalls.push({
|
||||
id: mistralToolCall.id,
|
||||
type: 'function',
|
||||
function: {
|
||||
name: mistralToolCall.function.name,
|
||||
arguments: toolCallArguments,
|
||||
},
|
||||
})
|
||||
return toolCalls
|
||||
}, [] as llm.ToolCall[])
|
||||
}
|
||||
|
||||
function extractTextContent(content: string | ContentChunk[] | null | undefined): string | null {
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
if (typeof content === 'string') {
|
||||
return content
|
||||
}
|
||||
// content is ContentChunk[] - extract only text chunks
|
||||
return (
|
||||
content
|
||||
.filter((chunk): chunk is Extract<ContentChunk, { type: 'text' }> => chunk.type === 'text')
|
||||
.map((chunk) => chunk.text)
|
||||
.join('\n\n') || null
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { llm } from '@botpress/common'
|
||||
import { Mistral } from '@mistralai/mistralai'
|
||||
import { generateContent } from './actions/generate-content'
|
||||
import { DefaultModel, ModelId } from './schemas'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
const mistral = new Mistral({ apiKey: bp.secrets.MISTRAL_API_KEY })
|
||||
|
||||
const LanguageModels: Record<ModelId, llm.ModelDetails> = {
|
||||
// Reference: https://docs.mistral.ai/getting-started/models
|
||||
'mistral-large-2512': {
|
||||
name: 'Mistral Large 3',
|
||||
description:
|
||||
'Mistral Large 3, is a state-of-the-art, open-weight, general-purpose multimodal model with a granular Mixture-of-Experts architecture. It features 41B active parameters and 675B total parameters.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 0.5,
|
||||
maxTokens: 256_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 1.5,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'mistral-medium-2508': {
|
||||
name: 'Mistral Medium 3.1',
|
||||
description: 'Frontier-class multimodal model released August 2025. Improving tone and performance.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 0.4,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 2,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'mistral-small-2506': {
|
||||
name: 'Mistral Small 3.2',
|
||||
description: 'An update to the previous small model, released June 2025.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 0.1,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 0.3,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'ministral-14b-2512': {
|
||||
name: 'Ministral 3 14B',
|
||||
description:
|
||||
'Ministral 3 14B is the largest model in the Ministral 3 family, offering state-of-the-art capabilities and performance comparable to its larger Mistral Small 3.2 24B counterpart.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 0.2,
|
||||
maxTokens: 256_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 0.2,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'ministral-8b-2512': {
|
||||
name: 'Ministral 3 8B',
|
||||
description:
|
||||
'Ministral 3 8B is a powerful and efficient model in the Ministral 3 family, offering best-in-class text and vision capabilities.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 0.15,
|
||||
maxTokens: 256_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 0.15,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'ministral-3b-2512': {
|
||||
name: 'Ministral 3 3B',
|
||||
description:
|
||||
'Ministral 3 3B is the smallest and most efficient model in the Ministral 3 family, offering robust language and vision capabilities in a compact package.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 0.1,
|
||||
maxTokens: 256_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 0.1,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'magistral-medium-2509': {
|
||||
name: 'Magistral Medium 1.2',
|
||||
description: 'Frontier-class multimodal reasoning model update of September 2025.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 2,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 5,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
'magistral-small-2509': {
|
||||
name: 'Magistral Small 1.2',
|
||||
description: 'Small multimodal reasoning model update of September 2025.',
|
||||
tags: [
|
||||
/* TODO: Add tags */
|
||||
],
|
||||
input: {
|
||||
costPer1MTokens: 0.5,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
output: {
|
||||
costPer1MTokens: 1.5,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default new bp.Integration({
|
||||
register: async () => {},
|
||||
unregister: async () => {},
|
||||
actions: {
|
||||
generateContent: async ({ input, logger, metadata }) => {
|
||||
const output = await generateContent(<llm.GenerateContentInput>input, mistral, logger, {
|
||||
models: LanguageModels,
|
||||
defaultModel: DefaultModel,
|
||||
})
|
||||
metadata.setCost(output.botpress.cost)
|
||||
return output
|
||||
},
|
||||
listLanguageModels: async ({}) => {
|
||||
return {
|
||||
models: Object.entries(LanguageModels).map(([id, model]) => ({ id: <ModelId>id, ...model })),
|
||||
}
|
||||
},
|
||||
},
|
||||
channels: {},
|
||||
handler: async () => {},
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import { z } from '@botpress/sdk'
|
||||
|
||||
export type ModelId = z.infer<typeof ModelId>
|
||||
|
||||
export const DefaultModel: ModelId = 'mistral-large-2512'
|
||||
|
||||
export const ModelId = z
|
||||
.enum([
|
||||
'mistral-large-2512',
|
||||
'mistral-medium-2508',
|
||||
'mistral-small-2506',
|
||||
'ministral-14b-2512',
|
||||
'ministral-8b-2512',
|
||||
'ministral-3b-2512',
|
||||
'magistral-medium-2509',
|
||||
'magistral-small-2509',
|
||||
])
|
||||
.describe('Model to use for content generation')
|
||||
.placeholder(DefaultModel)
|
||||
@@ -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