2
0

(openai) Add tools and functions support (#1167)

Closes #863

Got helped from #1162 for the implementation. Closing it in favor of
this PR.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced `CodeEditor` with additional properties for better form
control and validation.
- Introduced tools and functions in OpenAI integrations documentation
for custom JavaScript execution.
- Added capability to define and use custom JavaScript functions with
the OpenAI assistant.
- Expanded layout metadata options to include various input types and
languages.

- **Improvements**
- Updated the OpenAI actions to support new function execution features.

- **Documentation**
- Added new sections for tools and functions in the OpenAI integrations
guide.

- **Refactor**
- Refactored components and actions to integrate new features and
improve existing functionalities.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Baptiste Arnaud
2024-01-19 08:05:38 +01:00
committed by GitHub
parent 61bfe1bb96
commit f4d315fed5
12 changed files with 460 additions and 137 deletions

View File

@ -1,11 +1,15 @@
import {
BoxProps,
Fade,
FormControl,
FormHelperText,
FormLabel,
HStack,
Stack,
useColorModeValue,
useDisclosure,
} from '@chakra-ui/react'
import { useEffect, useRef, useState } from 'react'
import React, { ReactNode, useEffect, useRef, useState } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { VariablesButton } from '@/features/variables/components/VariablesButton'
import { Variable } from '@typebot.io/schemas'
@ -16,8 +20,10 @@ import { githubLight } from '@uiw/codemirror-theme-github'
import { LanguageName, loadLanguage } from '@uiw/codemirror-extensions-langs'
import { isDefined } from '@udecode/plate-common'
import { CopyButton } from '../CopyButton'
import { MoreInfoTooltip } from '../MoreInfoTooltip'
type Props = {
label?: string
value?: string
defaultValue?: string
lang: LanguageName
@ -27,11 +33,18 @@ type Props = {
height?: string
maxHeight?: string
minWidth?: string
moreInfoTooltip?: string
helperText?: ReactNode
isRequired?: boolean
onChange?: (value: string) => void
}
export const CodeEditor = ({
label,
defaultValue,
lang,
moreInfoTooltip,
helperText,
isRequired,
onChange,
height = '250px',
maxHeight = '70vh',
@ -86,71 +99,91 @@ export const CodeEditor = ({
)
return (
<HStack
align="flex-end"
spacing={0}
borderWidth={'1px'}
rounded="md"
bg={useColorModeValue('white', '#1A1B26')}
width="full"
h="full"
pos="relative"
minW={minWidth}
onMouseEnter={onOpen}
onMouseLeave={onClose}
maxWidth={props.maxWidth}
sx={{
'& .cm-editor': {
maxH: maxHeight,
outline: '0px solid transparent !important',
rounded: 'md',
},
'& .cm-scroller': {
rounded: 'md',
overflow: 'auto',
},
'& .cm-gutter,.cm-content': {
minH: isReadOnly ? '0' : height,
},
'& .ͼ1 .cm-scroller': {
fontSize: '14px',
fontFamily:
'JetBrainsMono, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace',
},
}}
<FormControl
isRequired={isRequired}
as={Stack}
justifyContent="space-between"
spacing={2}
flex="1"
>
<CodeMirror
data-testid="code-editor"
ref={codeEditor}
value={props.value ?? value}
onChange={handleChange}
onBlur={rememberCarretPosition}
theme={theme}
extensions={[loadLanguage(lang)].filter(isDefined)}
editable={!isReadOnly}
style={{
width: isVariableButtonDisplayed ? 'calc(100% - 32px)' : '100%',
}}
spellCheck={false}
basicSetup={{
highlightActiveLine: false,
}}
/>
{isVariableButtonDisplayed && (
<VariablesButton onSelectVariable={handleVariableSelected} size="sm" />
{label && (
<FormLabel display="flex" flexShrink={0} gap="1" mb="0" mr="0">
{label}{' '}
{moreInfoTooltip && (
<MoreInfoTooltip>{moreInfoTooltip}</MoreInfoTooltip>
)}
</FormLabel>
)}
{isReadOnly && (
<Fade in={isOpen}>
<CopyButton
textToCopy={props.value ?? value}
pos="absolute"
right={0.5}
top={0.5}
size="xs"
colorScheme="blue"
<HStack
align="flex-end"
spacing={0}
borderWidth={'1px'}
rounded="md"
bg={useColorModeValue('white', '#1A1B26')}
width="full"
h="full"
pos="relative"
minW={minWidth}
onMouseEnter={onOpen}
onMouseLeave={onClose}
maxWidth={props.maxWidth}
sx={{
'& .cm-editor': {
maxH: maxHeight,
outline: '0px solid transparent !important',
rounded: 'md',
},
'& .cm-scroller': {
rounded: 'md',
overflow: 'auto',
},
'& .cm-gutter,.cm-content': {
minH: isReadOnly ? '0' : height,
},
'& .ͼ1 .cm-scroller': {
fontSize: '14px',
fontFamily:
'JetBrainsMono, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace',
},
}}
>
<CodeMirror
data-testid="code-editor"
ref={codeEditor}
value={props.value ?? value}
onChange={handleChange}
onBlur={rememberCarretPosition}
theme={theme}
extensions={[loadLanguage(lang)].filter(isDefined)}
editable={!isReadOnly}
style={{
width: isVariableButtonDisplayed ? 'calc(100% - 32px)' : '100%',
}}
spellCheck={false}
basicSetup={{
highlightActiveLine: false,
}}
/>
{isVariableButtonDisplayed && (
<VariablesButton
onSelectVariable={handleVariableSelected}
size="sm"
/>
</Fade>
)}
</HStack>
)}
{isReadOnly && (
<Fade in={isOpen}>
<CopyButton
textToCopy={props.value ?? value}
pos="absolute"
right={0.5}
top={0.5}
size="xs"
colorScheme="blue"
/>
</Fade>
)}
</HStack>
{helperText && <FormHelperText mt="0">{helperText}</FormHelperText>}
</FormControl>
)
}

View File

@ -22,6 +22,7 @@ import { DropdownList } from '@/components/DropdownList'
import { ForgedBlockDefinition, ForgedBlock } from '@typebot.io/forge-schemas'
import { PrimitiveList } from '@/components/PrimitiveList'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { CodeEditor } from '@/components/inputs/CodeEditor'
const mdComponents = {
a: ({ href, children }) => (
@ -99,6 +100,8 @@ export const ZodFieldLayout = ({
<ZodArrayContent
data={data}
schema={schema}
blockDef={blockDef}
blockOptions={blockOptions}
layout={layout}
onDataChange={onDataChange}
isInAccordion
@ -111,6 +114,8 @@ export const ZodFieldLayout = ({
<ZodArrayContent
data={data}
schema={schema}
blockDef={blockDef}
blockOptions={blockOptions}
layout={layout}
onDataChange={onDataChange}
/>
@ -229,6 +234,28 @@ export const ZodFieldLayout = ({
/>
)
}
if (layout?.inputType === 'code')
return (
<CodeEditor
defaultValue={data ?? layout?.defaultValue}
lang={layout.lang ?? 'javascript'}
label={layout?.label}
placeholder={layout?.placeholder}
helperText={
layout?.helperText ? (
<Markdown components={mdComponents}>
{layout.helperText}
</Markdown>
) : undefined
}
isRequired={layout?.isRequired}
withVariableButton={layout?.withVariableButton}
moreInfoTooltip={layout.moreInfoTooltip}
onChange={onDataChange}
width={width}
/>
)
return (
<TextInput
defaultValue={data ?? layout?.defaultValue}
@ -254,12 +281,16 @@ export const ZodFieldLayout = ({
const ZodArrayContent = ({
schema,
data,
blockDef,
blockOptions,
layout,
isInAccordion,
onDataChange,
}: {
schema: z.ZodTypeAny
data: any
blockDef?: ForgedBlockDefinition
blockOptions?: ForgedBlock['options']
layout: ZodLayoutMetadata<ZodTypeAny> | undefined
isInAccordion?: boolean
onDataChange: (val: any) => void
@ -283,6 +314,8 @@ const ZodArrayContent = ({
<ZodFieldLayout
schema={schema._def.innerType._def.type}
data={item}
blockDef={blockDef}
blockOptions={blockOptions}
isInAccordion={isInAccordion}
onDataChange={onItemChange}
width="full"
@ -302,9 +335,11 @@ const ZodArrayContent = ({
isOrdered={layout?.isOrdered}
>
{({ item, onItemChange }) => (
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
<Stack p="4" rounded="md" flex="1" borderWidth="1px" maxW="100%">
<ZodFieldLayout
schema={schema._def.innerType._def.type}
blockDef={blockDef}
blockOptions={blockOptions}
data={item}
isInAccordion={isInAccordion}
onDataChange={onItemChange}

View File

@ -33,6 +33,18 @@ Then you can give the OpenAI block access to this sequence of messages:
/>
</Frame>
### Tools
The tools section allows you to add functions that the OpenAI model can execute. Here is an example of a function named `getWeather` that returns 'Sunny and warm' if you ask about the weather of Paris and 'Rainy and cold' if you ask for any other city 😂.
A more useful example would be, of course, to call an API to get the weather of the city the user is asking about.
<Frame>
<img src="/images/blocks/integrations/openai/tools.png" alt="OpenAI tools" />
</Frame>
As you can see, the code block expects the body of the Javascript function. You can use the `return` keyword to return values.
## Ask assistant
This action allows you to talk with your [OpenAI assistant](https://platform.openai.com/assistants). All you have to do is to provide its ID.
@ -44,6 +56,10 @@ This action allows you to talk with your [OpenAI assistant](https://platform.ope
/>
</Frame>
### Functions
If you defined functions in your assistant, you can define the function to execute in the `Functions` section.
## Create speech
This action allows you to transform a text input into an audio URL that you can reuse in your bot.

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB