Skip to content
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

Closed
wants to merge 2 commits into from

Conversation

vforvilela
Copy link
Contributor

@vforvilela vforvilela commented Jan 17, 2024

OpenAI Function Calling Documentationimage

Summary by CodeRabbit

  • New Features
    • Enhanced chat functionality to include the ability to execute custom functions within chat completions for richer interactions.

Copy link

vercel bot commented Jan 17, 2024

@vforvilela is attempting to deploy a commit to the Typebot Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

coderabbitai bot commented Jan 17, 2024

Walkthrough

The update introduces the ability to execute custom functions within OpenAI chat completions, enhancing the createChatCompletion function. This is achieved by adding a functions array to the options object and utilizing the vm module to run these functions in a secure, sandboxed environment. Additionally, a new helper, parseFunctions, has been added to process these custom functions and integrate them into the chat completion tools.

Changes

File Path Change Summary
.../forge/blocks/openai/actions/createChatCompletion.tsx Extended functionality to include custom functions execution using vm module in chat completions.
.../forge/blocks/openai/helpers/parseFunctions.ts Added new helper to parse custom functions into ChatCompletionTool objects.

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?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 8771def and 519b35f.
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 and vm 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 the options 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 the create method call for the OpenAI chat completions, ensuring that the parsed functions are included in the request.

Comment on lines +6 to +40
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) ?? [],
},
},
})) ?? []
)
}
Copy link

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.

Comment on lines 171 to 231
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(),
})
}
}
Copy link

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.

Copy link

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 519b35f and 1bceb60.
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 the options 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 to vm.runInNewContext is secure and that the executed code cannot escape the sandbox or access sensitive data.
  • 270-271: The tools property is set using the parseFunctions method for the streaming version of createChatCompletion. Ensure that the streaming functionality is tested with the new tools property.

Comment on lines +171 to +186
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',
}
: {}),
}
Copy link

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

Comment on lines +188 to +231
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(),
})
}
}
Copy link

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.

Suggested change
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(),
})
}
}

@baptisteArno
Copy link
Owner

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 🙏

baptisteArno added a commit that referenced this pull request Jan 19, 2024
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 -->
thercd pushed a commit to thercd/typebot.io that referenced this pull request Feb 2, 2024
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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants