👌

マネージド サービスの Redmine に対して Azure OpenAI Service を利用して自動返信する

2023/10/22に公開

TL;DR

  • マネージド サービスの Redmine (ex. Lychee Redmine) だとプラグインを入れづらい
  • API が提供されているので、Azure Functions などから定期的に実行して OpenAI と連携させる
  • ChatGPT をふんだんに使ってとりあえずサンプル実装を作ってみた

実装例

OpenAI が動作する条件は以下の感じ。

  • 直近 3 日以内に作成された課題のみを対象とする
  • 課題にコメントがついていないもののみを対象とする
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')  # Azure OpenAI ServiceのURL
AZURE_OPENAI_KEY = os.environ.get('AZURE_OPENAI_KEY')  # Azure OpenAI ServiceのAPIキー
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()

    # デバッグのための出力
    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()

    # デバッグのための出力
    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):
    """
    Redmineの課題にコメントを追加する関数
    """
    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', [])

        # コメントが一つもついていない課題のみを表示
        if not journals:
            print(f"ID: {issue_details['id']}, Subject: {issue_details['subject']}, Created on: {issue_details['created_on']}")
            print("No comments or interactions.")

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

            # デバッグ出力
            print("=== Azure OpenAI Response ===")
            print(openai_response)
            print("==============================")

            # OpenAIの回答をRedmineの課題にコメントとして追加
            add_comment_to_issue(issue_details['id'], openai_response)

            print("-" * 50)

参考

  • REST API

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

  • Rest Issues - Redmine

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

  • Redmine で課題が起票された際に Azure OpenAI Service を利用して自動返信する

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

Discussion