iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
👌

Automating Replies to Managed Redmine with Azure OpenAI Service

に公開

TL;DR

  • It is difficult to install plugins in managed Redmine services (e.g., Lychee Redmine).
  • Since an API is provided, we can integrate it with OpenAI by running it periodically from services like Azure Functions.
  • I've created a sample implementation as a starting point, making extensive use of ChatGPT.

Implementation Example

The conditions for triggering OpenAI are as follows:

  • Target only issues created within the last 3 days.
  • Target only issues that do not have any comments.
import requests
from datetime import datetime, timedelta
import os
import json

REDMINE_URL = os.environ.get('REDMINE_URL')
API_KEY = os.environ.get('API_KEY')
PROJECT_ID = os.environ.get('PROJECT_ID')
AZURE_OPENAI_URL = os.environ.get('AZURE_OPENAI_URL')  # URL for Azure OpenAI Service
AZURE_OPENAI_KEY = os.environ.get('AZURE_OPENAI_KEY')  # API key for Azure OpenAI Service
AZURE_OPENAI_SYSTEM_PROMPT = os.environ.get('AZURE_OPENAI_SYSTEM_PROMPT')

def get_recent_issues():
    one_day_ago = (datetime.now() - timedelta(days=3)).strftime("%Y-%m-%d")
    endpoint = f"{REDMINE_URL}/issues.json"
    params = {
        "key": API_KEY,
        "created_on": f">={one_day_ago}T00:00:00Z",
        "project_id": PROJECT_ID
    }
    response = requests.get(endpoint, params=params)
    response.raise_for_status()

    # Output for debugging
    print("=== DEBUG: Recent Issues Response JSON ===")
    print(json.dumps(response.json(), indent=4))
    print("==========================================")

    issues = response.json().get('issues', [])
    return issues

def get_issue_details(issue_id):
    endpoint = f"{REDMINE_URL}/issues/{issue_id}.json"
    params = {
        "key": API_KEY,
        "include": "journals"
    }
    response = requests.get(endpoint, params=params)
    response.raise_for_status()

    # Output for debugging
    print(f"=== DEBUG: Details for Issue {issue_id} Response JSON ===")
    print(json.dumps(response.json(), indent=4))
    print("=======================================================")

    return response.json().get('issue', {})

def add_comment_to_issue(issue_id, comment):
    """
    Function to add a comment to a Redmine issue
    """
    endpoint = f"{REDMINE_URL}/issues/{issue_id}.json"
    headers = {
        "Content-Type": "application/json",
        "X-Redmine-API-Key": API_KEY
    }
    data = {
        "issue": {
            "notes": comment
        }
    }
    response = requests.put(endpoint, headers=headers, json=data)
    response.raise_for_status()

def ask_openai(question):
    headers = {
        "Content-Type": "application/json",
        "api-key": AZURE_OPENAI_KEY
    }
    data = {
        "messages": [{"role": "system", "content": AZURE_OPENAI_SYSTEM_PROMPT},
                     {"role": "user", "content": question}],
        "max_tokens": 800,
        "temperature": 0.7,
        "frequency_penalty": 0,
        "presence_penalty": 0,
        "top_p": 0.95,
        "stop": None
    }
    response = requests.post(f"https://{AZURE_OPENAI_URL}/openai/deployments/gpt-35-turbo/chat/completions?api-version=2023-07-01-preview", headers=headers, json=data)
    response.raise_for_status()
    return response.json().get("choices", [{}])[0].get("message", {}).get("content", "").strip()

if __name__ == "__main__":
    recent_issues = get_recent_issues()
    for issue in recent_issues:
        issue_details = get_issue_details(issue['id'])

        journals = issue_details.get('journals', [])

        # Display only issues that do not have any comments
        if not journals:
            print(f"ID: {issue_details['id']}, Subject: {issue_details['subject']}, Created on: {issue_details['created_on']}")
            print("No comments or interactions.")

            # Query Azure OpenAI Service
            question = f"Can you provide insights on this issue? Think in English and answer in Japanese - 'Subject: {issue_details['subject']}\nDescription: {issue_details.get('description', 'No description provided.')}'"
            openai_response = ask_openai(question)

            # Debug output
            print("=== Azure OpenAI Response ===")
            print(openai_response)
            print("==============================")

            # Add OpenAI's response as a comment to the Redmine issue
            add_comment_to_issue(issue_details['id'], openai_response)

            print("-" * 50)

References

  • REST API

https://redmine.jp/glossary/r/rest-api/

  • Rest Issues - Redmine

https://www.redmine.org/projects/redmine/wiki/Rest_Issues

  • Automatically replying using Azure OpenAI Service when an issue is created in Redmine

https://zenn.dev/skmkzyk/articles/openai-redmine-plugin

Discussion