🐈
Redmine で課題が起票された際に Azure OpenAI Service を利用して自動返信する
TL;DR
- Redmine で課題起票したときに Azure OpenAI Service が返事返してくれたら便利そう
- Redmine には plugin の仕組みがあるので割と簡単に実装できそう
- ChatGPT にめっちゃお世話になりながらとりあえず動くものができた
plugin のひな形を作成する
ikko@vm-rdn01:/var/lib/redmine$ sudo -u www-data RAILS_ENV=production bundle exec rails generate redmine_plugin openai
create plugins/openai/app
create plugins/openai/app/controllers
create plugins/openai/app/helpers
create plugins/openai/app/models
create plugins/openai/app/views
create plugins/openai/db/migrate
create plugins/openai/lib/tasks
create plugins/openai/assets/images
create plugins/openai/assets/javascripts
create plugins/openai/assets/stylesheets
create plugins/openai/config/locales
create plugins/openai/test
create plugins/openai/test/fixtures
create plugins/openai/test/unit
create plugins/openai/test/functional
create plugins/openai/test/integration
create plugins/openai/test/system
create plugins/openai/README.rdoc
create plugins/openai/init.rb
create plugins/openai/config/routes.rb
create plugins/openai/config/locales/en.yml
create plugins/openai/test/test_helper.rb
plugin の概要を記述する
ファイル自体は rails generate redmine_plugin
で作成されているのですが、settings
から始まる行を追加して Web から設定が変更できるようにします。
ikko@vm-rdn01:/var/lib/redmine$ cat /var/lib/redmine/plugins/openai/init.rb
init.rb
Redmine::Plugin.register :openai do
name 'Openai plugin'
author 'Author name'
description 'This is a plugin for Redmine'
version '0.0.1'
url 'http://example.com/path/to/plugin'
author_url 'http://example.com/about'
settings default: {'api_url' => '', 'api_key' => ''}, partial: 'settings/openai_settings'
end
plugin の設定画面を作成する
該当のフォルダとファイルはないので新規作成します。
root 権限で作業する場合には owner に注意してください。
ikko@vm-rdn01:/var/lib/redmine$ cat /var/lib/redmine/plugins/openai/app/views/settings/_openai_settings.html.erb
_openai_settings.html.erb
<p>
<label for="your_plugin_name_api_url">ChatGPT API URL:</label>
<%= text_field_tag 'settings[api_url]', Setting.plugin_openai['api_url'], :size => 40 %>
</p>
<p>
<label for="your_plugin_name_api_key">ChatGPT API Key:</label>
<%= text_field_tag 'settings[api_key]', Setting.plugin_openai['api_key'], :size => 40 %>
</p>
<p>
<label for="your_plugin_name_system_prompt">System Prompt:</label><br>
<%= text_area_tag 'settings[system_prompt]', Setting.plugin_openai['system_prompt'], :rows => 5, :cols => 60 %>
</p>
課題が起票された際に Azure OpenAI Service の API を呼び出す hook を記述する
こちらも同じく、該当のフォルダとファイルはないので新規作成します。
root 権限で作業する場合には owner に注意してください。
ikko@vm-rdn01:/var/lib/redmine$ cat /var/lib/redmine/plugins/openai/lib/openai/hooks.rb
hooks.rb
module Openai
class Hooks < Redmine::Hook::ViewListener
def controller_issues_new_after_save(context = {})
issue = context[:issue]
# Call the ChatGPT API and retrieve the response
response = call_chatgpt_api(issue)
# Add the response as a comment to the Issue
Journal.create!(
journalized_id: issue.id,
journalized_type: 'Issue',
user_id: User.find_by(login: 'chatbot').id, # Use the user with login 'chatbot'
notes: response
)
end
private
def call_chatgpt_api(issue)
require 'net/http'
require 'json'
# Retrieve the settings
api_base_url = "https://#{Setting.plugin_openai['api_url']}"
api_path = "/openai/deployments/gpt-35-turbo/chat/completions?api-version=2023-07-01-preview"
api_key = Setting.plugin_openai['api_key']
system_prompt = Setting.plugin_openai['system_prompt']
# Create a conversational structure for the API request
messages = [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": "Issue: #{issue.subject}\nDescription: #{issue.description}"
}
]
# Set up the API request details
uri = URI(api_base_url + api_path)
request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json', 'api-key' => api_key)
request.body = {
"messages": messages,
"max_tokens": 800,
"temperature": 0.7,
"frequency_penalty": 0,
"presence_penalty": 0,
"top_p": 0.95,
"stop": nil
}.to_json
# Execute the API request
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
# Log the response
Rails.logger.info("ChatGPT API Response: #{response.body}")
# Parse the response and return the bot's message
result = JSON.parse(response.body)
if result['choices']
result['choices'].first['message']['content'].strip
else
Rails.logger.warn("Unexpected response format from ChatGPT API")
""
end
end
end
end
require_dependency
はいらない
ChatGPT を使いながら作成しており、その中で require_dependency 'openai/hooks'
を init.rb に書くべしと回答が来ていたのですが、Redmine 5.0 (というよりかは Zeitwerk) の仕様により、不要になっているようです。
GitHub - fxn/zeitwerk: Efficient and thread-safe code loader for Ruby とか Zeitwerkの壊し方 - Qiita などからそうだと判断しています。
参考
- GitHub - fxn/zeitwerk: Efficient and thread-safe code loader for Ruby
- Zeitwerkの壊し方 - Qiita
- マネージド サービスの Redmine に対して Azure OpenAI Service を利用して自動返信する
Discussion