Skip to content

Commit

Permalink
Merge pull request #29 from craigloewen-msft/dev/ailabels
Browse files Browse the repository at this point in the history
Implement an 'AI Labels' page
  • Loading branch information
craigloewen-msft authored Sep 10, 2024
2 parents 7dbfe5e + 885554a commit 079cdba
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 363 deletions.
12 changes: 12 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ if (process.env.NODE_ENV == 'production') {
config.azureOpenAIAPIKey = process.env.azureOpenAIAPIKey;
config.azureEndpointURL = process.env.azureEndpointURL;
config.debugDisableEmbeddings = false;
config.debugDisableAILabels = false;
hostPort = process.env.PORT ? process.env.PORT : 8080;
} else {
mongooseConnectionString = config.devMongoDBConnectionString;
Expand Down Expand Up @@ -636,6 +637,17 @@ app.post('/api/getmentions', authenticateToken, async function (req, res) {
}
});

app.post('/api/getailabels', async function (req, res) {
try {
var returnData = await dataHandler.getAILabels(req.body);

return res.json({ success: true, aiLabels: returnData });
} catch (error) {
let errorToString = error.toString();
return res.json(returnFailure(error));
}
});

// User data APIs

app.post('/api/setuserrepo', authenticateToken, async function (req, res) {
Expand Down
71 changes: 71 additions & 0 deletions backendsrc/aiLabelHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const { AzureOpenAI } = require('openai');
const { GetRepoLabels } = require('./helpers');
const { Semaphore } = require("async-mutex");

class aiLabelHandler {

static deploymentId = "auto-label-test";
static apiVersion = "2024-04-01-preview";

constructor(inConfigObject) {

// Set up azureClient and Pinecone
this.maxConcurrentRequests = 1;
this.azureSemaphore = new Semaphore(this.maxConcurrentRequests);

this.debugDisableAILabels = inConfigObject.debugDisableAILabels;
if (!this.debugDisableAILabels) {
this.azureClient = new AzureOpenAI({endpoint: inConfigObject.azureEndpointURL, apiKey: inConfigObject.azureOpenAIAPIKey, apiVersion: aiLabelHandler.apiVersion, deployment: aiLabelHandler.deploymentId});
}
}

async generateAILabels(repoName, issueTitle, issueBody) {

const labels = await GetRepoLabels(repoName);

let messages = [
{
role: "system", content: `Your task is to apply labels to the incoming GitHub issue. Do not output anything else besides the label output that is requested by the user.`
},
{
role: "user", content: `Please help me label an incoming GitHub issue.
Here is the list of labels that you have available to you.
They are listed in priority order (So most used labels are at the top, least used are at the bottom).
Each label has its name in bold, and then a description of when it should be applied afterwards.
${labels}
And here is the issue info:
# Issue info:
**Title**: ${issueTitle}
${issueBody}
# Final instructions
Please output the labels that you think should be applied to this issue.
Only use the labels that are in the list above.
Output them as a comma seperated list, with no spaces between labels, and with 'Output labels:' infront. Output nothing else.
Example output: \`Output Labels: Label A, Label B, Label C\`
Now please give your output below.` }
];
try {
const result = await this.azureClient.chat.completions.create({
messages: messages,
temperature: 0.001,
model: "",
});
return result.choices[0].message.content;
} catch (error) {
console.error('Error getting labels from Azure OpenAI:', error);
return '';
}
}
}

module.exports = aiLabelHandler;
23 changes: 23 additions & 0 deletions backendsrc/helpers.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
const { getEncoding } = require("js-tiktoken")
const fs = require('fs');
const path = require('path');

module.exports = {
PromiseTimeout(delayms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, delayms);
});
},
GetRepoLabels(inputRepoName) {
const filePath = path.join(process.cwd(), 'label_lists', `${inputRepoName}.json`);
let labelsString = "# Available labels\n\n";

try {
const data = fs.readFileSync(filePath, 'utf8');
const labels = JSON.parse(data);

for (let label of labels) {
labelsString += `- **${label.labelName}**: ${label.labelDescription}\n`;
}
} catch (error) {
if (error.code === 'ENOENT') {
labelsString += "Label file not found.\n";
} else {
labelsString += "An error occurred while reading the label file.\n";
}
}

return labelsString;
},
GetMentions(inputString) {
let mentionsPattern = /\B@[a-z0-9_-]+/gi;
if (inputString == null) {
Expand Down
1 change: 1 addition & 0 deletions backendsrc/refreshRepoHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ class RefreshRepoHandler {

}

// TO DO: Refresh repo labels once new schema is set up.
async startRefreshingRepos() {
if (!this.refreshingRepos) {
this.refreshingRepos = true;
Expand Down
42 changes: 40 additions & 2 deletions backendsrc/webDataHandler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const RefreshRepoHandler = require('./refreshRepoHandler')
const RepoScanner = require('./repoScanner')
const embeddingsHandler = require('./embeddingsHandler')
const aiLabelHandler = require('./aiLabelHandler')
const axios = require('axios');
const mongoose = require('mongoose');
const ObjectId = mongoose.Types.ObjectId;
Expand All @@ -24,6 +25,7 @@ class WebDataHandler {
this.configObject = inConfigObject;

this.embeddingsHandler = new embeddingsHandler(this.configObject);
this.aiLabelHandler = new aiLabelHandler(this.configObject);
this.refreshRepoHandler = new RefreshRepoHandler(this.RepoDetails, this.IssueDetails,
this.IssueCommentDetails, this.UserDetails, this.IssueCommentMentionDetails, this.IssueReadDetails,
this.ghToken, this.IssueLinkDetails, this.embeddingsHandler);
Expand Down Expand Up @@ -2069,7 +2071,8 @@ class WebDataHandler {

if (!addedLabelList.includes(labelVisitor.name)) {
addedLabelList.push(labelVisitor.name);
let labelNode = { id: labelVisitor.name, name: labelVisitor.name, totalVal: 1, graphVal: 1, group: "label" };
let labelURL = labelVisitor.url.replace("https://api.github.com/repos/", "https://github.com/").replace("labels/","issues/?q=label%3A");
let labelNode = { id: labelVisitor.name, name: labelVisitor.name, totalVal: 1, graphVal: 1, url: labelURL, group: "label" };
nodeReturnDictionary[labelVisitor.name] = labelNode;
}

Expand Down Expand Up @@ -2143,7 +2146,7 @@ class WebDataHandler {
async getSimilarIssues(queryData) {
const { organizationName, repoName, issueTitle, issueBody } = queryData;

let issueDescription = GetDescription(issueTitle, issueBody) // to do rewrite to take in issue title and body
let issueDescription = GetDescription(issueTitle, issueBody)

let dbRepoName = (organizationName + "/" + repoName).toLowerCase();

Expand Down Expand Up @@ -2174,6 +2177,41 @@ class WebDataHandler {

return returnArray;
}

async getAILabels(queryData) {

const { issueID } = queryData;

let issue = await this.IssueDetails.findOne({ _id: issueID });

if (!issue) {
return [];
}

const repo = await this.RepoDetails.findOne({ _id: issue.repoRef });

if (!repo) {
return [];
}

let repoName = repo.shortURL.split("/").pop();

const aiLabelsString = await this.aiLabelHandler.generateAILabels(repoName, issue.title, issue.body);

const aiLabelsData = aiLabelsString.replace("Output Labels: ", "").split(",");

let returnLabels = [];

for (let i = 0; i < aiLabelsData.length; i++) {
let aiLabel = aiLabelsData[i];
returnLabels.push({
name: aiLabel,
color: "AA0000",
});
}

return returnLabels;
}
}

module.exports = WebDataHandler;
1 change: 1 addition & 0 deletions defaultconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ module.exports = {
'azureEndpointURL' : "url",
'pineconeAPIKey' : 'key',
'debugDisableEmbeddings': true,
'debugDisableAILabels': true,
};
Loading

0 comments on commit 079cdba

Please sign in to comment.