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 5 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
106 changes: 106 additions & 0 deletions .github/actions/summarizer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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);

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`
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Building the string like this is a bit clunky, maybe try something like:

const commentLog = comments.filter(c => c.user.login != "github-actions[bot]")
    .map(c => `${c.user.login} says: ...`)
    .join('\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 messages = [
{
role: "user",
content: []
}
];

messages[0].content.push({
type: "text",
text: `
Human: ${prompt}
Assistant:
`
});
Copy link
Member

Choose a reason for hiding this comment

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

Why don't you move this into the declaration of messages above?


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);

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!");
} catch (error) {
console.log(error)
throw error;
}
})();
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"
Copy link
Member

Choose a reason for hiding this comment

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

If this is going to be hard-coded, it can just go in your Action js script

Copy link
Author

@trm109 trm109 Aug 21, 2024

Choose a reason for hiding this comment

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

True, will migrate in next revision. Originally the idea was to be able to hot-swap models if needed, but I've noticed the inputs vary too much from model to model to be feasible.

COMMENT_BODY: ${{ github.event.comment.body }}
run: node .github/actions/summarizer/index.js