Skip to content

Commit 4352fb2

Browse files
committed
fix(review): review refactoring, fixed openai pooling
1 parent d7921bc commit 4352fb2

5 files changed

Lines changed: 205 additions & 114 deletions

File tree

.github/actions/javascript/proposalPoliceComment/index.js

Lines changed: 97 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11554,31 +11554,14 @@ const date_fns_tz_1 = __nccwpck_require__(99297);
1155411554
const ActionUtils_1 = __nccwpck_require__(96981);
1155511555
const CONST_1 = __importDefault(__nccwpck_require__(29873));
1155611556
const GithubUtils_1 = __importDefault(__nccwpck_require__(19296));
11557-
const sanitizeJSONStringValues_1 = __importDefault(__nccwpck_require__(40136));
11557+
const proposalPolice_1 = __importDefault(__nccwpck_require__(67282));
1155811558
const OpenAIUtils_1 = __importDefault(__nccwpck_require__(23956));
1155911559
function isCommentCreatedEvent(payload) {
1156011560
return payload.action === CONST_1.default.ACTIONS.CREATED;
1156111561
}
1156211562
function isCommentEditedEvent(payload) {
1156311563
return payload.action === CONST_1.default.ACTIONS.EDITED;
1156411564
}
11565-
class ProposalPoliceTemplates {
11566-
static getPromptForNewProposalTemplateCheck(commentBody) {
11567-
return `I NEED HELP WITH CASE (1.), CHECK IF COMMENT IS PROPOSAL AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "${CONST_1.default.NO_ACTION}" IN CASE THE COMMENT IS NOT A PROPOSAL. Comment content: ${commentBody}`;
11568-
}
11569-
static getPromptForNewProposalDuplicateCheck(existingProposal, newProposalBody) {
11570-
return `I NEED HELP WITH CASE (3.) [INSTRUCTIONS SECTION: IX. DUPLICATE PROPOSAL DETECTION], COMPARE THE FOLLOWING TWO PROPOSALS AND RETURN A SIMILARITY PERCENTAGE (0-100) REPRESENTING HOW SIMILAR THESE TWO PROPOSALS ARE IN THOSE SECTIONS AS PER THE INSTRUCTIONS. \n\nProposal 1:\n${existingProposal}\n\nProposal 2:\n${newProposalBody}`;
11571-
}
11572-
static getPromptForEditedProposal(previousBody, editedBody) {
11573-
return `I NEED HELP WITH CASE (2.) WHEN A USER THAT POSTED AN INITIAL PROPOSAL OR COMMENT (UNEDITED) THEN EDITS THE COMMENT - WE NEED TO CLASSIFY THE COMMENT BASED IN THE GIVEN INSTRUCTIONS AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "${CONST_1.default.NO_ACTION}" IN CASE THE COMMENT IS NOT A PROPOSAL. \n\nPrevious comment content: ${previousBody}.\n\nEdited comment content: ${editedBody}`;
11574-
}
11575-
static getDuplicateCheckWithdrawMessage() {
11576-
return '#### 🚫 Duplicated proposal withdrawn by 🤖 ProposalPolice.';
11577-
}
11578-
static getDuplicateCheckNoticeMessage(proposalAuthor) {
11579-
return `⚠️ @${proposalAuthor} Your proposal is a duplicate of an already existing proposal and has been automatically withdrawn to prevent spam. Please review the existing proposals before submitting a new one.`;
11580-
}
11581-
}
1158211565
// Main function to process the workflow event
1158311566
async function run() {
1158411567
// Capture the timestamp immediately at the start of the run
@@ -11629,37 +11612,37 @@ async function run() {
1162911612
console.log('Get comments for issue #', issueNumber);
1163011613
const commentsResponse = await GithubUtils_1.default.getAllCommentDetails(issueNumber);
1163111614
console.log('commentsResponse', commentsResponse);
11632-
// Find previous proposals
11633-
const previousProposals = commentsResponse?.filter((comment) => new Date(comment.created_at).getTime() < newProposalCreatedAt && comment.body?.includes(CONST_1.default.PROPOSAL_KEYWORD));
1163411615
let didFindDuplicate = false;
11635-
for (const previousProposal of previousProposals) {
11616+
for (const previousProposal of commentsResponse) {
1163611617
const isProposal = !!previousProposal.body?.includes(CONST_1.default.PROPOSAL_KEYWORD);
11618+
const previousProposalCreatedAt = new Date(previousProposal.created_at).getTime();
11619+
// Early continue if not a proposal or previous comment is newer than current one
11620+
if (!isProposal || previousProposalCreatedAt >= newProposalCreatedAt) {
11621+
continue;
11622+
}
1163711623
const isAuthorBot = previousProposal.user?.login === CONST_1.default.COMMENT.NAME_GITHUB_ACTIONS || previousProposal.user?.type === CONST_1.default.COMMENT.TYPE_BOT;
11638-
// Skip prompting if comment is author is the GH bot or comment is empty / not a proposal
11639-
if (isAuthorBot || !isProposal) {
11624+
// Skip prompting if comment author is the GH bot
11625+
if (isAuthorBot) {
1164011626
continue;
1164111627
}
11642-
const duplicateCheckPrompt = ProposalPoliceTemplates.getPromptForNewProposalDuplicateCheck(previousProposal.body, newProposalBody);
11628+
const duplicateCheckPrompt = proposalPolice_1.default.getPromptForNewProposalDuplicateCheck(previousProposal.body, newProposalBody);
1164311629
const duplicateCheckResponse = await openAI.promptAssistant(assistantID, duplicateCheckPrompt);
1164411630
let similarityPercentage = 0;
11645-
try {
11646-
const parsedDuplicateCheckResponse = JSON.parse((0, sanitizeJSONStringValues_1.default)(duplicateCheckResponse));
11647-
console.log('parsedDuplicateCheckResponse: ', parsedDuplicateCheckResponse);
11631+
const parsedDuplicateCheckResponse = openAI.parseAssistantResponse(duplicateCheckResponse);
11632+
console.log('parsedDuplicateCheckResponse: ', parsedDuplicateCheckResponse);
11633+
if (parsedDuplicateCheckResponse) {
1164811634
const { similarity = 0 } = parsedDuplicateCheckResponse ?? {};
1164911635
similarityPercentage = (0, ActionUtils_1.convertToNumber)(similarity);
11650-
}
11651-
catch (e) {
11652-
console.error('Failed to parse AI response:', duplicateCheckResponse);
11653-
}
11654-
if (similarityPercentage >= 90) {
11655-
console.log(`Found duplicate with ${similarityPercentage}% similarity.`);
11656-
didFindDuplicate = true;
11657-
break;
11636+
if (similarityPercentage >= 90) {
11637+
console.log(`Found duplicate with ${similarityPercentage}% similarity.`);
11638+
didFindDuplicate = true;
11639+
break;
11640+
}
1165811641
}
1165911642
}
1166011643
if (didFindDuplicate) {
11661-
const duplicateCheckWithdrawMessage = ProposalPoliceTemplates.getDuplicateCheckWithdrawMessage();
11662-
const duplicateCheckNoticeMessage = ProposalPoliceTemplates.getDuplicateCheckNoticeMessage(newProposalAuthor);
11644+
const duplicateCheckWithdrawMessage = proposalPolice_1.default.getDuplicateCheckWithdrawMessage();
11645+
const duplicateCheckNoticeMessage = proposalPolice_1.default.getDuplicateCheckNoticeMessage(newProposalAuthor);
1166311646
// If a duplicate proposal is detected, update the comment to withdraw it
1166411647
console.log('ProposalPolice™ withdrawing duplicated proposal...');
1166511648
await GithubUtils_1.default.octokit.issues.updateComment({
@@ -11676,10 +11659,10 @@ async function run() {
1167611659
}
1167711660
}
1167811661
const prompt = isCommentCreatedEvent(payload)
11679-
? ProposalPoliceTemplates.getPromptForNewProposalTemplateCheck(payload.comment?.body)
11680-
: ProposalPoliceTemplates.getPromptForEditedProposal(payload.changes.body?.from, payload.comment?.body);
11662+
? proposalPolice_1.default.getPromptForNewProposalTemplateCheck(payload.comment?.body)
11663+
: proposalPolice_1.default.getPromptForEditedProposal(payload.changes.body?.from, payload.comment?.body);
1168111664
const assistantResponse = await openAI.promptAssistant(assistantID, prompt);
11682-
const parsedAssistantResponse = JSON.parse((0, sanitizeJSONStringValues_1.default)(assistantResponse));
11665+
const parsedAssistantResponse = openAI.parseAssistantResponse(assistantResponse);
1168311666
console.log('parsedAssistantResponse: ', parsedAssistantResponse);
1168411667
// fallback to empty strings to avoid crashing in case parsing fails
1168511668
const { action = '', message = '' } = parsedAssistantResponse ?? {};
@@ -12490,6 +12473,38 @@ function sanitizeJSONStringValues(inputString) {
1249012473
exports["default"] = sanitizeJSONStringValues;
1249112474

1249212475

12476+
/***/ }),
12477+
12478+
/***/ 67282:
12479+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
12480+
12481+
"use strict";
12482+
12483+
var __importDefault = (this && this.__importDefault) || function (mod) {
12484+
return (mod && mod.__esModule) ? mod : { "default": mod };
12485+
};
12486+
Object.defineProperty(exports, "__esModule", ({ value: true }));
12487+
const CONST_1 = __importDefault(__nccwpck_require__(29873));
12488+
class ProposalPoliceTemplates {
12489+
static getPromptForNewProposalTemplateCheck(commentBody) {
12490+
return `I NEED HELP WITH CASE (1.), CHECK IF COMMENT IS PROPOSAL AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "${CONST_1.default.NO_ACTION}" IN CASE THE COMMENT IS NOT A PROPOSAL. Comment content: ${commentBody}`;
12491+
}
12492+
static getPromptForNewProposalDuplicateCheck(existingProposal, newProposalBody) {
12493+
return `I NEED HELP WITH CASE (3.) [INSTRUCTIONS SECTION: IX. DUPLICATE PROPOSAL DETECTION], COMPARE THE FOLLOWING TWO PROPOSALS AND RETURN A SIMILARITY PERCENTAGE (0-100) REPRESENTING HOW SIMILAR THESE TWO PROPOSALS ARE IN THOSE SECTIONS AS PER THE INSTRUCTIONS. \n\nProposal 1:\n${existingProposal}\n\nProposal 2:\n${newProposalBody}`;
12494+
}
12495+
static getPromptForEditedProposal(previousBody, editedBody) {
12496+
return `I NEED HELP WITH CASE (2.) WHEN A USER THAT POSTED AN INITIAL PROPOSAL OR COMMENT (UNEDITED) THEN EDITS THE COMMENT - WE NEED TO CLASSIFY THE COMMENT BASED IN THE GIVEN INSTRUCTIONS AND IF TEMPLATE IS FOLLOWED AS PER INSTRUCTIONS. IT IS MANDATORY THAT YOU RESPOND ONLY WITH "${CONST_1.default.NO_ACTION}" IN CASE THE COMMENT IS NOT A PROPOSAL. \n\nPrevious comment content: ${previousBody}.\n\nEdited comment content: ${editedBody}`;
12497+
}
12498+
static getDuplicateCheckWithdrawMessage() {
12499+
return '#### 🚫 Duplicated proposal withdrawn by 🤖 ProposalPolice.';
12500+
}
12501+
static getDuplicateCheckNoticeMessage(proposalAuthor) {
12502+
return `⚠️ @${proposalAuthor} Your proposal is a duplicate of an already existing proposal and has been automatically withdrawn to prevent spam. Please review the existing proposals before submitting a new one.`;
12503+
}
12504+
}
12505+
exports["default"] = ProposalPoliceTemplates;
12506+
12507+
1249312508
/***/ }),
1249412509

1249512510
/***/ 23956:
@@ -12502,6 +12517,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
1250212517
};
1250312518
Object.defineProperty(exports, "__esModule", ({ value: true }));
1250412519
const openai_1 = __importDefault(__nccwpck_require__(60047));
12520+
const sanitizeJSONStringValues_1 = __importDefault(__nccwpck_require__(40136));
1250512521
const retryWithBackoff_1 = __importDefault(__nccwpck_require__(54583));
1250612522
class OpenAIUtils {
1250712523
/**
@@ -12512,6 +12528,18 @@ class OpenAIUtils {
1251212528
* The maximum amount of time to wait for a thread to produce a response.
1251312529
*/
1251412530
static POLL_TIMEOUT = 90000;
12531+
/**
12532+
* The role of the `user` in the OpenAI model.
12533+
*/
12534+
static USER = 'user';
12535+
/**
12536+
* The role of the `assistant` in the OpenAI model.
12537+
*/
12538+
static ASSISTANT = 'assistant';
12539+
/**
12540+
* The status of a completed run in the OpenAI model.
12541+
*/
12542+
static OPENAI_RUN_COMPLETED = 'completed';
1251512543
/**
1251612544
* The maximum number of requests to make when polling for thread completion.
1251712545
*/
@@ -12546,29 +12574,30 @@ class OpenAIUtils {
1254612574
* Prompt a pre-defined assistant.
1254712575
*/
1254812576
async promptAssistant(assistantID, userMessage) {
12549-
// start a thread run
12550-
let threadRun = await (0, retryWithBackoff_1.default)(() => this.client.beta.threads.createAndRun({
12551-
/* eslint-disable @typescript-eslint/naming-convention */
12577+
// 1. Create a thread
12578+
const thread = await (0, retryWithBackoff_1.default)(() => this.client.beta.threads.create({
12579+
messages: [{ role: OpenAIUtils.USER, content: userMessage }],
12580+
}), { isRetryable: (err) => OpenAIUtils.isRetryableError(err) });
12581+
// 2. Create a run on the thread
12582+
let run = await (0, retryWithBackoff_1.default)(() => this.client.beta.threads.runs.create(thread.id, {
12583+
// eslint-disable-next-line @typescript-eslint/naming-convention
1255212584
assistant_id: assistantID,
12553-
thread: {
12554-
messages: [{ role: 'user', content: userMessage }],
12555-
},
1255612585
}), { isRetryable: (err) => OpenAIUtils.isRetryableError(err) });
12557-
// poll for completion
12586+
// 3. Poll for completion
1255812587
let response = '';
1255912588
let count = 0;
1256012589
while (!response && count < OpenAIUtils.MAX_POLL_COUNT) {
12561-
// await thread run completion
12562-
threadRun = await this.client.beta.threads.runs.retrieve(threadRun.thread_id, { thread_id: threadRun.id });
12563-
if (threadRun.status !== 'completed') {
12590+
// eslint-disable-next-line @typescript-eslint/naming-convention
12591+
run = await this.client.beta.threads.runs.retrieve(run.id, { thread_id: thread.id });
12592+
if (run.status !== OpenAIUtils.OPENAI_RUN_COMPLETED) {
1256412593
count++;
1256512594
await new Promise((resolve) => {
1256612595
setTimeout(resolve, OpenAIUtils.POLL_RATE);
1256712596
});
1256812597
continue;
1256912598
}
12570-
for await (const message of this.client.beta.threads.messages.list(threadRun.thread_id)) {
12571-
if (message.role !== 'assistant') {
12599+
for await (const message of this.client.beta.threads.messages.list(thread.id)) {
12600+
if (message.role !== OpenAIUtils.ASSISTANT) {
1257212601
continue;
1257312602
}
1257412603
response += message.content
@@ -12610,6 +12639,22 @@ class OpenAIUtils {
1261012639
}
1261112640
return false;
1261212641
}
12642+
parseAssistantResponse(response) {
12643+
const sanitized = (0, sanitizeJSONStringValues_1.default)(response);
12644+
let parsed;
12645+
try {
12646+
parsed = JSON.parse(sanitized);
12647+
}
12648+
catch (e) {
12649+
console.error('Failed to parse AI response as JSON:', response);
12650+
return null;
12651+
}
12652+
if (typeof parsed !== 'object' || typeof parsed.action !== 'string' || typeof parsed.message !== 'string') {
12653+
console.error('AI response missing required fields:', parsed);
12654+
return null;
12655+
}
12656+
return parsed;
12657+
}
1261312658
}
1261412659
exports["default"] = OpenAIUtils;
1261512660

0 commit comments

Comments
 (0)