Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added LLM-based issue summarizer. #1927

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/actions/bot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ function buildCommand(uuid, payload, name, args) {
return new EchoCommand(uuid, payload, args);
case "ci":
return new CICommand(uuid, payload, args);
case "summarize"
return new SummarizeCommand(uuid, payload, args);
default:
console.log(`Unknown command: ${name}`);
return null;
Expand Down Expand Up @@ -147,6 +149,20 @@ class EchoCommand {
}
}

class SummarizeCommand {
constructor(uuid, payload, args){
console.log("Constructor");
console.log(JSON.stringify(uuid));
console.log(JSON.stringify(payload));
console.log(JSON.stringify(args));
}

async run(author, github) {
console.log("Run!");
console.log(JSON.stringify(author));
console.log(JSON.stringify(github));
}
}
class CICommand {
workflow_goal_prefix = "workflow:";
constructor(uuid, payload, args) {
Expand Down
111 changes: 111 additions & 0 deletions .github/actions/summarizer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const github = require('@actions/github');
const core = require('@actions/core');
const token = process.env.GITHUB_TOKEN;
const octokit = new github.getOctokit(token);
const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");

(async () => {
const context = github.context;
const payload = context.payload;
const author = payload.comment.user.login;
const authorized = ["OWNER", "MEMBER"].includes(payload.comment.author_association);
// Ensure that invoker is either a Owner or member of awslabs / amazon-eks-ami
if (!authorized) {
console.log(`Comment author is not authorized: ${author}`);
return;
}
console.log(`Comment author is authorized: ${author}`);
Comment on lines +11 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to extend the existing bot vs reimplementing the authz here


// Split the command into parts
const parts = process.env.COMMENT_BODY.trim().split(' ');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the comment has more than just the command in it? what if it has multiple commands?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, it requires that you only type a single command. Working on moving the summarizer over to the existing bot.


// Commands can take three forms:
// /summarize owner repo issue_no (length 4)
// /summarize issue_no (length 2) (defaults owner & repo to context based)
// /summarize (default) (defaults owner, repo, & issue_no to context based)
let issueContext = {
owner: parts.length == 4 ? parts[1] : context.repo.owner,
repo: parts.length == 4 ? parts[2] : context.repo.repo,
issue_number: parts.length == 4 ? parts[3] : ( parts.length == 2 ? parts[1] : context.issue.number),
};

console.log("Issue Context:\n" + JSON.stringify(issueContext));

const { data: issue } = await octokit.rest.issues.get(issueContext);

const { data: comments } = await octokit.rest.issues.listComments(issueContext);

const commentLog = "Comment Log:\n" +
`${issue.user.login} created the issue:\n ${issue.body}\n` +
comments.filter(c => c.user.login != "github-actions[bot]")
.filter(c -> !c.body.startsWith("/"))
.map(c => `${c.user.login} says:\n "${c.body}"`)
.join('\n');

//let commentLog = "Comment Log:\n";
//commentLog += `${issue.user.login} created the issue:\n "${issue.body}"\n`
//for (const comment of comments) {
// if(
// (comment.user.login != "github-actions[bot]") &&
// (!comment.body.startsWith("/"))
// ){
// commentLog += `${comment.user.login} says:\n"${comment.body}"\n`
// }
//}

const client = new BedrockRuntimeClient({ region: process.env.AWS_REGION });

// There can be a lot more prompt engineering done for the perfect summarizations, this one works really well however.
const prompt = `Give me a short summary of this GitHub Issue reply chain. Include details on what the issue is, and what was the conclusion. The full comment history is below: ${commentLog}`;

const content = [
{
type: "text",
text: `Human: ${prompt}\nAssistant:`
}
];

const messages = [
{
role: "user",
content,
}
];

const modelInput = {
anthropic_version: "bedrock-2023-05-31",
max_tokens: 16384, // Adjust this if issue comment chain is long.
messages: messages
};

const command = new InvokeModelCommand({
contentType: "application/json",
body: JSON.stringify(modelInput),
modelId: process.env.MODEL_ID,
});

console.log("Prompting LLM with:\n" + JSON.stringify(modelInput));

try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. too much going on in a single try/catch
  2. if you just catch the error, log it, and rethrow -- you don't need the try/catch

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, revising in next revision.

const response = await client.send(command);
} catch (error) {
console.log("Failure: Unable to access Bedrock. Either invalid credentials or a service outage!");
throw error;
}

const responseBody = JSON.parse(new TextDecoder().decode(response.body));
const generation = responseBody.content[0].text;

console.log(`Raw response:\n${JSON.stringify(response)}`);
console.log(`parsed response:\n${generation}`);

await octokit.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: generation,
});

console.log("Finished!");
return;
})();
2 changes: 1 addition & 1 deletion .github/workflows/bot-trigger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- created
jobs:
bot:
if: ${{ github.event.issue.pull_request }}
#if: ${{ github.event.issue.pull_request }}
runs-on: ubuntu-latest
permissions: write-all
steps:
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/summarize.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Summarizer
on:
issue_comment:
types: [created]
permissions:
id-token: write
contents: read
issues: write
env:
AWS_REGION : "us-west-2"
jobs:
#summarize:
# if: contains(github.event.comment.body, '/summarize')
# runs-on: ubuntu-latest
# steps:
# # Checkout the repo
# - name: Checkout repository
# uses: actions/checkout@v2
# # Get Nodejs lib
# - name: Set up Node.js
# uses: actions/setup-node@v2
# with:
# node-version: '14'
# # Install github actions lib
# - name: Install github actions lib
# run: npm install @actions/[email protected]
# - name: Install core actions lib
# run: npm install @actions/core
# - name: Install aws bedrock lib
# run: npm install @aws-sdk/client-bedrock-runtime
# # Get AWS Credentials
# - name: configure aws credentials
# uses: aws-actions/configure-aws-credentials@v4
# with:
# role-to-assume: ${{ secrets.BEDROCK_ACTION_ROLE_ARN }}
# aws-region: ${{ env.AWS_REGION }}
# role-session-name: GITHUB_ACTION
# # invoke Bedrock to summarize the issue
# - name: Summarize issues
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# MODEL_ID : "anthropic.claude-3-5-sonnet-20240620-v1:0"
# COMMENT_BODY: ${{ github.event.comment.body }}
# run: node .github/actions/summarizer/index.js