rsyslog/.github/workflows/label-issues.yml
2025-06-10 17:41:55 +02:00

186 lines
7.9 KiB
YAML

name: Auto-label Issues with Gemini
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue Number (for manual testing of existing issue)'
required: false
type: number
issue_title:
description: 'Issue Title (for manual testing)'
required: false
default: ''
issue_body:
description: 'Issue Body (for manual testing)'
required: false
default: ''
jobs:
label_issue:
runs-on: ubuntu-latest
permissions:
issues: write # Grant write permission for issues to this job
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install Google Generative AI client library
run: pip install google-generativeai
- name: Determine Issue Content and Number
id: get_issue_context
shell: bash # Explicitly set shell to bash for robust string handling
env:
# Define your available labels here, separated by commas
AVAILABLE_LABELS: "bug,feature,documentation,question,enhancement,performance,security,configuration"
run: |
# Initialize variables
ISSUE_NUMBER=""
ISSUE_TITLE=""
ISSUE_BODY=""
# Determine context based on trigger event
if [[ "${{ github.event_name }}" == "issues" ]]; then
ISSUE_NUMBER="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}"
ISSUE_BODY="${{ github.event.issue.body }}"
echo "Triggered by issue creation event."
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
# For manual dispatch, use inputs
ISSUE_NUMBER="${{ github.event.inputs.issue_number }}"
ISSUE_TITLE="${{ github.event.inputs.issue_title }}"
ISSUE_BODY="${{ github.event.inputs.issue_body }}"
echo "Triggered by manual workflow_dispatch."
fi
# Set output variables for use in subsequent steps using GITHUB_OUTPUT file
echo "ISSUE_NUMBER=${ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
echo "ISSUE_TITLE=${ISSUE_TITLE}" >> "$GITHUB_OUTPUT"
# --- FIX for multiline ISSUE_BODY using EOF delimiter ---
echo "ISSUE_BODY<<EOF_BODY_TEXT" >> "$GITHUB_OUTPUT"
echo "${ISSUE_BODY}" >> "$GITHUB_OUTPUT"
echo "EOF_BODY_TEXT" >> "$GITHUB_OUTPUT"
# --------------------------------------------------------
echo "AVAILABLE_LABELS=${{ env.AVAILABLE_LABELS }}" >> "$GITHUB_OUTPUT"
- name: Call Gemini API to suggest label
id: gemini_call
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
# Use outputs from the previous step as environment variables for the Python script
ISSUE_TITLE: ${{ steps.get_issue_context.outputs.ISSUE_TITLE }}
ISSUE_BODY: ${{ steps.get_issue_context.outputs.ISSUE_BODY }}
AVAILABLE_LABELS: ${{ steps.get_issue_context.outputs.AVAILABLE_LABELS }}
GITHUB_OUTPUT: ${{ github.output }} # Ensure GITHUB_OUTPUT path is available to Python
run: |
python <<EOF
import google.generativeai as genai
import os
import json
api_key = os.environ.get('GEMINI_API_KEY')
if not api_key:
print("Error: GEMINI_API_KEY not found in environment variables. Exiting.")
# Ensure fallback also writes to GITHUB_OUTPUT
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write("SUGGESTED_LABEL=no label\n")
exit(1) # Exit with an error code
genai.configure(api_key=api_key)
# Using gemini-2.0-flash as per your modification
model = genai.GenerativeModel('gemini-2.0-flash')
issue_title = os.environ['ISSUE_TITLE']
issue_body = os.environ['ISSUE_BODY']
available_labels = os.environ['AVAILABLE_LABELS'].split(',')
# Construct a robust prompt. Instruct it to only output the label name and choose from the list.
prompt = f"""
You are an automated issue labeling bot for the rsyslog open source project.
Based on the following GitHub issue, suggest the single most appropriate label from the list provided.
Only output the label name. Do NOT output any additional text or explanation.
You MUST choose one label from the 'Available labels' list.
Available labels: {', '.join(available_labels)}
Issue Title: {issue_title}
Issue Body:
{issue_body}
"""
suggested_label_raw = "no label" # Default fallback for API errors or unexpected responses
try:
# Add generation_config to ensure the model responds with only plain text.
response = model.generate_content(prompt, generation_config=genai.types.GenerationConfig(
response_mime_type='text/plain' # Ensure plain text output
))
suggested_label_raw = response.text.strip().lower()
# Clean and validate the suggested label against available labels (case-insensitive check)
cleaned_label = "no label" # Default if no valid match found in the list
for label in available_labels:
if suggested_label_raw == label.lower():
cleaned_label = label
break
# If Gemini suggested something not in our exact list (despite prompt instructions)
if cleaned_label == "no label":
print(f"Warning: Gemini suggested '{suggested_label_raw}', which was not an exact match in the available labels list. Defaulting to 'no label'.")
print(f"Suggested Label: {cleaned_label}")
# --- CRITICAL FIX: Write to GITHUB_OUTPUT file instead of using ##vso[task.setvariable] ---
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write(f"SUGGESTED_LABEL={cleaned_label}\n")
# -----------------------------------------------------------------------------------------
except Exception as e:
print(f"Error calling Gemini API: {e}")
# Ensure fallback also writes to GITHUB_OUTPUT
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write("SUGGESTED_LABEL=no label\n")
EOF
- name: Add label to issue
# Only attempt to add label if a valid one was suggested AND an issue number is available
if: steps.gemini_call.outputs.SUGGESTED_LABEL != 'no label' && steps.get_issue_context.outputs.ISSUE_NUMBER != ''
uses: actions/github-script@v6
with:
script: |
// Access variables directly from process.env, as they are passed in the 'env' block
const label = process.env.SUGGESTED_LABEL;
const issue_number = process.env.ISSUE_NUMBER;
// These debug lines should now show the correct 'feature' label
console.log(`DEBUG: Value of label (from process.env.SUGGESTED_LABEL): "${label}"`);
console.log(`DEBUG: Value of issue_number (from process.env.ISSUE_NUMBER): "${issue_number}"`);
console.log(`Attempting to add label "${label}" to issue #${issue_number}`);
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issue_number), // Ensure issue_number is an integer for the API call
labels: [label]
});
console.log(`Successfully added label "${label}" to issue #${issue_number}`);
} catch (error) {
console.error(`Failed to add label "${label}" to issue #${issue_number}: ${error.message}`);
}
# Pass variables as environment variables for the script to access
env:
SUGGESTED_LABEL: ${{ steps.gemini_call.outputs.SUGGESTED_LABEL }}
ISSUE_NUMBER: ${{ steps.get_issue_context.outputs.ISSUE_NUMBER }} # Pass the determined issue number