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

@ -24,6 +24,24 @@ export const askAssistant = createAction({
label: 'Message',
inputType: 'textarea',
}),
functions: option
.array(
option.object({
name: option.string.layout({
fetcher: 'fetchAssistantFunctions',
label: 'Name',
}),
code: option.string.layout({
inputType: 'code',
label: 'Code',
lang: 'javascript',
moreInfoTooltip:
'A javascript code snippet that can use the defined parameters. It should return a value.',
withVariableButton: false,
}),
})
)
.layout({ accordion: 'Functions', itemLabel: 'function' }),
responseMapping: option
.saveResponseArray(['Message', 'Thread ID'] as const)
.layout({
@ -64,6 +82,40 @@ export const askAssistant = createAction({
},
dependencies: ['baseUrl', 'apiVersion'],
},
{
id: 'fetchAssistantFunctions',
fetch: async ({ options, credentials }) => {
if (!options.assistantId) return []
const config = {
apiKey: credentials.apiKey,
baseURL: options.baseUrl,
defaultHeaders: {
'api-key': credentials.apiKey,
},
defaultQuery: options.apiVersion
? {
'api-version': options.apiVersion,
}
: undefined,
} satisfies ClientOptions
const openai = new OpenAI(config)
const response = await openai.beta.assistants.retrieve(
options.assistantId
)
return response.tools
.filter((tool) => tool.type === 'function')
.map((tool) =>
tool.type === 'function' && tool.function.name
? tool.function.name
: undefined
)
.filter(isDefined)
},
dependencies: ['baseUrl', 'apiVersion', 'assistantId'],
},
],
getSetVariableIds: ({ responseMapping }) =>
responseMapping?.map((r) => r.variableId).filter(isDefined) ?? [],
@ -77,6 +129,7 @@ export const askAssistant = createAction({
message,
responseMapping,
threadId,
functions,
},
variables,
logs,
@ -139,6 +192,45 @@ export const askAssistant = createAction({
) {
throw new Error(run.status)
}
if (run.status === 'requires_action') {
if (run.required_action?.type === 'submit_tool_outputs') {
const tool_outputs = (
await Promise.all(
run.required_action.submit_tool_outputs.tool_calls.map(
async (toolCall) => {
const parameters = JSON.parse(toolCall.function.arguments)
const functionToExecute = functions?.find(
(f) => f.name === toolCall.function.name
)
if (!functionToExecute) return
const name = toolCall.function.name
if (!name) return
const func = AsyncFunction(
...Object.keys(parameters),
functionToExecute.code
)
const output = await func(...Object.values(parameters))
return {
tool_call_id: toolCall.id,
output,
}
}
)
)
).filter(isDefined)
run = await openai.beta.threads.runs.submitToolOutputs(
currentThreadId,
run.id,
{ tool_outputs }
)
await waitForRun(run)
}
}
}
await waitForRun(run)
@ -170,3 +262,5 @@ export const askAssistant = createAction({
},
},
})
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor