ci(backend-review.yml): add linter step to the backend review workflow (#625)

* ci(backend-review.yml): add linter step to the backend review workflow

* chore(backend-review.yml): remove prettier from lint-action configuration

* chore: apply new linting workflow

* chore(lint-staged.config.js): reorder lint-staged tasks for JavaScript and TypeScript files

* chore(eslint): update ignorePatterns in .eslintrc.js
chore(lint-action): remove prettier option in backend-review.yml
chore(package.json): add lint and lint:fix scripts

* chore(lint-staged.config.js): remove prettier --write command for js, jsx, ts, tsx files

* chore(titleConvo.js): remove unnecessary console.log statement
chore(titleConvo.js): add missing comma in options object

* chore: apply linting to all files

* chore(lint-staged.config.js): update lint-staged configuration to include prettier formatting
This commit is contained in:
Danny Avila 2023-07-14 09:36:49 -04:00 committed by GitHub
parent 637bb6bc11
commit e5336039fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
231 changed files with 1688 additions and 1526 deletions

View file

@ -57,7 +57,7 @@ function extractShortVersion(openapiSpec) {
const shortApiSpec = {
openapi: fullApiSpec.openapi,
info: fullApiSpec.info,
paths: {}
paths: {},
};
for (let path in fullApiSpec.paths) {
@ -68,8 +68,8 @@ function extractShortVersion(openapiSpec) {
operationId: fullApiSpec.paths[path][method].operationId,
parameters: fullApiSpec.paths[path][method].parameters?.map((parameter) => ({
name: parameter.name,
description: parameter.description
}))
description: parameter.description,
})),
};
}
}
@ -199,14 +199,16 @@ class AIPluginTool extends Tool {
const apiUrlRes = await fetch(aiPluginJson.api.url, {});
if (!apiUrlRes.ok) {
throw new Error(
`Failed to fetch API spec from ${aiPluginJson.api.url} with status ${apiUrlRes.status}`
`Failed to fetch API spec from ${aiPluginJson.api.url} with status ${apiUrlRes.status}`,
);
}
const apiUrlJson = await apiUrlRes.text();
const shortApiSpec = extractShortVersion(apiUrlJson);
return new AIPluginTool({
name: aiPluginJson.name_for_model.toLowerCase(),
description: `A \`tool\` to learn the API documentation for ${aiPluginJson.name_for_model.toLowerCase()}, after which you can use 'http_request' to make the actual API call. Short description of how to use the API's results: ${aiPluginJson.description_for_model})`,
description: `A \`tool\` to learn the API documentation for ${aiPluginJson.name_for_model.toLowerCase()}, after which you can use 'http_request' to make the actual API call. Short description of how to use the API's results: ${
aiPluginJson.description_for_model
})`,
apiSpec: `
As an AI, your task is to identify the operationId of the relevant API path based on the condensed OpenAPI specifications provided.
@ -228,7 +230,7 @@ ${shortApiSpec}
\`\`\`
`,
openaiSpec: apiUrlJson,
model: model
model: model,
});
}
}

View file

@ -56,11 +56,17 @@ Guidelines:
}
replaceUnwantedChars(inputString) {
return inputString.replace(/\r\n|\r|\n/g, ' ').replace('"', '').trim();
return inputString
.replace(/\r\n|\r|\n/g, ' ')
.replace('"', '')
.trim();
}
getMarkdownImageUrl(imageName) {
const imageUrl = path.join(this.relativeImageUrl, imageName).replace(/\\/g, '/').replace('public/', '');
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
}
@ -70,13 +76,13 @@ Guidelines:
// TODO: Future idea -- could we ask an LLM to extract these arguments from an input that might contain them?
n: 1,
// size: '1024x1024'
size: '512x512'
size: '512x512',
});
const theImageUrl = resp.data.data[0].url;
if (!theImageUrl) {
throw new Error(`No image URL returned from OpenAI API.`);
throw new Error('No image URL returned from OpenAI API.');
}
const regex = /img-[\w\d]+.png/;

View file

@ -23,7 +23,8 @@ class GoogleSearchAPI extends Tool {
* A description for the agent to use
* @type {string}
*/
description = `Use the 'google' tool to retrieve internet search results relevant to your input. The results will return links and snippets of text from the webpages`;
description =
'Use the \'google\' tool to retrieve internet search results relevant to your input. The results will return links and snippets of text from the webpages';
getCx() {
const cx = process.env.GOOGLE_CSE_ID || '';
@ -79,7 +80,7 @@ class GoogleSearchAPI extends Tool {
q: input,
cx: this.cx,
auth: this.apiKey,
num: 5 // Limit the number of results to 5
num: 5, // Limit the number of results to 5
});
// return response.data;
@ -87,7 +88,7 @@ class GoogleSearchAPI extends Tool {
if (!response.data.items || response.data.items.length === 0) {
return this.resultsToReadableFormat([
{ title: 'No good Google Search Result was found', link: '' }
{ title: 'No good Google Search Result was found', link: '' },
]);
}
@ -97,7 +98,7 @@ class GoogleSearchAPI extends Tool {
for (const result of results) {
const metadataResult = {
title: result.title || '',
link: result.link || ''
link: result.link || '',
};
if (result.snippet) {
metadataResult.snippet = result.snippet;

View file

@ -55,7 +55,8 @@ class HttpRequestTool extends Tool {
this.headers = headers;
this.name = 'http_request';
this.maxOutputLength = maxOutputLength;
this.description = `Executes HTTP methods (GET, POST, PUT, DELETE, etc.). The input is an object with three keys: "url", "method", and "data". Even for GET or DELETE, include "data" key as an empty string. "method" is the HTTP method, and "url" is the desired endpoint. If POST or PUT, "data" should contain a stringified JSON representing the body to send. Only one url per use.`;
this.description =
'Executes HTTP methods (GET, POST, PUT, DELETE, etc.). The input is an object with three keys: "url", "method", and "data". Even for GET or DELETE, include "data" key as an empty string. "method" is the HTTP method, and "url" is the desired endpoint. If POST or PUT, "data" should contain a stringified JSON representing the body to send. Only one url per use.';
}
async _call(input) {
@ -77,7 +78,7 @@ class HttpRequestTool extends Tool {
let options = {
method: method,
headers: this.headers
headers: this.headers,
};
if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && data) {

View file

@ -5,7 +5,8 @@ class SelfReflectionTool extends Tool {
super();
this.reminders = 0;
this.name = 'self-reflection';
this.description = `Take this action to reflect on your thoughts & actions. For your input, provide answers for self-evaluation as part of one input, using this space as a canvas to explore and organize your ideas in response to the user's message. You can use multiple lines for your input. Perform this action sparingly and only when you are stuck.`;
this.description =
'Take this action to reflect on your thoughts & actions. For your input, provide answers for self-evaluation as part of one input, using this space as a canvas to explore and organize your ideas in response to the user\'s message. You can use multiple lines for your input. Perform this action sparingly and only when you are stuck.';
this.message = message;
this.isGpt3 = isGpt3;
// this.returnDirect = true;
@ -17,9 +18,9 @@ class SelfReflectionTool extends Tool {
async selfReflect() {
if (this.isGpt3) {
return `I should finalize my reply as soon as I have satisfied the user's query.`;
return 'I should finalize my reply as soon as I have satisfied the user\'s query.';
} else {
return ``;
return '';
}
}
}

View file

@ -26,7 +26,10 @@ Guidelines:
}
getMarkdownImageUrl(imageName) {
const imageUrl = path.join(this.relativeImageUrl, imageName).replace(/\\/g, '/').replace('public/', '');
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
}
@ -43,7 +46,7 @@ Guidelines:
const payload = {
prompt: input.split('|')[0],
negative_prompt: input.split('|')[1],
steps: 20
steps: 20,
};
const response = await axios.post(`${url}/sdapi/v1/txt2img`, payload);
const image = response.data.images[0];
@ -68,8 +71,8 @@ Guidelines:
await sharp(buffer)
.withMetadata({
iptcpng: {
parameters: info
}
parameters: info,
},
})
.toFile(this.outputPath + '/' + imageName);
this.result = this.getMarkdownImageUrl(imageName);

View file

@ -71,7 +71,7 @@ General guidelines:
console.log('Error data:', error.response.data);
return error.response.data;
} else {
console.log(`Error querying Wolfram Alpha`, error.message);
console.log('Error querying Wolfram Alpha', error.message);
// throw error;
return 'There was an error querying Wolfram Alpha.';
}

View file

@ -19,5 +19,5 @@ module.exports = {
StructuredSD,
WolframAlphaAPI,
StructuredWolfram,
SelfReflectionTool
}
SelfReflectionTool,
};

View file

@ -7,7 +7,7 @@ async function saveImageFromUrl(url, outputPath, outputFilename) {
// Fetch the image from the URL
const response = await axios({
url,
responseType: 'stream'
responseType: 'stream',
});
// Check if the output directory exists, if not, create it

View file

@ -20,8 +20,16 @@ Guidelines:
"negative_prompt":"semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, out of frame, low quality, ugly, mutation, deformed"
- Generate images only once per human query unless explicitly requested by the user`;
this.schema = z.object({
prompt: z.string().describe("Detailed keywords to describe the subject, using at least 7 keywords to accurately describe the image, separated by comma"),
negative_prompt: z.string().describe("Keywords we want to exclude from the final image, using at least 7 keywords to accurately describe the image, separated by comma")
prompt: z
.string()
.describe(
'Detailed keywords to describe the subject, using at least 7 keywords to accurately describe the image, separated by comma',
),
negative_prompt: z
.string()
.describe(
'Keywords we want to exclude from the final image, using at least 7 keywords to accurately describe the image, separated by comma',
),
});
}
@ -30,7 +38,10 @@ Guidelines:
}
getMarkdownImageUrl(imageName) {
const imageUrl = path.join(this.relativeImageUrl, imageName).replace(/\\/g, '/').replace('public/', '');
const imageUrl = path
.join(this.relativeImageUrl, imageName)
.replace(/\\/g, '/')
.replace('public/', '');
return `![generated image](/${imageUrl})`;
}
@ -48,7 +59,7 @@ Guidelines:
const payload = {
prompt,
negative_prompt,
steps: 20
steps: 20,
};
const response = await axios.post(`${url}/sdapi/v1/txt2img`, payload);
const image = response.data.images[0];
@ -58,7 +69,17 @@ Guidelines:
// Generate unique name
const imageName = `${Date.now()}.png`;
this.outputPath = path.resolve(__dirname, '..', '..', '..', '..', '..', 'client', 'public', 'images');
this.outputPath = path.resolve(
__dirname,
'..',
'..',
'..',
'..',
'..',
'client',
'public',
'images',
);
const appRoot = path.resolve(__dirname, '..', '..', '..', '..', '..', 'client');
this.relativeImageUrl = path.relative(appRoot, this.outputPath);
@ -72,8 +93,8 @@ Guidelines:
await sharp(buffer)
.withMetadata({
iptcpng: {
parameters: info
}
parameters: info,
},
})
.toFile(this.outputPath + '/' + imageName);
this.result = this.getMarkdownImageUrl(imageName);

View file

@ -18,7 +18,9 @@ Guidelines include:
- Make separate calls for each property and choose relevant 'Assumptions' if results aren't relevant.
- The tool also performs data analysis, plotting, and information retrieval.`;
this.schema = z.object({
nl_query: z.string().describe("Natural language query to WolframAlpha following the guidelines"),
nl_query: z
.string()
.describe('Natural language query to WolframAlpha following the guidelines'),
});
}
@ -61,7 +63,7 @@ Guidelines include:
console.log('Error data:', error.response.data);
return error.response.data;
} else {
console.log(`Error querying Wolfram Alpha`, error.message);
console.log('Error querying Wolfram Alpha', error.message);
// throw error;
return 'There was an error querying Wolfram Alpha.';
}

View file

@ -1,10 +1,7 @@
const { getUserPluginAuthValue } = require('../../../../server/services/PluginService');
const { OpenAIEmbeddings } = require('langchain/embeddings/openai');
const { ZapierToolKit } = require('langchain/agents');
const {
SerpAPI,
ZapierNLAWrapper
} = require('langchain/tools');
const { SerpAPI, ZapierNLAWrapper } = require('langchain/tools');
const { ChatOpenAI } = require('langchain/chat_models/openai');
const { Calculator } = require('langchain/tools/calculator');
const { WebBrowser } = require('langchain/tools/webbrowser');
@ -24,7 +21,7 @@ const validateTools = async (user, tools = []) => {
try {
const validToolsSet = new Set(tools);
const availableToolsToValidate = availableTools.filter((tool) =>
validToolsSet.has(tool.pluginKey)
validToolsSet.has(tool.pluginKey),
);
const validateCredentials = async (authField, toolName) => {
@ -79,14 +76,14 @@ const loadTools = async ({ user, model, functions = null, tools = [], options =
google: GoogleSearchAPI,
wolfram: functions ? StructuredWolfram : WolframAlphaAPI,
'dall-e': OpenAICreateImage,
'stable-diffusion': functions ? StructuredSD : StableDiffusionAPI
'stable-diffusion': functions ? StructuredSD : StableDiffusionAPI,
};
const customConstructors = {
browser: async () => {
let openAIApiKey = options.openAIApiKey ?? process.env.OPENAI_API_KEY;
openAIApiKey = openAIApiKey === 'user_provided' ? null : openAIApiKey;
openAIApiKey = openAIApiKey || await getUserPluginAuthValue(user, 'OPENAI_API_KEY');
openAIApiKey = openAIApiKey || (await getUserPluginAuthValue(user, 'OPENAI_API_KEY'));
return new WebBrowser({ model, embeddings: new OpenAIEmbeddings({ openAIApiKey }) });
},
serpapi: async () => {
@ -97,7 +94,7 @@ const loadTools = async ({ user, model, functions = null, tools = [], options =
return new SerpAPI(apiKey, {
location: 'Austin,Texas,United States',
hl: 'en',
gl: 'us'
gl: 'us',
});
},
zapier: async () => {
@ -113,16 +110,16 @@ const loadTools = async ({ user, model, functions = null, tools = [], options =
new HttpRequestTool(),
await AIPluginTool.fromPluginUrl(
'https://www.klarna.com/.well-known/ai-plugin.json',
new ChatOpenAI({ openAIApiKey: options.openAIApiKey, temperature: 0 })
)
new ChatOpenAI({ openAIApiKey: options.openAIApiKey, temperature: 0 }),
),
];
}
},
};
const requestedTools = {};
const toolOptions = {
serpapi: { location: 'Austin,Texas,United States', hl: 'en', gl: 'us' }
serpapi: { location: 'Austin,Texas,United States', hl: 'en', gl: 'us' },
};
const toolAuthFields = {};
@ -147,7 +144,7 @@ const loadTools = async ({ user, model, functions = null, tools = [], options =
user,
toolAuthFields[tool],
toolConstructors[tool],
options
options,
);
requestedTools[tool] = toolInstance;
}
@ -158,5 +155,5 @@ const loadTools = async ({ user, model, functions = null, tools = [], options =
module.exports = {
validateTools,
loadTools
loadTools,
};

View file

@ -7,11 +7,11 @@ const mockUser = {
var mockPluginService = {
updateUserPluginAuth: jest.fn(),
deleteUserPluginAuth: jest.fn(),
getUserPluginAuthValue: jest.fn()
getUserPluginAuthValue: jest.fn(),
};
jest.mock('../../../../models/User', () => {
return function() {
return function () {
return mockUser;
};
});
@ -42,9 +42,11 @@ describe('Tool Handlers', () => {
mockPluginService.getUserPluginAuthValue.mockImplementation((userId, authField) => {
return userAuthValues[`${userId}-${authField}`];
});
mockPluginService.updateUserPluginAuth.mockImplementation((userId, authField, _pluginKey, credential) => {
userAuthValues[`${userId}-${authField}`] = credential;
});
mockPluginService.updateUserPluginAuth.mockImplementation(
(userId, authField, _pluginKey, credential) => {
userAuthValues[`${userId}-${authField}`] = credential;
},
);
fakeUser = new User({
name: 'Fake User',
@ -57,11 +59,16 @@ describe('Tool Handlers', () => {
role: 'USER',
googleId: null,
plugins: [],
refreshToken: []
refreshToken: [],
});
await fakeUser.save();
for (const authConfig of authConfigs) {
await PluginService.updateUserPluginAuth(fakeUser._id, authConfig.authField, pluginKey, mockCredential);
await PluginService.updateUserPluginAuth(
fakeUser._id,
authConfig.authField,
pluginKey,
mockCredential,
);
}
});
@ -113,14 +120,14 @@ describe('Tool Handlers', () => {
const sampleTools = [...initialTools, 'calculator'];
let ToolClass2 = Calculator;
let remainingTools = availableTools.filter(
(tool) => sampleTools.indexOf(tool.pluginKey) === -1
(tool) => sampleTools.indexOf(tool.pluginKey) === -1,
);
beforeAll(async () => {
toolFunctions = await loadTools({
user: fakeUser._id,
model: BaseChatModel,
tools: sampleTools
tools: sampleTools,
});
loadTool1 = toolFunctions[sampleTools[0]];
loadTool2 = toolFunctions[sampleTools[1]];
@ -161,7 +168,7 @@ describe('Tool Handlers', () => {
toolFunctions = await loadTools({
user: fakeUser._id,
model: BaseChatModel,
tools: [testPluginKey]
tools: [testPluginKey],
});
const Tool = await toolFunctions[testPluginKey]();
expect(Tool).toBeInstanceOf(TestClass);
@ -169,7 +176,7 @@ describe('Tool Handlers', () => {
it('returns an empty object when no tools are requested', async () => {
toolFunctions = await loadTools({
user: fakeUser._id,
model: BaseChatModel
model: BaseChatModel,
});
expect(toolFunctions).toEqual({});
});
@ -179,7 +186,7 @@ describe('Tool Handlers', () => {
user: fakeUser._id,
model: BaseChatModel,
tools: ['stable-diffusion'],
functions: true
functions: true,
});
const structuredTool = await toolFunctions['stable-diffusion']();
expect(structuredTool).toBeInstanceOf(StructuredSD);

View file

@ -2,5 +2,5 @@ const { validateTools, loadTools } = require('./handleTools');
module.exports = {
validateTools,
loadTools
loadTools,
};