diff --git a/server/brain/codex/docs.ts b/server/brain/codex/docs.ts index 1f88af1..530358d 100644 --- a/server/brain/codex/docs.ts +++ b/server/brain/codex/docs.ts @@ -1,12 +1,35 @@ import { getLanguageContextById } from 'brain/languages'; import { addComments } from 'brain/helpers'; -import { Synopsis, Property, ParamExplained, FunctionSynopsis, TypedefSynopsis } from 'parsing/types'; -import { EXPLAIN_PARAM, EXPLAIN_SIMPLE, SUMMARIZE_FUNCTION, - EXPLAIN_CONTEXT, EXPLAIN_PROPERTY, SUMMARIZE_TYPE, GET_RETURN, SUMMARIZE_FUNCTION_SIMPLE, SUMMARIZE_CLASS, CodexCall, SUMMARIZE_CLASS_SIMPLE } from 'brain/codex/prompt'; +import { + Synopsis, + Property, + ParamExplained, + FunctionSynopsis, + TypedefSynopsis, +} from 'parsing/types'; +import { + EXPLAIN_PARAM, + EXPLAIN_SIMPLE, + SUMMARIZE_FUNCTION, + EXPLAIN_CONTEXT, + EXPLAIN_PROPERTY, + SUMMARIZE_TYPE, + GET_RETURN, + SUMMARIZE_FUNCTION_SIMPLE, + SUMMARIZE_CLASS, + OpenAPICall, + SUMMARIZE_CLASS_SIMPLE, +} from 'brain/codex/prompt'; import { writeFunctionInFormat } from 'formatting/functions'; import { formatTypedef } from 'formatting/typedef'; import { DocFormat } from 'constants/enums'; -import { chooseDocstringPrompt, formatReturnExplained, getSummaryFromMultipleResponses, makeCodexCall, OpenAIResponse } from 'brain/codex/helpers'; +import { + chooseDocstringPrompt, + formatReturnExplained, + getSummaryFromMultipleResponses, + makeCodexCall, + OpenAIResponse, +} from 'brain/codex/helpers'; import { AxiosResponse } from 'axios'; import { Custom } from 'routes/writer/helpers'; import { getMultipleTranslations } from 'services/translate'; @@ -16,60 +39,98 @@ const MAX_CHARACTER_COUNT = 12000; export type DocstringPrompt = { docstring: string; promptId: string; -} +}; -const responseToDocstringPrompt = (responses: AxiosResponse[], callTypes: CodexCall[]): DocstringPrompt[] => { +const responseToDocstringPrompt = ( + responses: AxiosResponse[], + callTypes: OpenAPICall[] +): DocstringPrompt[] => { const docstringPrompts: DocstringPrompt[] = []; - for(let i=0; i => { - const paramPromises = synopsis.params?.map((param) => { - return makeCodexCall(EXPLAIN_PARAM, code, languageCommented, { parameter: param.name }); - }) ?? []; +const getFunctionDocstringPrompt = async ( + synopsis: FunctionSynopsis, + code: string, + languageCommented: string, + docFormat: DocFormat, + custom: Custom +): Promise => { + const paramPromises = + synopsis.params?.map((param) => { + return makeCodexCall(EXPLAIN_PARAM, code, languageCommented, { parameter: param.name }); + }) ?? []; - const returnPromise = synopsis.returns ? makeCodexCall(GET_RETURN, code, languageCommented) : null; + const returnPromise = synopsis.returns + ? makeCodexCall(GET_RETURN, code, languageCommented) + : null; const summaryPromise = makeCodexCall(SUMMARIZE_FUNCTION, code, languageCommented); const summarySimplePromise = makeCodexCall(SUMMARIZE_FUNCTION_SIMPLE, code, languageCommented); const docPromises = [returnPromise, summaryPromise, summarySimplePromise, ...paramPromises]; const docResponses = await Promise.all(docPromises); - let paramsExplained: ParamExplained[] = paramPromises.length > 0 ? docResponses.slice(3).map((param, i) => { - const explained = param.data.choices[0].text.trim(); - return { - ...synopsis.params[i], - explanation: explained - } - }) : []; + let paramsExplained: ParamExplained[] = + paramPromises.length > 0 + ? docResponses.slice(3).map((param, i) => { + const explained = param.data.choices[0].text.trim(); + return { + ...synopsis.params[i], + explanation: explained, + }; + }) + : []; const [returnRes, summaryRes, summarySimpleRes] = docResponses; - const docstringPrompts = responseToDocstringPrompt([summaryRes, summarySimpleRes], [SUMMARIZE_FUNCTION, SUMMARIZE_FUNCTION_SIMPLE]); + const docstringPrompts = responseToDocstringPrompt( + [summaryRes, summarySimpleRes], + [SUMMARIZE_FUNCTION, SUMMARIZE_FUNCTION_SIMPLE] + ); const docstringPrompt = chooseDocstringPrompt(docstringPrompts); let returnExplained = formatReturnExplained(returnRes?.data?.choices[0].text); // Adding premium feature for additional language translation if (custom.language) { const paramsExplinations = paramsExplained.map((paramExplained) => paramExplained.explanation); - const translatedResults = await getMultipleTranslations([docstringPrompt.docstring, returnExplained, ...paramsExplinations], custom.language); - const [translatedDocstring, translatedReturnExplained, ...translatedParamsExplained] = translatedResults; + const translatedResults = await getMultipleTranslations( + [docstringPrompt.docstring, returnExplained, ...paramsExplinations], + custom.language + ); + const [translatedDocstring, translatedReturnExplained, ...translatedParamsExplained] = + translatedResults; docstringPrompt.docstring = translatedDocstring; returnExplained = translatedReturnExplained; paramsExplained = paramsExplained.map((paramExplained, i) => { return { ...paramExplained, - explanation: translatedParamsExplained[i] - } + explanation: translatedParamsExplained[i], + }; }); } -const docstring = writeFunctionInFormat(docFormat, docstringPrompt.docstring, paramsExplained, returnExplained, synopsis.returnsType, custom); + const docstring = writeFunctionInFormat( + docFormat, + docstringPrompt.docstring, + paramsExplained, + returnExplained, + synopsis.returnsType, + custom + ); return { docstring, promptId: docstringPrompt.promptId }; -} +}; -const getTypedefDocstring = async (synopsis: TypedefSynopsis, code: string, languageCommented: string, docFormat: DocFormat, custom: Custom): Promise => { +const getTypedefDocstring = async ( + synopsis: TypedefSynopsis, + code: string, + languageCommented: string, + docFormat: DocFormat, + custom: Custom +): Promise => { const propertiesPromises = synopsis.properties?.map((property) => { return makeCodexCall(EXPLAIN_PROPERTY, code, languageCommented, { property: property.name }); }); @@ -78,39 +139,54 @@ const getTypedefDocstring = async (synopsis: TypedefSynopsis, code: string, lang const docPromises = [summaryPromise, ...propertiesPromises]; const docResponses = await Promise.all(docPromises); - let propertiesWithExplanation: Property[] = docResponses.slice(1)?.map((param, i) => { + let propertiesWithExplanation: Property[] = docResponses.slice(1)?.map((param, i) => { return { ...synopsis.properties[i], - explanation: param.data.choices[0].text.trim() - } + explanation: param.data.choices[0].text.trim(), + }; }); const [summaryRes] = docResponses; let summary = getSummaryFromMultipleResponses(summaryRes.data.choices[0].text); // Adding languages feature if (custom.language) { - const propertiesExplanations = propertiesWithExplanation.map((propertyExplained) => propertyExplained.explanation); - const translatedResults = await getMultipleTranslations([summary, ...propertiesExplanations], custom.language); + const propertiesExplanations = propertiesWithExplanation.map( + (propertyExplained) => propertyExplained.explanation + ); + const translatedResults = await getMultipleTranslations( + [summary, ...propertiesExplanations], + custom.language + ); const [translatedSummary, ...translatedPropertiesExplained] = translatedResults; summary = translatedSummary; propertiesWithExplanation = propertiesWithExplanation.map((propertyExplained, i) => { return { ...propertyExplained, - explanation: translatedPropertiesExplained[i] - } + explanation: translatedPropertiesExplained[i], + }; }); } return formatTypedef(docFormat, summary, propertiesWithExplanation); -} +}; -const getTypedefDocstringPrompt = async (synopsis: TypedefSynopsis, code: string, languageCommented: string, docFormat: DocFormat, custom: Custom): Promise => { +const getTypedefDocstringPrompt = async ( + synopsis: TypedefSynopsis, + code: string, + languageCommented: string, + docFormat: DocFormat, + custom: Custom +): Promise => { const docstring = await getTypedefDocstring(synopsis, code, languageCommented, docFormat, custom); - return { docstring, promptId: SUMMARIZE_TYPE.id}; -} + return { docstring, promptId: SUMMARIZE_TYPE.id }; +}; -const getClassDocstring = async (code: string, languageCommented: string, custom: Custom): Promise => { - const callOptions: CodexCall[] = [SUMMARIZE_CLASS, SUMMARIZE_CLASS_SIMPLE]; +const getClassDocstring = async ( + code: string, + languageCommented: string, + custom: Custom +): Promise => { + const callOptions: OpenAPICall[] = [SUMMARIZE_CLASS, SUMMARIZE_CLASS_SIMPLE]; const promises = callOptions.map((option) => { return makeCodexCall(option, code, languageCommented); }); @@ -118,14 +194,17 @@ const getClassDocstring = async (code: string, languageCommented: string, custom const docstringPrompts: DocstringPrompt[] = callOptions.map((option, i) => { return { docstring: responses[i]?.data?.choices[0].text, - promptId: option.id - } + promptId: option.id, + }; }); const selectedPrompt = chooseDocstringPrompt(docstringPrompts); if (custom.language) { - const translatedResults = await getMultipleTranslations([selectedPrompt.docstring], custom.language); + const translatedResults = await getMultipleTranslations( + [selectedPrompt.docstring], + custom.language + ); const [translatedDocstring] = translatedResults; selectedPrompt.docstring = translatedDocstring; } @@ -133,33 +212,51 @@ const getClassDocstring = async (code: string, languageCommented: string, custom return selectedPrompt; }; -const getSimpleSummary = async (code: string, languageCommented: string, context: string, custom: Custom): Promise => { +const getSimpleSummary = async ( + code: string, + languageCommented: string, + context: string, + custom: Custom +): Promise => { let summaryContextPromise; if (context.length + code.length <= MAX_CHARACTER_COUNT) { summaryContextPromise = makeCodexCall(EXPLAIN_CONTEXT, code, languageCommented, { context }); } const summarySimplePromise = makeCodexCall(EXPLAIN_SIMPLE, code, languageCommented); - const [summaryContext, summarySimple] = await Promise.all([summaryContextPromise, summarySimplePromise]); + const [summaryContext, summarySimple] = await Promise.all([ + summaryContextPromise, + summarySimplePromise, + ]); const docstringOptions: DocstringPrompt[] = [ - { docstring: summaryContext?.data.choices[0]?.text, promptId: EXPLAIN_CONTEXT.id}, - { docstring: summarySimple?.data.choices[0]?.text, promptId: EXPLAIN_SIMPLE.id } + { docstring: summaryContext?.data.choices[0]?.text, promptId: EXPLAIN_CONTEXT.id }, + { docstring: summarySimple?.data.choices[0]?.text, promptId: EXPLAIN_SIMPLE.id }, ]; const selectedPrompt = chooseDocstringPrompt(docstringOptions); if (custom.language) { - const translatedResults = await getMultipleTranslations([selectedPrompt.docstring], custom.language); + const translatedResults = await getMultipleTranslations( + [selectedPrompt.docstring], + custom.language + ); const [translatedDocstring] = translatedResults; selectedPrompt.docstring = translatedDocstring; } return selectedPrompt; -} +}; -export const getDocstringPrompt = async (code: string, synopsis: Synopsis, languageId: string | null, docFormat: DocFormat, context: string, custom: Custom = {}): Promise => { +export const getDocstringPrompt = async ( + code: string, + synopsis: Synopsis, + languageId: string | null, + docFormat: DocFormat, + context: string, + custom: Custom = {} +): Promise => { const languageContext = getLanguageContextById(languageId); const languageCommented = addComments(languageContext.name, languageId); - switch(synopsis.kind) { + switch (synopsis.kind) { case 'function': return await getFunctionDocstringPrompt(synopsis, code, languageCommented, docFormat, custom); case 'class': @@ -169,4 +266,4 @@ export const getDocstringPrompt = async (code: string, synopsis: Synopsis, langu default: return await getSimpleSummary(code, languageCommented, context, custom); } -} +}; diff --git a/server/brain/codex/helpers.ts b/server/brain/codex/helpers.ts index fe1b137..e898751 100644 --- a/server/brain/codex/helpers.ts +++ b/server/brain/codex/helpers.ts @@ -1,35 +1,44 @@ import axios, { AxiosResponse } from 'axios'; import { CommentPosition } from 'constants/enums'; import { CURSOR_MARKER, EMPTY_PROMPT } from 'constants/values'; -import { CodexCall, CustomComponent } from './prompt'; +import { OpenAPICall, CustomComponent } from './prompt'; import { DocstringPrompt } from './docs'; import dotenv from 'dotenv'; dotenv.config(); -export const CUSHMAN_CODEX_COMPLETIONS = 'https://api.openai.com/v1/engines/code-cushman-001/completions'; -export const DAVINCI_CODEX_COMPLETIONS = 'https://api.openai.com/v1/engines/code-davinci-002/completions'; +export const GPT_COMPLETIONS = 'https://api.openai.com/v1/completions'; +export const GPT_MODEL = 'gpt-3.5-turbo'; export const OPENAI_AUTHORIZATION = { headers: { - Authorization: `Bearer ${process.env.OPENAI_TOKEN}` - } + Authorization: `Bearer ${process.env.OPENAI_TOKEN}`, + }, }; export type OpenAIResponse = { choices: { - text: string, - }[], -} + text: string; + }[]; +}; -export const makeCodexCall = (call: CodexCall, code: string, languageCommented: string, custom?: CustomComponent): Promise> => { +export const makeCodexCall = ( + call: OpenAPICall, + code: string, + languageCommented: string, + custom?: CustomComponent +): Promise> => { const { prompt, stop, temperature, maxTokens, engineEndpoint } = call; - return axios.post(engineEndpoint, { - prompt: prompt(code, languageCommented, custom), - temperature, - max_tokens: maxTokens, - stop - }, OPENAI_AUTHORIZATION); -} + return axios.post( + engineEndpoint, + { + prompt: prompt(code, languageCommented, custom), + temperature, + max_tokens: maxTokens, + stop, + }, + OPENAI_AUTHORIZATION + ); +}; export const sanityCheck = (response: string): string => { if (!response) return response; @@ -39,7 +48,7 @@ export const sanityCheck = (response: string): string => { const withoutItIs = removedQuotes.replace(/^it is /gim, ''); const upperCaseFirstCharacter = withoutItIs.charAt(0).toUpperCase() + withoutItIs.slice(1); return upperCaseFirstCharacter; -} +}; /** * Get the first valid summary from multiple responses. @@ -68,14 +77,14 @@ export const chooseDocstringPrompt = (docstringPrompts: DocstringPrompt[]): Docs // Use marker to indicate that no summary was found and identify location for cursor placement return EMPTY_PROMPT; -} +}; export const getLocationAndRemoveMarker = (docstring: string, position: CommentPosition) => { const markerIndex = docstring.indexOf(CURSOR_MARKER); if (markerIndex === -1) { return { docstring, - } + }; } const upToIndex = docstring.substring(0, markerIndex); @@ -84,16 +93,19 @@ export const getLocationAndRemoveMarker = (docstring: string, position: CommentP cursorMarker: { line: upToIndex.split('\n').length - 1 + specialPositionIncrement, character: Number.MAX_SAFE_INTEGER, - message: 'Unable to generate summary' + message: 'Unable to generate summary', }, docstring: docstring.replace(CURSOR_MARKER, ''), - } -} + }; +}; export const formatReturnExplained = (returnExplained: string | null): string => { if (!returnExplained) return ''; const trimmed = returnExplained.trim(); - const withoutIntro = trimmed.replace(/^(the function|it is|the function is)\s(returns|returning)\s/i, ''); + const withoutIntro = trimmed.replace( + /^(the function|it is|the function is)\s(returns|returning)\s/i, + '' + ); return withoutIntro; -} \ No newline at end of file +}; diff --git a/server/brain/codex/prompt.ts b/server/brain/codex/prompt.ts index c67954e..7b45994 100644 --- a/server/brain/codex/prompt.ts +++ b/server/brain/codex/prompt.ts @@ -1,47 +1,51 @@ -import { DAVINCI_CODEX_COMPLETIONS } from 'brain/codex/helpers'; +import { GPT_COMPLETIONS, GPT_MODEL } from 'brain/codex/helpers'; export type CustomComponent = { - parameter?: string, - property?: string, - context?: string, -} + parameter?: string; + property?: string; + context?: string; +}; -export type CodexCall = { - id: string, - engineEndpoint: string, - prompt: (code: string, languageCommented: string, custom?: CustomComponent) => string, - stop: string[] - temperature: number, - maxTokens: number -} +export type OpenAPICall = { + id: string; + model: string; + engineEndpoint: string; + prompt: (code: string, languageCommented: string, custom?: CustomComponent) => string; + stop: string[]; + temperature: number; + maxTokens: number; +}; -export const EXPLAIN_PARAM: CodexCall = { +export const EXPLAIN_PARAM: OpenAPICall = { id: 'explain-param', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, _, { parameter }: CustomComponent): string => `${code} ### Here's what the above parameters are: ${parameter}: `, -stop: ['###', '\n'], + stop: ['###', '\n'], temperature: 0, maxTokens: 60, -} +}; -export const SUMMARIZE_FUNCTION: CodexCall = { +export const SUMMARIZE_FUNCTION: OpenAPICall = { id: 'summarize-function', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, languageCommented: string): string => `${languageCommented} ${code} ### Here's a one sentence summary of the above function: `, temperature: 0, maxTokens: 200, - stop: ['##', '``', '.\n\n'] -} + stop: ['##', '``', '.\n\n'], +}; -export const SUMMARIZE_FUNCTION_SIMPLE: CodexCall = { +export const SUMMARIZE_FUNCTION_SIMPLE: OpenAPICall = { id: 'summarize-function-simple', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, language: string): string => `${language} ${code} ### @@ -49,37 +53,40 @@ Question: What does the above function do? Answer: `, temperature: 0, maxTokens: 200, - stop: ['##', '``', '\n\n'] -} + stop: ['##', '``', '\n\n'], +}; -export const GET_RETURN: CodexCall = { +export const GET_RETURN: OpenAPICall = { id: 'return', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string): string => `${code} ### Question: What is being returned? Answer: `, temperature: 0, maxTokens: 80, - stop: ['###', '\n\n'] -} + stop: ['###', '\n\n'], +}; // class -export const SUMMARIZE_CLASS: CodexCall = { +export const SUMMARIZE_CLASS: OpenAPICall = { id: 'summarize-function', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, languageCommented: string): string => `${languageCommented} ${code} ### Here's a one sentence summary of the above class: `, temperature: 0, maxTokens: 200, - stop: ['##', '``', '.\n\n'] -} + stop: ['##', '``', '.\n\n'], +}; -export const SUMMARIZE_CLASS_SIMPLE: CodexCall = { +export const SUMMARIZE_CLASS_SIMPLE: OpenAPICall = { id: 'summarize-function-simple', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, language: string): string => `${language} ${code} ### @@ -87,26 +94,27 @@ Question: What does the above class do? Answer: `, temperature: 0, maxTokens: 200, - stop: ['##', '``', '\n\n'] -} + stop: ['##', '``', '\n\n'], +}; // typedef -export const SUMMARIZE_TYPE: CodexCall = { +export const SUMMARIZE_TYPE: OpenAPICall = { id: 'summarize-type', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, language: string): string => `${language} ${code} ### Here's a one sentence summary of the above type: `, temperature: 0, maxTokens: 120, - stop: ['###', '##', '```'] -} - + stop: ['###', '##', '```'], +}; -export const EXPLAIN_PROPERTY: CodexCall = { +export const EXPLAIN_PROPERTY: OpenAPICall = { id: 'explain-property', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, _, { property }: CustomComponent): string => `${code} ### Here's what the above properties are: @@ -114,12 +122,13 @@ ${property}: `, temperature: 0, maxTokens: 60, stop: ['###', '\n'], -} +}; // unspecified -export const EXPLAIN_SIMPLE: CodexCall = { +export const EXPLAIN_SIMPLE: OpenAPICall = { id: 'simple', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, language: string): string => `${language} ${code} ### @@ -127,12 +136,13 @@ Question: What is the above code doing? Answer: `, temperature: 0, maxTokens: 120, - stop: ['###', 'Question:', '```', '\n\n'] -} + stop: ['###', 'Question:', '```', '\n\n'], +}; -export const EXPLAIN_CONTEXT: CodexCall = { +export const EXPLAIN_CONTEXT: OpenAPICall = { id: 'context', - engineEndpoint: DAVINCI_CODEX_COMPLETIONS, + engineEndpoint: GPT_COMPLETIONS, + model: GPT_MODEL, prompt: (code: string, language: string, { context }: CustomComponent): string => `${language} ${context} ### @@ -140,5 +150,5 @@ Question: What is \`${code}\` doing? Answer: `, temperature: 0, maxTokens: 240, - stop: ['###', 'Question:', '```', '\n\n'] -} \ No newline at end of file + stop: ['###', 'Question:', '```', '\n\n'], +};