Skip to content

Commit

Permalink
Merge pull request activepieces#5339 from activepieces/fix/improve-di…
Browse files Browse the repository at this point in the history
…sable

fix: improve and disable ask ai on angular
  • Loading branch information
abuaboud authored Aug 12, 2024
2 parents 0d1f01e + d35bdb4 commit a39e7f9
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
ErrorCode,
isNil,
} from '@activepieces/shared'
import { FastifyPluginAsyncTypebox, Type } from '@fastify/type-provider-typebox'
import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
import { FastifyRequest } from 'fastify'
import { StatusCodes } from 'http-status-codes'
import { DEFAULT_PRIORITY } from '../workers/queue/queue-manager'
import { appEventRoutingService } from './app-event-routing.service'
import { FastifyRequest } from 'fastify'

const appWebhooks: Record<string, Piece> = {
slack,
Expand Down
5 changes: 1 addition & 4 deletions packages/server/api/src/app/copilot/copilot.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ export const copilotModule: FastifyPluginAsyncTypebox = async () => {
websocketService.addListener(WebsocketServerEvent.GENERATE_CODE, (socket) => {
return async (data: GenerateCodeRequest) => {
const { prompt, previousContext } = data
const result = await copilotService.generateCode({ prompt, previousContext })
const response: GenerateCodeResponse = {
result,
}
const response: GenerateCodeResponse = await copilotService.generateCode({ prompt, previousContext })
socket.emit(WebsocketClientEvent.GENERATE_CODE_FINISHED, response)
}
})
Expand Down
288 changes: 154 additions & 134 deletions packages/server/api/src/app/copilot/copilot.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AppSystemProp, CopilotInstanceTypes, logger, system } from '@activepieces/server-shared'
import { assertNotNullOrUndefined } from '@activepieces/shared'
import { assertNotNullOrUndefined, GenerateCodeResponse } from '@activepieces/shared'
import OpenAI from 'openai'
import {
ChatCompletionMessageParam,
Expand All @@ -11,12 +11,141 @@ type GenerateCodeParams = {
previousContext: ChatCompletionMessageParam[]
}

const CODE_TOOLS: ChatCompletionTool[] = [
{
type: 'function',
function: {
name: 'generate_code',
description: 'Write TypeScript code snippet based on user prompt.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'The code snippet to write.',
},
inputs: {
type: 'array',
description: 'The inputs used in the code snippet.',
items: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'The name of the input property.',
},
value: {
type: 'string',
description: 'The value to fill the property with.',
},
},
},
},
packages: {
type: 'array',
description: 'The packages imported in the code snippet',
items: {
type: 'string',
description:
'The name of the package, e.g axios, lodash, etc.',
},
},
},
required: ['code'],
},
},
},
]


const CODE_PROMPT: ChatCompletionMessageParam[] = [
{
role: 'user',
content: `
# INTRODUCTION
You are a TypeScript coding bot that helps users turn natural language into usable code, for an open-source automation platform called Activepieces.
# RESPONSE FORMAT
You will not respond to any messages that require a conversational answer.
You will not elaborate.
You MUST respond ONLY with a function call.
You must import syntax since you are using typescript, prefer fetch over axios.
`,
},
{
role: 'user',
content: 'I want code that will combine 2 arrays and only return the unique elements',
},
{
role: 'assistant',
content: null,
function_call: {
name: 'generate_code',
arguments: JSON.stringify({
code: `
export const code = async (inputs) => {
const combinedArray = [...inputs.array1, ...inputs.array2];
const uniqueArray = Array.from(new Set(combinedArray));
return uniqueArray;
};`,
inputs: [
{ key: 'array1', value: '[1,2,3]' },
{ key: 'array2', value: '[4,5,6]' },
],
packages: [],
}),
},
},
{
role: 'user',
content: 'Write me a piece of code that splits the user\'s first name from his last name in a full name string received in inputs.',
},
{
role: 'assistant',
content: null,
function_call: {
name: 'generate_code',
arguments: JSON.stringify({
code: `
export const code = async (inputs) => {
const nameParts = inputs.fullName.split(' ');
const firstName = nameParts[0];
const lastName = nameParts.slice(1).join('');
return { firstName, lastName };
};`,
inputs: [{ key: 'fullName', value: 'John Doe' }],
packages: [],
}),
},
},
{
role: 'user',
content: 'from an array of objects, take the created_at property for each object and print it as an ISO string',
},
{
role: 'assistant',
content: null,
function_call: {
name: 'generate_code',
arguments: JSON.stringify({
code: `
export const code = async (inputs) => {
const isoStrings = inputs.array.map(obj => new Date(obj.created_at).toISOString());
return isoStrings;
};`,
inputs: [
{ key: 'array', value: '[{ "created_at": "2022-01-14T12:34:56Z" }, { "created_at": "2022-01-15T09:45:30Z" }]' },
],
packages: [],
}),
},
},
]

function getOpenAI(): OpenAI {
let openai
const apiKey = system.getOrThrow(AppSystemProp.OPENAI_API_KEY)
const openaiInstanceType = system.getOrThrow<CopilotInstanceTypes>(AppSystemProp.COPILOT_INSTANCE_TYPE)

switch (openaiInstanceType) {
case CopilotInstanceTypes.AZURE_OPENAI: {
const apiVersion = system.getOrThrow(AppSystemProp.AZURE_OPENAI_API_VERSION)
Expand All @@ -39,20 +168,25 @@ function getOpenAI(): OpenAI {
return openai
}

type OpenAIResponse = {
inputs: { key: string, value: string }[]
packages: string[]
code: string
}
export const copilotService = {
async generateCode({ prompt, previousContext }: GenerateCodeParams): Promise<string> {
async generateCode({ prompt, previousContext }: GenerateCodeParams): Promise<GenerateCodeResponse> {
logger.debug({ prompt }, '[CopilotService#generateCode] Prompting...')
const result = await getOpenAI().chat.completions.create({
model: 'gpt-3.5-turbo',
model: 'gpt-4o',
messages: [
...this.createCodeMessageContext(),
...CODE_PROMPT,
...previousContext,
{
role: 'user',
content: prompt,
},
],
tools: this.createCodeTools(),
tools: CODE_TOOLS,
tool_choice: {
type: 'function',
function: {
Expand All @@ -65,133 +199,19 @@ export const copilotService = {
result.choices[0].message.tool_calls,
'OpenAICodeResponse',
)
logger.debug(
{ response: result.choices[0].message.tool_calls[0] },
'[CopilotService#generateCode] Response received...',
)
return result.choices[0].message.tool_calls[0].function.arguments
},

createCodeTools(): ChatCompletionTool[] {
const tools = [
{
type: 'function',
function: {
name: 'generate_code',
description: 'Write TypeScript code snippet based on user prompt.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'The code snippet to write.',
},
inputs: {
type: 'array',
description: 'The inputs used in the code snippet.',
items: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'The name of the input property.',
},
value: {
type: 'string',
description: 'The value to fill the property with.',
},
},
},
},
packages: {
type: 'array',
description: 'The packages imported in the code snippet',
items: {
type: 'string',
description:
'The name of the package, e.g axios, lodash, etc.',
},
},
},
required: ['code'],
},
},
},
]

return tools as ChatCompletionTool[]
},

createCodeMessageContext(): ChatCompletionMessageParam[] {
return [
{
role: 'user',
content: `
# INTRODUCTION
You are a TypeScript coding bot that helps users turn natural language into useable code, for an open-source automation platform called Activepieces.
# RESPONSE FORMAT
You will not respond to any messages that require a conversational answer.
You will not elaborate.
You MUST respond ONLY with a function call.
You will use import to import any libraries you need. You will be penalized for using require. You will be punished for using libraries that are not imported.
`,
const response = JSON.parse(result.choices[0].message.tool_calls[0].function.arguments) as OpenAIResponse
return {
code: response.code,
inputs: response.inputs.reduce((acc, curr) => {
acc[curr.key] = curr.value
return acc
}, {} as Record<string, string>),
packageJson: {
depdedencies: response.packages.reduce((acc, curr) => {
acc[curr] = curr
return acc
}, {} as Record<string, string>),
},
{
role: 'user',
content:
'I want code that will combine 2 arrays and only return the unique elements',
},
{
role: 'assistant',
content: null,
function_call: {
name: 'generate_code',
arguments:
'{ "code": "export const code = async (inputs) => { const combinedArray = [...inputs.array1, ...inputs.array2] const uniqueArray = Array.from(new Set(combinedArray)) return uniqueArray};", "inputs": [ { "key": "array1", "value": "[1,2,3]" }, { "key": "array2", "value": "[4,5,6]" } ], "packages": [] }',
},
},
{
role: 'user',
content:
'Write me a piece of code that splits the user\'s first name from his last name in a full name string received in inputs.',
},
{
role: 'assistant',
content: null,
function_call: {
name: 'generate_code',
arguments:
'{ "code": "export const code = async (inputs) => { const nameParts = inputs.fullName.split(\' \') const firstName = nameParts[0] const lastName = nameParts.slice(1).join(\'\') return { firstName, lastName }};", "inputs": [ { "key": "fullName","value": "John Doe" } ], "packages": [] }',
},
},
{
role: 'user',
content:
'from an array of objects, take the created_at property for each object and print it as an ISO string',
},
{
role: 'assistant',
content: null,
function_call: {
name: 'generate_code',
arguments:
'{ "code": "export const code = async (inputs) => { const isoStrings = inputs.array.map(obj => new Date(obj.created_at).toISOString()) return isoStrings;};", "inputs": [ { "key": "array","value": "[{ "created_at": "2022-01-14T12:34:56Z" }, { "created_at": "2022-01-15T09:45:30Z" } ]" } ], "packages": [] }',
},
},
{
role: 'user',
content: 'Hi',
},
{
role: 'assistant',
content: null,
function_call: {
name: 'generate_code',
arguments:
'{ "code": "export const code = async (inputs) => { return \'Hi\'};", "inputs": [], "packages": [] }',
},
},
]
}
},
}
}
6 changes: 5 additions & 1 deletion packages/shared/src/lib/copilot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export const GenerateCodeRequest = Type.Object({
export type GenerateCodeRequest = Static<typeof GenerateCodeRequest>

export const GenerateCodeResponse = Type.Object({
result: Type.String(),
code: Type.String(),
packageJson: Type.Object({
depdedencies: Type.Record(Type.String(), Type.String()),
}),
inputs: Type.Record(Type.String(), Type.String()),
})

export type GenerateCodeResponse = Static<typeof GenerateCodeResponse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,6 @@
>
<span i18n>Code Editor</span>

<ap-button
btnStyle="stroked"
btnSize="small"
(buttonClicked)="state.openCodeWriterDialog$.emit()"
[disabled]="(generateCodeEnabled$ | async) === false"
[tooltipText]="
(generateCodeEnabled$ | async)
? codeGeneratorTooltip
: disabledCodeGeneratorTooltip
"
i18n-tooltipText
>
<div class="ap-flex ap-gap-3 ap-items-center" i18n>
<svg-icon
src="assets/img/custom/AI.svg"
class="ap-w-[20px] ap-h-[20px] ap-fill-primary"
[applyClass]="true"
></svg-icon>
Ask AI
</div>
</ap-button>

<div class="ap-flex-grow"></div>
@if(!readOnly && (allowNpmPackages$ | async)) {
Expand Down
Loading

0 comments on commit a39e7f9

Please sign in to comment.