-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🔧 Allow OpenAI to call functions #1162
Conversation
@vforvilela is attempting to deploy a commit to the Typebot Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThe update introduces the ability to execute custom functions within OpenAI chat completions, enhancing the Changes
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (invoked as PR comments)
Additionally, you can add CodeRabbit Configration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 2
Configuration used: CodeRabbit UI
Files selected for processing (2)
- packages/forge/blocks/openai/actions/createChatCompletion.tsx (5 hunks)
- packages/forge/blocks/openai/helpers/parseFunctions.ts (1 hunks)
Additional comments: 4
packages/forge/blocks/openai/actions/createChatCompletion.tsx (4)
- 6-10: Imports for
parseFunctions
andvm
have been added correctly, aligning with the PR objectives to allow function execution within OpenAI chat completions.- 50-80: The schemas for parameters and functions are well-defined, using the
option
utility to specify the expected structure. This should facilitate the creation of custom functions within the UI.- 98-100: The addition of the
functions
property to theoptions
object is consistent with the PR objectives. It allows users to define custom functions that can be executed within OpenAI chat completions.- 270-271: The
tools
property is correctly added to thecreate
method call for the OpenAI chat completions, ensuring that the parsed functions are included in the request.
export const parseFunctions = ({ | ||
options: { functions }, | ||
variables, | ||
}: { | ||
options: Pick<z.infer<typeof createChatCompletionOption>, 'functions'> | ||
variables: ReadOnlyVariableStore | ||
}): OpenAI.ChatCompletionTool[] => { | ||
return ( | ||
functions?.map(({ name, description, parameters }) => ({ | ||
type: 'function', | ||
function: { | ||
name: name ?? 'function', | ||
description, | ||
parameters: { | ||
type: 'object', | ||
properties: Object.fromEntries( | ||
new Map( | ||
parameters?.map(({ name, type, description }) => [ | ||
name, | ||
{ | ||
type, | ||
description, | ||
}, | ||
]) | ||
) | ||
), | ||
required: | ||
parameters | ||
?.filter((param) => param.required) | ||
.map((param) => param.name) ?? [], | ||
}, | ||
}, | ||
})) ?? [] | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parseFunctions
function correctly constructs an array of OpenAI.ChatCompletionTool
objects from the provided functions
array. The use of optional chaining and nullish coalescing ensures that the function handles cases where functions
might not be provided. However, there is no validation to ensure that the name
property of a function or parameter is not an empty string, which could lead to unexpected behavior.
Consider adding validation to ensure that the name
property for both functions and parameters is not an empty string.
const messages = parseChatCompletionMessages({ options, variables }) | ||
|
||
const body: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = | ||
{ | ||
model: options.model ?? defaultOpenAIOptions.model, | ||
temperature: options.temperature | ||
? Number(options.temperature) | ||
: undefined, | ||
messages, | ||
...(options.functions?.length | ||
? { | ||
tools: parseFunctions({ options, variables }), | ||
tool_choice: 'auto', | ||
} | ||
: {}), | ||
} | ||
|
||
const response = await (async () => { | ||
while (true) { | ||
const response = await openai.chat.completions.create(body) | ||
|
||
if (!response.choices[0].message.tool_calls) { | ||
return response | ||
} | ||
|
||
messages.push(response.choices[0].message) | ||
|
||
for (const tool of response.choices[0].message.tool_calls) { | ||
const toolName = tool.function.name | ||
const toolArguments = Object.entries( | ||
JSON.parse(tool.function.arguments) | ||
) | ||
.map(([key, value]) => `${key} = ${value}`) | ||
.join('\n') | ||
const toolCallId = tool.id | ||
const toolCode = `${toolArguments}\n${ | ||
options.functions?.find((func) => func.name === toolName)?.code | ||
}` | ||
|
||
const context = { | ||
require: () => { | ||
// For safety | ||
throw new Error('require is not allowed') | ||
}, | ||
} | ||
|
||
let toolResponse = '' | ||
try { | ||
toolResponse = vm.runInNewContext(toolCode, context) | ||
} catch (e) { | ||
toolResponse = JSON.stringify(e) | ||
} | ||
|
||
messages.push({ | ||
role: 'tool', | ||
tool_call_id: toolCallId, | ||
// name: toolName, | ||
content: toolResponse.toString(), | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The run
method has been extended to handle custom functions. It uses the vm
module to execute the functions in a sandboxed environment, which is a security best practice to prevent arbitrary code execution. However, there is a potential infinite loop in the while (true)
statement that could lead to performance issues.
Consider implementing a condition to break out of the while
loop to prevent potential infinite execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 2
Configuration used: CodeRabbit UI
Files selected for processing (1)
- packages/forge/blocks/openai/actions/createChatCompletion.tsx (5 hunks)
Additional comments: 5
packages/forge/blocks/openai/actions/createChatCompletion.tsx (5)
- 6-6: Import of
parseFunctions
added without issues.- 10-10: Import of
vm
module added to support the execution of custom functions in a sandboxed environment.- 98-100: Added
functions
property to theoptions
object to specify custom functions for execution.- 210-229: The use of
vm.runInNewContext
to execute tool code. This is a security-sensitive operation. Ensure that the context provided tovm.runInNewContext
is secure and that the executed code cannot escape the sandbox or access sensitive data.- 270-271: The
tools
property is set using theparseFunctions
method for the streaming version ofcreateChatCompletion
. Ensure that the streaming functionality is tested with the newtools
property.
const messages = parseChatCompletionMessages({ options, variables }) | ||
|
||
const body: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = | ||
{ | ||
model: options.model ?? defaultOpenAIOptions.model, | ||
temperature: options.temperature | ||
? Number(options.temperature) | ||
: undefined, | ||
messages, | ||
...(options.functions?.length | ||
? { | ||
tools: parseFunctions({ options, variables }), | ||
tool_choice: 'auto', | ||
} | ||
: {}), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip
Codebase Verification
The verification process did not find any test cases for the parseFunctions
method within the codebase. It is recommended to implement thorough testing for this method, especially considering its role in parsing user input, which can be a vector for security vulnerabilities.
Analysis chain
Extended createChatCompletion
function to handle custom functions. Ensure that the parseFunctions
method is thoroughly tested to prevent potential security risks when parsing user input.
Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Verify that parseFunctions is covered by tests
rg --type ts "describe\(.*parseFunctions.*\)" -C 3
Length of output: 52
Script:
#!/bin/bash
# Attempt to find any test files related to parseFunctions by looking for files with 'parseFunctions' in their name or content
rg --type ts "parseFunctions" --files-with-matches
Length of output: 167
Script:
#!/bin/bash
# Check for inline tests or test references within parseFunctions.ts
rg --type ts -C 3 "test|it|describe" packages/forge/blocks/openai/helpers/parseFunctions.ts
# Search for a general test directory or common test file naming patterns
rg --type ts "test" --files-with-matches
Length of output: 10832
const response = await (async () => { | ||
while (true) { | ||
const response = await openai.chat.completions.create(body) | ||
|
||
if (!response.choices[0].message.tool_calls) { | ||
return response | ||
} | ||
|
||
messages.push(response.choices[0].message) | ||
|
||
for (const tool of response.choices[0].message.tool_calls) { | ||
const toolName = tool.function.name | ||
const toolArguments = Object.entries( | ||
JSON.parse(tool.function.arguments) | ||
) | ||
.map(([key, value]) => `let ${key} = ${value};`) | ||
.join('\n') | ||
const toolCallId = tool.id | ||
const toolCode = `${toolArguments}\n${ | ||
options.functions?.find((func) => func.name === toolName)?.code | ||
}` | ||
|
||
const context = { | ||
require: () => { | ||
// For safety | ||
throw new Error('require is not allowed') | ||
}, | ||
} | ||
|
||
let toolResponse = '' | ||
try { | ||
toolResponse = vm.runInNewContext(toolCode, context) | ||
} catch (e) { | ||
toolResponse = `${e}` | ||
} | ||
|
||
messages.push({ | ||
role: 'tool', | ||
tool_call_id: toolCallId, | ||
// name: toolName, | ||
content: toolResponse.toString(), | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop within the createChatCompletion
function's run
method does not have a clear exit condition, which could potentially lead to an infinite loop.
- while (true) {
+ while (someCondition) {
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
const response = await (async () => { | |
while (true) { | |
const response = await openai.chat.completions.create(body) | |
if (!response.choices[0].message.tool_calls) { | |
return response | |
} | |
messages.push(response.choices[0].message) | |
for (const tool of response.choices[0].message.tool_calls) { | |
const toolName = tool.function.name | |
const toolArguments = Object.entries( | |
JSON.parse(tool.function.arguments) | |
) | |
.map(([key, value]) => `let ${key} = ${value};`) | |
.join('\n') | |
const toolCallId = tool.id | |
const toolCode = `${toolArguments}\n${ | |
options.functions?.find((func) => func.name === toolName)?.code | |
}` | |
const context = { | |
require: () => { | |
// For safety | |
throw new Error('require is not allowed') | |
}, | |
} | |
let toolResponse = '' | |
try { | |
toolResponse = vm.runInNewContext(toolCode, context) | |
} catch (e) { | |
toolResponse = `${e}` | |
} | |
messages.push({ | |
role: 'tool', | |
tool_call_id: toolCallId, | |
// name: toolName, | |
content: toolResponse.toString(), | |
}) | |
} | |
} | |
const response = await (async () => { | |
while (someCondition) { | |
const response = await openai.chat.completions.create(body) | |
if (!response.choices[0].message.tool_calls) { | |
return response | |
} | |
messages.push(response.choices[0].message) | |
for (const tool of response.choices[0].message.tool_calls) { | |
const toolName = tool.function.name | |
const toolArguments = Object.entries( | |
JSON.parse(tool.function.arguments) | |
) | |
.map(([key, value]) => `let ${key} = ${value};`) | |
.join('\n') | |
const toolCallId = tool.id | |
const toolCode = `${toolArguments}\n${ | |
options.functions?.find((func) => func.name === toolName)?.code | |
}` | |
const context = { | |
require: () => { | |
// For safety | |
throw new Error('require is not allowed') | |
}, | |
} | |
let toolResponse = '' | |
try { | |
toolResponse = vm.runInNewContext(toolCode, context) | |
} catch (e) { | |
toolResponse = `${e}` | |
} | |
messages.push({ | |
role: 'tool', | |
tool_call_id: toolCallId, | |
// name: toolName, | |
content: toolResponse.toString(), | |
}) | |
} | |
} |
Thank you for the help in the implementation of it. Closing it in favor of #1167. I added the support for functions in the Ask assistant action and also renamed things etc... Thank you again for your work on that 🙏 |
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 -->
Closes baptisteArno#863 Got helped from baptisteArno#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 -->
OpenAI Function Calling Documentation
Summary by CodeRabbit