diff --git a/integrations/messenger/src/channels/comment-replies.ts b/integrations/messenger/src/channels/comment-replies.ts index a069a3f07cb..15008f58c47 100644 --- a/integrations/messenger/src/channels/comment-replies.ts +++ b/integrations/messenger/src/channels/comment-replies.ts @@ -8,6 +8,11 @@ const commentReplies: bp.IntegrationProps['channels']['commentReplies'] = { const { logger, conversation, payload, ctx, client, ack } = props const { id } = conversation.tags + if (ctx.configurationType === 'sandbox') { + logger.forBot().error('Comment replies are not supported in sandbox mode') + return + } + if (!id) { logger.forBot().error('Comment ID is required to reply to comments') return diff --git a/interfaces/creatable/interface.definition.ts b/interfaces/creatable/interface.definition.ts index 54914db24ce..e3cda8bb2eb 100644 --- a/interfaces/creatable/interface.definition.ts +++ b/interfaces/creatable/interface.definition.ts @@ -1,12 +1,11 @@ -/* bplint-disable */ import { z, InterfaceDefinition } from '@botpress/sdk' -const baseItem = z.object({ id: z.string() }) +const baseItem = z.object({ id: z.string().title('Item ID').describe('The unique identifier for the creatable item') }) const withId = (schema: z.ZodTypeAny) => z.intersection(schema, baseItem) export default new InterfaceDefinition({ name: 'creatable', - version: '0.0.2', + version: '0.0.3', entities: { item: { schema: baseItem, diff --git a/interfaces/deletable/interface.definition.ts b/interfaces/deletable/interface.definition.ts index 74360808e65..5ba9a89948e 100644 --- a/interfaces/deletable/interface.definition.ts +++ b/interfaces/deletable/interface.definition.ts @@ -1,11 +1,10 @@ -/* bplint-disable */ import { z, InterfaceDefinition } from '@botpress/sdk' -const baseItem = z.object({ id: z.string() }) +const baseItem = z.object({ id: z.string().title('Item ID').describe('The unique identifier for the deletable item') }) export default new InterfaceDefinition({ name: 'deletable', - version: '0.0.2', + version: '0.0.3', entities: { item: { schema: baseItem, diff --git a/interfaces/files-readonly/interface.definition.ts b/interfaces/files-readonly/interface.definition.ts index 6fb6f970459..7c3a94b1d17 100644 --- a/interfaces/files-readonly/interface.definition.ts +++ b/interfaces/files-readonly/interface.definition.ts @@ -1,4 +1,3 @@ -/* bplint-disable */ import * as sdk from '@botpress/sdk' const BASE_ITEM = (itemType: 'file' | 'folder' | 'item' = 'item') => @@ -63,6 +62,7 @@ export default new sdk.InterfaceDefinition({ sdk.z.object({ folderId: FOLDER.shape.id .optional() + .title('Folder ID') .describe('The folder ID to list the items from. Leave empty to list items from the root folder.'), filters: sdk.z .object({ @@ -75,8 +75,9 @@ export default new sdk.InterfaceDefinition({ .describe('Filter the items modified after the given date'), }) .optional() + .title('Search Filters') .describe('Optional search filters'), - nextToken: NEXT_TOKEN.describe( + nextToken: NEXT_TOKEN.title('Next Page Token').describe( 'The token to get the next page of items. Leave empty to get the first page.' ), }), @@ -84,10 +85,16 @@ export default new sdk.InterfaceDefinition({ output: { schema: () => sdk.z.object({ - items: sdk.z.array(sdk.z.union([FILE, FOLDER])).describe('The files and folders in the folder'), - meta: sdk.z.object({ - nextToken: NEXT_TOKEN, - }), + items: sdk.z + .array(sdk.z.union([FILE, FOLDER])) + .title('Folder Items') + .describe('The files and folders in the folder'), + meta: sdk.z + .object({ + nextToken: NEXT_TOKEN, + }) + .title('Metadata') + .describe('List items in folder metadata'), }), }, }, @@ -112,7 +119,7 @@ export default new sdk.InterfaceDefinition({ output: { schema: () => sdk.z.object({ - botpressFileId: sdk.z.string().describe('The file ID of the uploaded file on Botpress'), + botpressFileId: sdk.z.string().title('File ID').describe('The file ID of the uploaded file on Botpress'), }), }, }, @@ -121,25 +128,25 @@ export default new sdk.InterfaceDefinition({ fileCreated: { schema: () => sdk.z.object({ - file: FILE_WITH_PATH.describe('The created file'), + file: FILE_WITH_PATH.title('Created File').describe('The created file'), }), }, fileUpdated: { schema: () => sdk.z.object({ - file: FILE_WITH_PATH.describe('The updated file'), + file: FILE_WITH_PATH.title('Updated File').describe('The updated file'), }), }, fileDeleted: { schema: () => sdk.z.object({ - file: FILE_WITH_PATH.describe('The deleted file'), + file: FILE_WITH_PATH.title('Deleted File').describe('The deleted file'), }), }, folderDeletedRecursive: { schema: () => sdk.z.object({ - folder: FOLDER_WITH_PATH.describe('The deleted folder'), + folder: FOLDER_WITH_PATH.title('Deleted Folder').describe('The deleted folder'), }), }, aggregateFileChanges: { @@ -153,6 +160,7 @@ export default new sdk.InterfaceDefinition({ .array(sdk.z.union([FILE_WITH_PATH, FOLDER_WITH_PATH])) .describe('The files and folders deleted'), }) + .title('Modified Items') .describe('The modified items'), }), }, diff --git a/interfaces/listable/interface.definition.ts b/interfaces/listable/interface.definition.ts index 0ef1183ea97..c2969ab7fad 100644 --- a/interfaces/listable/interface.definition.ts +++ b/interfaces/listable/interface.definition.ts @@ -1,13 +1,12 @@ -/* bplint-disable */ import { z, InterfaceDefinition } from '@botpress/sdk' -const baseItem = z.object({ id: z.string() }) +const baseItem = z.object({ id: z.string().title('Item ID').describe('The unique identifier for the listable item') }) const withId = (schema: z.ZodTypeAny) => z.intersection(schema, baseItem) const nextToken = z.string().optional() export default new InterfaceDefinition({ name: 'listable', - version: '0.0.2', + version: '0.0.3', entities: { item: { schema: baseItem, @@ -17,13 +16,25 @@ export default new InterfaceDefinition({ actions: { list: { input: { - schema: () => z.object({ nextToken }), + schema: () => + z.object({ + nextToken: nextToken + .title('List Token') + .describe('The token to get a given list of items (e.g. a parent record ID, or a page index).'), + }), }, output: { schema: (args) => z.object({ items: z.array(withId(args.item)), - meta: z.object({ nextToken }), + meta: z + .object({ + nextToken: nextToken + .title('List Token') + .describe('The token to get a given list of items (e.g. a parent record ID, or a page index).'), + }) + .title('Metadata') + .describe('Metadata for the list operation'), }), }, }, diff --git a/interfaces/llm/interface.definition.ts b/interfaces/llm/interface.definition.ts index 099ddd6e7c7..db9ddc02e03 100644 --- a/interfaces/llm/interface.definition.ts +++ b/interfaces/llm/interface.definition.ts @@ -1,10 +1,9 @@ -/* bplint-disable */ import * as common from '@botpress/common' import { z, InterfaceDefinition } from '@botpress/sdk' export default new InterfaceDefinition({ name: 'llm', - version: '9.0.0', + version: '9.0.1', entities: { modelRef: { schema: common.llm.schemas.ModelRefSchema, diff --git a/interfaces/readable/interface.definition.ts b/interfaces/readable/interface.definition.ts index bcbdb623ea9..1429fa2c1c3 100644 --- a/interfaces/readable/interface.definition.ts +++ b/interfaces/readable/interface.definition.ts @@ -1,12 +1,11 @@ -/* bplint-disable */ import { z, InterfaceDefinition } from '@botpress/sdk' -const baseItem = z.object({ id: z.string() }) +const baseItem = z.object({ id: z.string().title('Item ID').describe('The unique identifier for the readable item') }) const withId = (schema: z.ZodTypeAny) => z.intersection(schema, baseItem) export default new InterfaceDefinition({ name: 'readable', - version: '0.0.2', + version: '0.0.3', entities: { item: { schema: baseItem, diff --git a/interfaces/speech-to-text/interface.definition.ts b/interfaces/speech-to-text/interface.definition.ts index 87b0775c70c..0b3ba7a2b86 100644 --- a/interfaces/speech-to-text/interface.definition.ts +++ b/interfaces/speech-to-text/interface.definition.ts @@ -1,10 +1,9 @@ -/* bplint-disable */ import * as common from '@botpress/common' import { z, InterfaceDefinition } from '@botpress/sdk' export default new InterfaceDefinition({ name: 'speech-to-text', - version: '2.0.1', + version: '2.0.2', entities: { speechToTextModelRef: { schema: common.speechToText.schemas.SpeechModelRefSchema, diff --git a/interfaces/text-to-image/interface.definition.ts b/interfaces/text-to-image/interface.definition.ts index 64722149bca..ba6a44bf9dd 100644 --- a/interfaces/text-to-image/interface.definition.ts +++ b/interfaces/text-to-image/interface.definition.ts @@ -1,10 +1,9 @@ -/* bplint-disable */ import * as common from '@botpress/common' import { z, InterfaceDefinition } from '@botpress/sdk' export default new InterfaceDefinition({ name: 'text-to-image', - version: '2.1.1', + version: '2.1.2', entities: { imageModelRef: { schema: common.textToImage.schemas.ImageModelRefSchema, diff --git a/interfaces/typing-indicator/interface.definition.ts b/interfaces/typing-indicator/interface.definition.ts index 2789e82f91e..0d91a237b8a 100644 --- a/interfaces/typing-indicator/interface.definition.ts +++ b/interfaces/typing-indicator/interface.definition.ts @@ -1,9 +1,8 @@ -/* bplint-disable */ import * as sdk from '@botpress/sdk' export default new sdk.InterfaceDefinition({ name: 'typing-indicator', - version: '0.0.3', + version: '0.0.4', entities: {}, events: {}, actions: { @@ -14,11 +13,18 @@ export default new sdk.InterfaceDefinition({ input: { schema: () => sdk.z.object({ - conversationId: sdk.z.string(), - messageId: sdk.z.string().describe('The message ID to which the typing indicator should be attached'), + conversationId: sdk.z + .string() + .title('Conversation ID') + .describe('The ID of the conversation where the typing indicator should be shown'), + messageId: sdk.z + .string() + .title('Message ID') + .describe('The message ID to which the typing indicator should be attached'), timeout: sdk.z .number() .optional() + .title('Typing Indicator Timeout') .describe('The timeout in milliseconds after which the typing indicator should stop'), }), }, @@ -33,8 +39,14 @@ export default new sdk.InterfaceDefinition({ input: { schema: () => sdk.z.object({ - conversationId: sdk.z.string(), - messageId: sdk.z.string().describe('The message ID from which the typing indicator should be removed'), + conversationId: sdk.z + .string() + .title('Conversation ID') + .describe('The ID of the conversation where the typing indicator should be removed'), + messageId: sdk.z + .string() + .title('Message ID') + .describe('The message ID from which the typing indicator should be removed'), }), }, output: { diff --git a/interfaces/updatable/interface.definition.ts b/interfaces/updatable/interface.definition.ts index e1e8dc0d01d..35dd76dc377 100644 --- a/interfaces/updatable/interface.definition.ts +++ b/interfaces/updatable/interface.definition.ts @@ -1,12 +1,11 @@ -/* bplint-disable */ import { z, InterfaceDefinition } from '@botpress/sdk' -const baseItem = z.object({ id: z.string() }) +const baseItem = z.object({ id: z.string().title('Item ID').describe('The unique identifier for the updatable item') }) const withId = (schema: z.ZodTypeAny) => z.intersection(schema, baseItem) export default new InterfaceDefinition({ name: 'updatable', - version: '0.0.2', + version: '0.0.3', entities: { item: { schema: baseItem, diff --git a/packages/cli/src/linter/ruleset-tests/interface.ruleset.test.ts b/packages/cli/src/linter/ruleset-tests/interface.ruleset.test.ts index e5459eb27ce..e742c0abab6 100644 --- a/packages/cli/src/linter/ruleset-tests/interface.ruleset.test.ts +++ b/packages/cli/src/linter/ruleset-tests/interface.ruleset.test.ts @@ -882,96 +882,6 @@ describeRule('legacy-zui-examples-should-be-removed', (lint) => { }) }) -describeRule('entities-should-have-a-title', (lint) => { - test('missing title should trigger', async () => { - // arrange - const definition = { - entities: { [ENTITY_NAME]: {} }, - } as const satisfies PartialInterface - - // act - const results = await lint(definition) - - // assert - expect(results).toHaveLength(1) - expect(results[0]?.path).toEqual(['entities', ENTITY_NAME]) - expect(results[0]?.message).toContain('title') - }) - - test('empty title should trigger', async () => { - // arrange - const definition = { - entities: { [ENTITY_NAME]: { title: EMPTY_STRING } }, - } as const satisfies PartialInterface - - // act - const results = await lint(definition) - - // assert - expect(results).toHaveLength(1) - expect(results[0]?.path).toEqual(['entities', ENTITY_NAME, 'title']) - expect(results[0]?.message).toContain('title') - }) - - test('valid title should not trigger', async () => { - // arrange - const definition = { - entities: { [ENTITY_NAME]: { title: TRUTHY_STRING } }, - } as const satisfies PartialInterface - - // act - const results = await lint(definition) - - // assert - expect(results).toHaveLength(0) - }) -}) - -describeRule('entities-must-have-a-description', (lint) => { - test('missing description should trigger', async () => { - // arrange - const definition = { - entities: { [ENTITY_NAME]: {} }, - } as const satisfies PartialInterface - - // act - const results = await lint(definition) - - // assert - expect(results).toHaveLength(1) - expect(results[0]?.path).toEqual(['entities', ENTITY_NAME]) - expect(results[0]?.message).toContain('description') - }) - - test('empty description should trigger', async () => { - // arrange - const definition = { - entities: { [ENTITY_NAME]: { description: EMPTY_STRING } }, - } as const satisfies PartialInterface - - // act - const results = await lint(definition) - - // assert - expect(results).toHaveLength(1) - expect(results[0]?.path).toEqual(['entities', ENTITY_NAME, 'description']) - expect(results[0]?.message).toContain('description') - }) - - test('valid description should not trigger', async () => { - // arrange - const definition = { - entities: { [ENTITY_NAME]: { description: TRUTHY_STRING } }, - } as const satisfies PartialInterface - - // act - const results = await lint(definition) - - // assert - expect(results).toHaveLength(0) - }) -}) - describeRule('entity-fields-should-have-a-title', (lint) => { test.each(PARAM_NAMES)('missing title should trigger (%s)', async (paramName) => { // arrange diff --git a/packages/cli/src/linter/rulesets/interface.ruleset.ts b/packages/cli/src/linter/rulesets/interface.ruleset.ts index cc4db42fa1f..0ad4eee54ef 100644 --- a/packages/cli/src/linter/rulesets/interface.ruleset.ts +++ b/packages/cli/src/linter/rulesets/interface.ruleset.ts @@ -115,20 +115,6 @@ export const INTERFACE_RULESET = { given: '$..ui[*].examples', then: [{ function: falsy }], }, - 'entities-should-have-a-title': { - description: 'All entities SHOULD have a title', - message: '{{description}}: {{error}} SHOULD have a non-empty title', - severity: 'warn', - given: '$.entities[*]', - then: [{ field: 'title', function: truthyWithMessage(({ path }) => `entity "${path[1]}"`) }], - }, - 'entities-must-have-a-description': { - description: 'All entities MUST have a description', - message: '{{description}}: {{error}} MUST have a non-empty description', - severity: 'error', - given: '$.entities[*]', - then: [{ field: 'description', function: truthyWithMessage(({ path }) => `entity "${path[1]}"`) }], - }, 'entity-fields-should-have-a-title': { description: 'All entity fields SHOULD have a title', message: '{{description}}: {{error}} SHOULD provide a non-empty title by using .title() in its Zod schema', diff --git a/packages/cli/src/linter/spectral-functions.ts b/packages/cli/src/linter/spectral-functions.ts index bf3797dca2c..38fc496ff48 100644 --- a/packages/cli/src/linter/spectral-functions.ts +++ b/packages/cli/src/linter/spectral-functions.ts @@ -20,7 +20,7 @@ type FallbackExtractor = ( jsonPathExtractor: (fallbackPath: string) => { resolvedPath: JsonPath; value: any }[] ) => { path: JsonPath; value: string } | null -const _anyOfFallbackExtractor = ( +const _anyOrAllOfFallbackExtractor = ( /** The relative path from within the "anyOf" node */ pathFromAnyOf: Segment[], options: { pathBackoff?: number } = {} @@ -31,16 +31,17 @@ const _anyOfFallbackExtractor = ( throw new Error('Path backoff must be a non-negative integer') } + const anyOrAllOfSegment = '[?(@property === "allOf" || @property === "anyOf")][*]' return (failedPath, jsonPathExtractor) => { const spliceIndex = failedPath.length - pathBackoff - const newPath = `$.${failedPath.toSpliced(spliceIndex, pathBackoff, 'anyOf[*]', ...pathFromAnyOf).join('.')}` + const newPath = `$.${failedPath.toSpliced(spliceIndex, pathBackoff, anyOrAllOfSegment, ...pathFromAnyOf).join('.')}` const match = jsonPathExtractor(newPath).find(({ value }) => typeof value === 'string') return match ? { value: match.value, path: match.resolvedPath } : null } } -export const titleFallbackExtractor: FallbackExtractor = _anyOfFallbackExtractor(['x-zui', 'title']) -export const descriptionFallbackExtractor: FallbackExtractor = _anyOfFallbackExtractor(['description']) +export const titleFallbackExtractor: FallbackExtractor = _anyOrAllOfFallbackExtractor(['x-zui', 'title']) +export const descriptionFallbackExtractor: FallbackExtractor = _anyOrAllOfFallbackExtractor(['description']) /** * When the input is falsy, return a message that is generated by the provided function diff --git a/packages/cognitive/src/schemas.gen.ts b/packages/cognitive/src/schemas.gen.ts index ab85fd62de4..c4eacb23289 100644 --- a/packages/cognitive/src/schemas.gen.ts +++ b/packages/cognitive/src/schemas.gen.ts @@ -48,6 +48,7 @@ export type GenerateContentInput = { topP?: number /** Sequences where the model should stop generating further tokens. */ stopSequences?: string[] + /** List of tools available for the model to use */ tools?: Array<{ type: 'function' function: { @@ -58,14 +59,17 @@ export type GenerateContentInput = { argumentsSchema?: {} } }> + /** The chosen tool to use for content generation */ toolChoice?: { type?: 'auto' | 'specific' | 'any' | 'none' | '' /** Required if `type` is "specific" */ functionName?: string } + /** Unique identifier of the user that sent the prompt */ userId?: string /** Set to `true` to output debug information to the bot logs */ debug?: boolean + /** Contextual metadata about the prompt */ meta?: { /** Source of the prompt, e.g. agent/:id/:version cards/ai-generate, cards/ai-task, nodes/autonomous, etc. */ promptSource?: string @@ -80,8 +84,9 @@ export type GenerateContentOutput = { id: string /** LLM provider name */ provider: string - /** Model name */ + /** The name of the LLM model that was used */ model: string + /** Array of generated message choices from the model */ choices: Array<{ type?: 'text' | 'tool_calls' | 'tool_result' | 'multipart' /** Required if `type` is "tool_calls" */ @@ -113,6 +118,7 @@ export type GenerateContentOutput = { index: number stopReason: 'stop' | 'max_tokens' | 'tool_calls' | 'content_filter' | 'other' }> + /** A breakdown of token usage and cost information */ usage: { /** Number of input tokens used by the model */ inputTokens: number @@ -123,6 +129,7 @@ export type GenerateContentOutput = { /** Cost of the output tokens generated by the model, in U.S. dollars */ outputCost: number } + /** Metadata added by Botpress */ botpress: { /** Total cost of the content generation, in U.S. dollars */ cost: number @@ -130,6 +137,7 @@ export type GenerateContentOutput = { } export type Model = { + /** Unique identifier of the large language model */ id: string name: string description: string diff --git a/packages/common/src/llm/schemas.ts b/packages/common/src/llm/schemas.ts index dba0eeac5b1..fb0a0159938 100644 --- a/packages/common/src/llm/schemas.ts +++ b/packages/common/src/llm/schemas.ts @@ -49,7 +49,9 @@ export const MessageSchema = z.object({ ), }) -export const ModelRefSchema = z.object({ id: z.string() }) +export const ModelRefSchema = z.object({ + id: z.string().title('LLM Model ID').describe('Unique identifier of the large language model'), +}) export const ModelSchema = ModelRefSchema.extend({ name: z.string(), @@ -85,23 +87,33 @@ export type ReasoningEffort = z.infer export const GenerateContentInputSchema = (modelRefSchema: S) => z.object({ - model: modelRefSchema.describe('Model to use for content generation').optional(), - reasoningEffort: ReasoningEffortSchema.optional().describe( - dedent` + model: modelRefSchema.optional().describe('Model to use for content generation'), + reasoningEffort: ReasoningEffortSchema.optional() + .title('Reasoning Effort Level') + .describe( + dedent` Reasoning effort level to use for models that support reasoning. Specifying "none" will indicate the LLM to not use reasoning (for models that support optional reasoning). A "dynamic" effort will indicate the provider to automatically determine the reasoning effort (if supported by the provider). If not provided the model will not use reasoning for models with optional reasoning or use the default reasoning effort specified by the provider for reasoning-only models. Note: A higher reasoning effort will incur in higher output token charges from the LLM provider. ` - ), - systemPrompt: z.string().optional().describe('Optional system prompt to guide the model'), - messages: z.array(MessageSchema).describe('Array of messages for the model to process'), + ), + systemPrompt: z.string().optional().title('System Prompt').describe('Optional system prompt to guide the model'), + messages: z + .array(MessageSchema) + .title('Messages to Process') + .describe('Array of messages for the model to process'), responseFormat: z .enum(['text', 'json_object']) .optional() + .title('Response Format') .describe( 'Response format expected from the model. If "json_object" is chosen, you must instruct the model to generate JSON either via the system prompt or a user message.' ), // note: only OpenAI and Groq support this but for other models we can just append this as an indication in the system prompt // note: we don't support streaming yet - maxTokens: z.number().optional().describe('Maximum number of tokens allowed in the generated response'), + maxTokens: z + .number() + .optional() + .title('Maximum number of output tokens') + .describe('Maximum number of tokens allowed in the generated response'), temperature: z .number() .min(0) @@ -109,6 +121,7 @@ export const GenerateContentInputSchema = (modelRefSchema // @ts-ignore .displayAs({ id: 'slider', params: { stepSize: 0.01, horizontal: true } }) .default(1) + .title('Temperature') .describe('Sampling temperature for the model. Higher values result in more random outputs.'), topP: z .number() @@ -117,6 +130,7 @@ export const GenerateContentInputSchema = (modelRefSchema .default(1) // @ts-ignore .displayAs({ id: 'slider', params: { stepSize: 0.01, horizontal: true } }) + .title('Top-P') .describe( 'Top-p sampling parameter. Limits sampling to the smallest set of tokens with a cumulative probability above the threshold.' ), // TODO: .placeholder() from zui doesn't work, so we have to use .default() which introduces some typing issues @@ -125,6 +139,7 @@ export const GenerateContentInputSchema = (modelRefSchema .array(z.string()) .max(4) .optional() + .title('Stop Sequences') .describe('Sequences where the model should stop generating further tokens.'), tools: z .array( @@ -137,11 +152,20 @@ export const GenerateContentInputSchema = (modelRefSchema }), }) ) - .optional(), + .optional() + .title('Tools') + .describe('List of tools available for the model to use'), // TODO: an object with options doesn't seem to be supported by the Studio as it's not rendering correctly, the dropdown for "type" is not working and it's sending a blank value instead which causes a schema validation error unless an empty value is allowed in the `type` enum - toolChoice: ToolChoiceSchema.optional(), // note: Gemini doesn't support this but we can just ignore it there - userId: z.string().optional(), - debug: z.boolean().optional().hidden().describe('Set to `true` to output debug information to the bot logs'), + toolChoice: ToolChoiceSchema.optional() + .title('Tool Choice') + .describe('The chosen tool to use for content generation'), // note: Gemini doesn't support this but we can just ignore it there + userId: z.string().optional().title('User ID').describe('Unique identifier of the user that sent the prompt'), + debug: z + .boolean() + .optional() + .hidden() + .title('Debug Mode') + .describe('Set to `true` to output debug information to the bot logs'), meta: z .object({ promptSource: z @@ -157,30 +181,41 @@ export const GenerateContentInputSchema = (modelRefSchema .describe('Name of the integration that originally received the message that initiated this action'), }) .optional() - .hidden(), + .hidden() + .title('Prompt Metadata') + .describe('Contextual metadata about the prompt'), }) export const GenerateContentInputBaseSchema = GenerateContentInputSchema(ModelRefSchema) export const GenerateContentOutputSchema = z.object({ - id: z.string().describe('Response ID from LLM provider'), - provider: z.string().describe('LLM provider name'), - model: z.string().describe('Model name'), - choices: z.array( - MessageSchema.omit({ role: true }).extend({ - role: z.literal('assistant'), - index: z.number().int(), - stopReason: z.enum(['stop', 'max_tokens', 'tool_calls', 'content_filter', 'other']), - // note: stopSequence is supported by Claude but not by OpenAI, Groq or Gemini + id: z.string().title('Response ID').describe('Response ID from LLM provider'), + provider: z.string().title('LLM Provider').describe('LLM provider name'), + model: z.string().title('Model Name').describe('The name of the LLM model that was used'), + choices: z + .array( + MessageSchema.omit({ role: true }).extend({ + role: z.literal('assistant'), + index: z.number().int(), + stopReason: z.enum(['stop', 'max_tokens', 'tool_calls', 'content_filter', 'other']), + // note: stopSequence is supported by Claude but not by OpenAI, Groq or Gemini + }) + ) + .title('Generated Choices') + .describe('Array of generated message choices from the model'), + usage: z + .object({ + inputTokens: z.number().int().describe('Number of input tokens used by the model'), + inputCost: z.number().describe('Cost of the input tokens received by the model, in U.S. dollars'), + outputTokens: z.number().int().describe('Number of output tokens used by the model'), + outputCost: z.number().describe('Cost of the output tokens generated by the model, in U.S. dollars'), }) - ), - usage: z.object({ - inputTokens: z.number().int().describe('Number of input tokens used by the model'), - inputCost: z.number().describe('Cost of the input tokens received by the model, in U.S. dollars'), - outputTokens: z.number().int().describe('Number of output tokens used by the model'), - outputCost: z.number().describe('Cost of the output tokens generated by the model, in U.S. dollars'), - }), - botpress: z.object({ - cost: z.number().describe('Total cost of the content generation, in U.S. dollars'), - }), + .title('Usage Information') + .describe('A breakdown of token usage and cost information'), + botpress: z + .object({ + cost: z.number().title('Generation Cost').describe('Total cost of the content generation, in U.S. dollars'), + }) + .title('Botpress Metadata') + .describe('Metadata added by Botpress'), }) diff --git a/packages/common/src/speech-to-text/schemas.ts b/packages/common/src/speech-to-text/schemas.ts index b2ce82bc237..8ccb633f971 100644 --- a/packages/common/src/speech-to-text/schemas.ts +++ b/packages/common/src/speech-to-text/schemas.ts @@ -1,36 +1,39 @@ import { z } from '@botpress/sdk' export const OpenAITranscribeAudioOutputSchema = z.object({ - language: z.string().describe('Detected language of the audio'), - duration: z.number().describe('Duration of the audio file, in seconds'), - segments: z.array( - z.object({ - text: z.string().describe('Text content of the segment.'), - id: z.number().describe('Unique identifier of the segment'), - seek: z.number().describe('Seek offset of the segment'), - start: z.number().describe('Start time of the segment in seconds.'), - end: z.number().describe('End time of the segment in seconds.'), - tokens: z.array(z.number()).describe('Array of token IDs for the text content.'), - temperature: z.number().describe('Temperature parameter used for generating the segment.'), - avg_logprob: z - .number() - .describe('Average logprob of the segment. If the value is lower than -1, consider the logprobs failed.'), - compression_ratio: z - .number() - .describe( - 'Compression ratio of the segment. If the value is greater than 2.4, consider the compression failed.' - ), - no_speech_prob: z - .number() - .describe( - 'Probability of no speech in the segment. If the value is higher than 1.0 and the avg_logprob is below -1, consider this segment silent.' - ), - }) - ), + language: z.string().title('Detected Language').describe('Detected language of the audio'), + duration: z.number().title('Audio Duration').describe('Duration of the audio file, in seconds'), + segments: z + .array( + z.object({ + text: z.string().title('Segment Text Content').describe('Text content of the segment.'), + id: z.number().title('Segment ID').describe('Unique identifier of the segment'), + seek: z.number().title('Seek Offset').describe('Seek offset of the segment'), + start: z.number().title('Segment Start Time').describe('Start time of the segment in seconds.'), + end: z.number().title('Segment End Time').describe('End time of the segment in seconds.'), + tokens: z.array(z.number()).title('Text Token IDs').describe('Array of token IDs for the text content.'), + temperature: z.number().title('Temperature').describe('Temperature parameter used for generating the segment.'), + avg_logprob: z + .number() + .describe('Average logprob of the segment. If the value is lower than -1, consider the logprobs failed.'), + compression_ratio: z + .number() + .describe( + 'Compression ratio of the segment. If the value is greater than 2.4, consider the compression failed.' + ), + no_speech_prob: z + .number() + .describe( + 'Probability of no speech in the segment. If the value is higher than 1.0 and the avg_logprob is below -1, consider this segment silent.' + ), + }) + ) + .title('Transcription Segments') + .describe('List of transcription segments'), }) export const SpeechModelRefSchema = z.object({ - id: z.string(), + id: z.string().title('Model ID').describe('Unique identifier of the speech-to-text model'), }) export const SpeechToTextModelSchema = SpeechModelRefSchema.extend({ @@ -44,18 +47,21 @@ export const TranscribeAudioInputSchema = (imageM fileUrl: z .string() .url() + .title('Audio File URL') .describe( 'URL of the audio file to transcribe. The URL should return a content-type header in order to detect the audio format. Supported audio formats supported are: mp3, mp4, mpeg, mpga, m4a, wav, webm' ), language: z .string() .optional() + .title('Audio Language') .describe( 'The language of the input audio (optional). Supplying the input language in ISO-639-1 format will improve accuracy and latency.' ), prompt: z .string() .optional() + .title('Transcription Prompt') .describe( "An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language." ), @@ -63,6 +69,7 @@ export const TranscribeAudioInputSchema = (imageM .number() .default(0) .optional() + .title('Temperature') .describe( 'The sampling temperature (optional), between 0 and 1. Defaults to 0 (automatic). Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.' ), @@ -71,9 +78,15 @@ export const TranscribeAudioInputSchema = (imageM export const TranscribeAudioBaseSchema = TranscribeAudioInputSchema(SpeechModelRefSchema) export const TranscribeAudioOutputSchema = OpenAITranscribeAudioOutputSchema.extend({ - model: z.string().describe('Model name used'), - cost: z.number().describe('Total cost of the transcription, in U.S. dollars (DEPRECATED)'), - botpress: z.object({ - cost: z.number().describe('Total cost of the transcription, in U.S. dollars'), - }), + model: z.string().title('AI Model Name').describe('Model name used'), + cost: z + .number() + .title('Transcription Cost') + .describe('Total cost of the transcription, in U.S. dollars (DEPRECATED)'), + botpress: z + .object({ + cost: z.number().title('Transcription Cost').describe('Total cost of the transcription, in U.S. dollars'), + }) + .title('Botpress Metadata') + .describe('Metadata added by Botpress'), }) diff --git a/packages/common/src/text-to-image/schemas.ts b/packages/common/src/text-to-image/schemas.ts index cf72cfd4428..530dddb3ffb 100644 --- a/packages/common/src/text-to-image/schemas.ts +++ b/packages/common/src/text-to-image/schemas.ts @@ -1,7 +1,7 @@ import { z } from '@botpress/sdk' export const ImageModelRefSchema = z.object({ - id: z.string(), + id: z.string().title('Model ID').describe('Unique identifier of the image generation model'), }) export const ImageModelSchema = ImageModelRefSchema.extend({ @@ -18,15 +18,19 @@ export const GenerateImageInputSchema = z.object({ - model: imageModelRefSchema.optional().describe('Model to use for image generation'), - prompt: z.string(), - size: z.string().optional(), + model: imageModelRefSchema + .optional() + .title('Model Name') + .describe('The name of the Generative AI Model to use for image generation'), + prompt: z.string().title('Image Prompt').describe('Text prompt describing the image to be generated'), + size: z.string().optional().title('Image Size').describe('Desired size of the generated image'), expiration: z .number() .int() .min(30) .max(90) .optional() + .title('Image Expiry (Days)') .describe( 'Expiration of the generated image in days, after which the image will be automatically deleted to free up storage space in your account. The default is to keep the image indefinitely (no expiration). The minimum is 30 days and the maximum is 90 days.' ), @@ -36,10 +40,16 @@ export const GenerateImageInputSchema =