Skip to content

base implementation and add playground directory #1

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

Merged
merged 7 commits into from
May 10, 2025
Merged
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
317 changes: 317 additions & 0 deletions .github/workflows/github_comment_trigger.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
name: LLM Coder

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]

permissions:
id-token: write
contents: write
pull-requests: write
issues: write

jobs:
llm-coder:
if: |
((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
contains(github.event.comment.body, '/llm-coder')
)
runs-on: ubuntu-latest
steps:
- name: Debug github.event
run: cat $GITHUB_EVENT_PATH
- name: Set environment variables
run: |
# Handle pull request events first
if [ -n "${{ github.event.pull_request.number }}" ]; then
echo "ISSUE_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
echo "ISSUE_TYPE=pr" >> $GITHUB_ENV
# Handle pull request review events
elif [ -n "${{ github.event.review.body }}" ]; then
echo "ISSUE_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
echo "ISSUE_TYPE=pr" >> $GITHUB_ENV
# Handle issue comment events that reference a PR
elif [ -n "${{ github.event.issue.pull_request }}" ]; then
echo "ISSUE_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV
echo "ISSUE_TYPE=pr" >> $GITHUB_ENV
# Handle regular issue events
else
echo "ISSUE_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV
echo "ISSUE_TYPE=issue" >> $GITHUB_ENV
fi

if [ -n "${{ github.event.review.body }}" ]; then
echo "COMMENT_ID=${{ github.event.review.id || 'None' }}" >> $GITHUB_ENV
else
echo "COMMENT_ID=${{ github.event.comment.id || 'None' }}" >> $GITHUB_ENV
fi
- name: React to trigger comment
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ github.token }}
script: |
const issueType = process.env.ISSUE_TYPE;
const commentId = process.env.COMMENT_ID;

if (commentId !== 'None') {
try {
if (context.eventName === 'pull_request_review_comment') {
await github.rest.reactions.createForPullRequestReviewComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: commentId,
content: 'eyes'
});
} else {
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: commentId,
content: 'eyes'
});
}
} catch (error) {
console.log('Failed to create reaction:', error);
}
}
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: Set up Python
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.13"
- name: Setup LLM Coder from source
run: |
mkdir -p /tmp/llm-coder
git clone https://github.com/igtm/llm-coder.git /tmp/llm-coder
cd /tmp/llm-coder
python -m pip install --upgrade pip
pip install --editable .
pip install boto3
- name: Setup Docker
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Generate Problem Statement
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
let problemStatement = '';
const isPullRequest = !!context.payload.issue?.pull_request;

if (context.eventName === 'issue_comment') {
if (isPullRequest) {
// PRへのコメントの場合
const prNumber = context.payload.issue.number;
// PRの情報を取得
// const { data: pr } = await github.rest.pulls.get({
// owner: context.repo.owner,
// repo: context.repo.repo,
// pull_number: prNumber
// });

// 変更ファイル一覧を取得
// const { data: files } = await github.rest.pulls.listFiles({
// owner: context.repo.owner,
// repo: context.repo.repo,
// pull_number: prNumber
// });

// const changedFiles = files.map(file => `- ${file.filename}`).join('\n');

problemStatement = `この指示に従ってコード修正してください:${context.payload.comment.body.replace('/llm-coder', '')}`;
} else {
// Issue へのコメントの場合
problemStatement = `
以下のGithub Issueの内容を解決できるようにコードを修正してください
Issue のタイトル: ${context.payload.issue.title}
Issue の本文: ${context.payload.issue.body}
コメント: ${context.payload.comment.body.replace('/llm-coder', '')}
`;
}
} else if (context.eventName === 'pull_request_review_comment') {
// PR review comment の場合
problemStatement = `
以下の指示に従って指示がなされたファイルのみを修正してください:
${context.payload.comment.body.replace('/llm-coder', '')}

指示がなされたファイル: ${context.payload.comment.path}
指示がなされた行番号: ${context.payload.comment.line}

指示がなされたコードの周辺差分: ${context.payload.comment.diff_hunk}

注意:指示がなされたファイル以外の変更は行わないでください。基本は、指示がなされた行番号付近のコードを修正してください。
`;
}

console.log('Event Name:', context.eventName);
console.log('Is Pull Request:', isPullRequest);
core.exportVariable('PROBLEM_STATEMENT', problemStatement);
- name: Run LLM Coder
run: |
# Escape newlines and quotes in problem statement
ESCAPED_PROBLEM_STATEMENT=$(echo "$PROBLEM_STATEMENT" | awk '{printf "%s\\n", $0}' | sed 's/"/\\"/g')

llm-coder "$ESCAPED_PROBLEM_STATEMENT"
env:
GITHUB_TOKEN: ${{ github.token }}
AWS_REGION_NAME: us-west-2
- name: Prepare Git and Push Changes
id: git_changes
run: |
# Configure git user
git config user.email "noemail@igtm.link" && git config user.name "LLM-Coder"

# Apply patch
git add .

# Check if there are any changes
if ! git diff --staged --quiet; then
echo "SKIP_REMAINING=false" >> $GITHUB_ENV
# Issue comment かつ PR でない場合は新しいブランチを作成
if [ "${{ github.event_name }}" = "issue_comment" ] && [ -z "${{ github.event.issue.pull_request }}" ]; then
BRANCH_NAME="LLM-Coder-fix-#${{ env.ISSUE_NUMBER }}-$(echo $RANDOM | md5sum | cut -c1-8)"
echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_ENV
git checkout -b "$BRANCH_NAME"
else
# PR に対するコメントの場合は、その PR のブランチを使用
PR_NUMBER=${{ env.ISSUE_NUMBER }}
PR_DATA=$(gh pr view $PR_NUMBER --json headRefName)
BRANCH_NAME=$(echo $PR_DATA | jq -r .headRefName)
echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_ENV
git fetch origin $BRANCH_NAME
git stash --include-untracked
git checkout $BRANCH_NAME
git stash pop || true
fi

git commit -m "Fixed by LLM-Coder" -m "Closes #${{ env.ISSUE_NUMBER }}"
git push origin "$BRANCH_NAME"
else
echo "SKIP_REMAINING=true" >> $GITHUB_ENV
echo "No changes to commit. LLM-Coder did not modify any files."
exit 0
fi
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Install OpenAI Package
if: env.SKIP_REMAINING != 'true'
run: npm install openai @azure/openai

- name: Create Pull Request
if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request && env.SKIP_REMAINING != 'true' }}
continue-on-error: true
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
OPENAI_API_KEY: ${{ secrets.LLM_API_KEY }}
with:
github-token: ${{ github.token }}
script: |
const { repo, owner } = context.repo;
const issueUrl = context.payload.issue?.html_url;
const issueNumber = process.env.ISSUE_NUMBER;

// Get issue title if available
let issueTitle = "llm-coder";
try {
const issue = await github.rest.issues.get({
owner,
repo,
issue_number: parseInt(issueNumber)
});
issueTitle = issue.data.title;
} catch (error) {
console.log('Could not fetch issue title:', error);
}

// Get diff content
const { execSync } = require('child_process');
const diff = execSync('git diff HEAD^').toString();

// Generate PR description using OpenAI API
const { OpenAI } = require("openai");
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const modelName = '${{ vars.LLM_MODEL || 'gpt-4.1-nano' }}';
const prompt = `Generate a concise Pull Request description based on the following git diff. Explain the main changes and their purpose. Format it nicely using Markdown.\n\nGit diff:\n\`\`\`diff\n${diff}\n\`\`\``;
let generatedDescription = 'Automated code changes by LLM Coder.';
try {
const completion = await client.chat.completions.create({
model: modelName, messages: [{ role: 'user', content: prompt }],
});
generatedDescription = completion.choices[0].message.content;
} catch (error) {
console.error("Error generating PR description:", error);
}
const prBody = `${generatedDescription}\n\n---\n*Created by LLM Coder based on [Issue #${issueNumber}](${context.payload.issue.html_url}).*\n*Triggered by [comment](${triggerCommentUrl}).*`;

const pr = await github.rest.pulls.create({
owner,
repo,
title: `LLM-Coder[bot]: ${issueTitle}`,
head: process.env.BRANCH_NAME,
base: 'develop',
body: prBody,
draft: true
});

console.log(`Pull Request created: ${pr.data.html_url}`);

- name: Add PR Comment
if: ${{ (github.event_name != 'issue_comment' || github.event.issue.pull_request) && env.SKIP_REMAINING != 'true' }}
continue-on-error: true
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
OPENAI_API_KEY: ${{ secrets.LLM_API_KEY }}
with:
github-token: ${{ github.token }}
script: |
const { repo, owner } = context.repo;
const prNumber = process.env.ISSUE_NUMBER;

// Get diff content
const { execSync } = require('child_process');
const diff = execSync('git diff HEAD^').toString();

const { OpenAI } = require("openai");
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const modelName = '${{ vars.LLM_MODEL || 'gpt-4.1-nano' }}';

const prompt = `Generate a concise comment explaining the changes introduced by the following git commit diff. Format it nicely using Markdown.\n\nGit diff:\n\`\`\`diff\n${diff}\n\`\`\``;
let generatedDescription = 'Applied automated code changes.';
try {
const completion = await client.chat.completions.create({
model: modelName,
messages: [{ role: 'user', content: prompt }],
});
generatedDescription = completion.choices[0].message.content;
} catch (error) {
console.error("Error generating comment description:", error);
}

let commentBody = `🤖 LLM Coder applied the following changes based on your request:\n\n${generatedDescription}\n\n---\n*Changes pushed to branch \`${branchName}\`.*`;

// PRのdiffコメントの場合は、そのスレッドに返信
if (context.eventName === 'pull_request_review_comment') {
const comment = await github.rest.pulls.createReplyForReviewComment({
owner,
repo,
pull_number: prNumber,
comment_id: context.payload.comment.id,
body: commentBody
});
console.log(`Reply added to review comment: ${comment.data.html_url}`);
} else {
// それ以外の場合は PR 全体にコメント
const comment = await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: commentBody
});
console.log(`Comment added: ${comment.data.html_url}`);
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ wheels/
.mypy_cache/
.ruff_cache/
.pytest_cache/
llm_coder_config.toml
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
Loading