以前の記事では、Redmineの情報をDifyを使って取り込む形で検討をしていましたが、Difyの繰り返し処理は育ったRedmineの全チケット例えば1万件を超えるようなものを処理するには荷が重すぎます。例えば、ループは100件までだったり、イテレーションもディクショナリ変数の上限1000件といった規模間でブラウザのタイムアウト以内に処理が返ってくるような応答で収める事を期待されているようです。
今回は、Redmineのチケットを要約してRAGとして登録する部分はDifyの外でバッチとして処理をし、DifyではそのRAGを使ったQAとして利用する方向で検討してみたいと思います。
Redmine REST APIによるチケット抽出・Ollama要約・Dify RAG登録のPython自動化ワークフロー:設計・実装・運用ガイド
はじめに
Redmineは多くの開発現場や業務プロジェクトで利用されているオープンソースのプロジェクト管理ツールであり、そのREST APIを活用することで、チケット(課題)情報の自動取得や外部システム連携が容易に実現できます。近年では、ローカルで動作する大規模言語モデル(LLM)であるOllamaや、RAG(Retrieval-Augmented Generation)型のAIアプリケーション開発基盤であるDifyといった新しいAI技術が登場し、これらを組み合わせることで、Redmineのチケット情報を自動で要約し、ナレッジベースとしてAIに活用する高度なワークフローが構築可能となっています。
今回は、RedmineのREST APIを用いてチケットの各項目(タイトル、説明、ステータス、担当者など)をPythonで抽出し、その内容をOllamaを使ったローカルLLMに要約させ、さらにその要約をDifyのRAGシステムに登録する一連のPythonコードのサンプルを、設計・実装・運用の観点から詳細に解説します。各ステップごとに、API認証、Ollamaとの連携方法、Difyへのデータ登録方法を含め、実行順に沿ったコメント付きコード例とともに、必要な外部ライブラリのインストール方法や設定ファイル例、運用上の注意点まで網羅的に解説します。
システム全体像とワークフロー設計
全体アーキテクチャ
本ワークフローは、以下の3つの主要ステップで構成されます。
- Redmine REST APIからのチケット情報取得
- OllamaローカルLLMによるチケット内容の要約
- Dify RAGシステムへの要約データ登録
この一連の流れをPythonスクリプトで自動化し、定期実行やバッチ処理にも対応できる設計とします。
システム構成図(テキストによる説明)
- Redmineサーバ
- Python実行環境(ローカルまたはサーバ)
- python-redmine(redminelib)またはrequests
- ollama(PythonライブラリまたはREST API)
- requests(Dify API連携用)
- Ollamaサーバ(ローカルLLM実行環境)
- Difyサーバ(クラウドまたはセルフホスト、RAGナレッジベース)
ポイント:
- Redmine、Ollama、Difyはいずれもローカルまたはクラウドで運用可能。セキュリティやパフォーマンス要件に応じて構成を選択します。
- 各APIの認証情報(APIキー等)は環境変数や設定ファイルで安全に管理します。
Redmine REST APIからのチケット情報取得
Redmine REST APIの概要と認証
RedmineのREST APIは、HTTP/HTTPS経由でチケットやプロジェクト、ユーザー情報などを操作できる強力なインターフェースです。APIの利用には、管理画面でREST APIを有効化し、各ユーザーごとにAPIアクセスキーを発行する必要があります。
API有効化手順
- Redmine管理者でログイン
- 「管理」→「設定」→「認証」タブを開く
- 「REST APIを有効にする」にチェックを入れて保存
APIキー取得手順
- Redmineにログインし、「個人設定」を開く
- 「APIアクセスキーを表示」をクリックし、表示されたキーをコピー
APIキーの管理は厳重に行い、環境変数やセキュアなストレージで保管してください。
PythonによるRedmine APIアクセス方法
Redmine REST APIへのアクセスには、主に以下の2つの方法があります。
- python-redmine(redminelib)ライブラリを利用
- requests等でREST APIを直接呼び出す
python-redmine(redminelib)によるアクセス
python-redmineは、RedmineのAPI機能を100%サポートし、ORMスタイルで直感的に操作できるPythonライブラリです。
インストール方法:
$ python3 -m venv ./venv
$ source ./venv/bin/activate
$ python -m pip install --upgrade pip
$ pip install python-redmine基本的な接続例:(このサンプル動かない…503エラーになるのでスキップ)
from redminelib import Redmine
REDMINE_URL = "https://your-redmine.com"
API_KEY = "your_api_key"
redmine = Redmine(REDMINE_URL, key=API_KEY) # 接続確認
try:
user = redmine.user.get('current')
print(f"ログインユーザー: {user.firstname} {user.lastname}")
except Exception as e:
print(f"接続エラー: {e}")requestsによる直接API呼び出し
REST APIの仕様に沿って、requestsで直接HTTPリクエストを送ることも可能です。APIキーはヘッダー(X-Redmine-API-Key)またはクエリパラメータで指定します。
例:
import requests
REDMINE_URL = "https://your-redmine.com"
API_KEY = "your_api_key"
headers = { "X-Redmine-API-Key": API_KEY, "Content-Type": "application/json" }
response = requests.get(f"{REDMINE_URL}/issues.json", headers=headers)
print(response.json())取得すべきチケット項目
Redmineのチケット(issue)には、以下のような主要項目があります。
| 項目名 | 属性名 | 説明 |
|---|---|---|
| ID | id | チケットID |
| 件名 | subject | タイトル |
| 説明 | description | 詳細説明 |
| ステータス | status | 現在の状態 |
| 担当者 | assigned_to | 担当ユーザー |
| 作成者 | author | 作成ユーザー |
| 優先度 | priority | 優先度 |
| カスタムフィールド | custom_fields | 任意の追加情報(配列) |
| プロジェクト | project | 所属プロジェクト |
| 作成日 | created_on | 作成日時 |
| 更新日 | updated_on | 最終更新日時 |
| コメント | journals.notes | コメント文 |
カスタムフィールドはRedmineの管理画面で自由に追加でき、APIレスポンスではcustom_fields配列として取得できます。
チケット情報取得のサンプルコード
プロジェクト内の全チケットを取得(python-redmine)
from redminelib import Redmine
REDMINE_URL = "https://your-redmine.com"
API_KEY = "your_api_key"
PROJECT_ID = "my_project"
redmine = Redmine(REDMINE_URL, key=API_KEY)
try:
issues = redmine.issue.filter(project_id=PROJECT_ID)
for issue in issues:
print(f"[{issue.id}] {issue.subject} - {issue.status.name}")
except Exception as e:
print(f"エラー: {e}")取得したチケットの項目を抽出
for issue in issues:
print(f"ID: {issue.id}")
print(f"件名: {issue.subject}")
print(f"説明: {issue.description}")
print(f"ステータス: {issue.status.name}")
print(f"担当者: {getattr(issue, 'assigned_to', None)}")
print(f"カスタムフィールド: {issue.custom_fields}")
print("------")備考: 担当者やカスタムフィールドは存在しない場合もあるため、getattrやhasattrで存在確認を推奨します。
REST API直接呼び出し例(requests)
import requests
REDMINE_URL = "https://your-redmine.com"
API_KEY = "your_api_key"
PROJECT_ID = "my_project"
headers = {
"X-Redmine-API-Key": API_KEY,
"Content-Type": "application/json"
}
params = {
"project_id": PROJECT_ID,
"limit": 100 # ページネーション対応
}
response = requests.get(f"{REDMINE_URL}/issues.json", headers=headers, params=params)
issues = response.json().get("issues", [])
for issue in issues:
print(issue["id"], issue["subject"])ページネーション:Redmine APIは1回のリクエストで取得できる件数に上限(デフォルト25件、最大100件)があるため、limitとoffsetパラメータで複数回に分けて取得します。
OllamaローカルLLMによるチケット要約
Ollamaの概要と導入
Ollamaは、Llama 3、Gemma、Qwen3、Phi-3などのオープンソースLLMをローカル環境で簡単に実行できるツールです。CLIとREST API、Pythonライブラリを備え、プライバシー保護やコスト削減、オフライン利用などのメリットがあります。Windowsへの導入方法はこちらをご参照下さい→こちら
Ollamaの主な特徴
| 特徴 | 説明 |
|---|---|
| ローカル実行 | データが外部に出ない為プライバシー保護 |
| コスト削減 | クラウドAPI利用料不要 |
| モデル管理容易 | ollama pullでモデルを簡単にダウンロード |
| REST API対応 | HTTP経由でモデル利用可能 |
| クロスプラットフォーム | macOS, Linux, Windows(WSL2)対応 |
| GPU/CPU対応 | GPUがあれば自動利用、なければCPU動作可 |
インストール方法(Linux/macOS/WSL2):
$ curl -fsSL https://ollama.com/install.sh | shモデルのダウンロード例:
$ ollama pull gemma3:4b
$ ollama pull qwen3:8bPythonからOllamaを利用する方法
Ollamaは公式のPythonライブラリ(ollama)を提供しており、REST API互換のOpenAI APIやHTTPリクエストでも利用可能です。
インストール:
$ pip install ollama基本的な要約実行例:
import ollama
response = ollama.chat(
model="gemma3:4b",
messages=[
{
"role": "user",
"content": "以下のRedmineチケット内容を要約してください。\n\nタイトル: ...\n説明: ...",
}
],
)
print(response["message"]["content"]) 要約プロンプト設計と出力制御
要約プロンプトの設計
要約の品質や速度はプロンプト設計に大きく依存します。出力形式や文字数制限、箇条書き指定などを明示することで、応答速度と品質が向上します。
プロンプト例:
以下のRedmineチケットの内容を、3つの箇条書き(各200文字以内)で要約してください。
- タイトル: {subject}
- 説明: {description}
- ステータス: {status}
- 担当者: {assigned_to}
モデル選定と日本語対応
- Gemma3:Google製、英語・日本語対応、要約・埋め込み両対応
- Qwen3:Alibaba製、日本語・中国語・英語に強い、軽量で高速
- Llama3:Meta製、英語中心だが日本語も一定対応
日本語要約にはGemma3やQwen3が推奨されます。
JSON出力やストリーミング制御
Ollamaのchatやgenerate関数では、stream=Trueでストリーミング応答、formatパラメータでJSON出力も制御可能です。
JSON形式で要約を取得する例:
import ollama
prompt = """
以下のRedmineチケット内容を要約し、JSON形式で出力してください。
{
"summary": "要約文",
"keywords": ["キーワード1", "キーワード2"]
}
タイトル: {subject}
説明: {description}
"""
response = ollama.generate(
model="gemma3:4b",
prompt=prompt,
options={"format": "json"}
)
print(response["response"])エラーハンドリングとパフォーマンス最適化
- タイムアウトやリトライ:API呼び出し時はタイムアウト・リトライ処理を実装し、Ollamaサーバの負荷やネットワーク障害に備えます。
- モデルサイズ選定:業務要件に応じて4B/8B/12Bなど適切なモデルサイズを選択し、メモリや応答速度を最適化します。
- プロンプト最適化:出力形式や文字数制限を明示することで推論速度が向上します。
Dify RAGシステムへの要約データ登録
Difyの概要とRAG構築
Difyは、ノーコード/ローコードでAIアプリやRAG(検索拡張生成)システムを構築できるオープンソースプラットフォームです。ナレッジベース(データセット)にドキュメントを登録し、外部からAPI経由でAIアプリやRAGワークフローを呼び出すことができます。
Difyの主な特徴
| 特徴 | 説明 |
|---|---|
| RAG対応 | 外部ナレッジベースをAI応答に組み込める |
| RESTful API | チャット、ワークフロー、ナレッジ管理APIを提供 |
| セルフホスト可 | Dockerでローカル/オンプレ運用が可能 |
| モデルプロバイダー | OpenAI、Ollama、Xinference等と連携可能 |
| メタデータ管理 | ドキュメントごとにメタデータやインデックスを設定可能 |
Dify API認証とAPIキー取得
DifyのAPIはBearerトークン認証を採用しており、アプリ単位でAPIキー(app-xxx)を発行します。アカウント全体のキー(sk-xxx)や外部LLMのAPIキー(OpenAI等)と混同しないよう注意が必要です。
APIキー取得手順:
- Difyダッシュボードでアプリを作成
- アプリ画面の「APIアクセス」から「APIキーを作成」
- 生成された
app-xxxキーを安全に保管
APIキーは環境変数や.envファイルで管理し、コードに直書きしないことが推奨されます。
ナレッジベース(データセット)へのドキュメント登録API
DifyのナレッジベースAPIでは、テキストまたはファイルでドキュメントを追加できます。Redmineチケット要約のようなテキストデータは、/datasets/{dataset_id}/document/create-by-text エンドポイントで登録します。
APIエンドポイント例:
POST https://api.dify.ai/v1/datasets/{dataset_id}/document/create-by-text
リクエスト例(JSON):
{
"name": "Redmineチケット要約_12345",
"text": "ここに要約テキストを記述",
"indexing_technique": "high_quality",
"process_rule": { "mode": "automatic" }
}
ヘッダー:
Authorization: Bearer app-xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
PythonによるDify API呼び出しサンプル
requestsライブラリのインストール:
$ pip install requestsサンプルコード:
import os
import requests
DIFY_API_KEY = os.getenv("DIFY_API_KEY")
DIFY_BASE_URL = "https://api.dify.ai/v1"
DATASET_ID = "your_dataset_id"
headers = { "Authorization": f"Bearer {DIFY_API_KEY}", "Content-Type": "application/json" }
def register_summary_to_dify(summary_text, ticket_id):
data = {
"name": f"Redmineチケット要約_{ticket_id}",
"text": summary_text,
"indexing_technique": "high_quality",
"process_rule": {"mode": "automatic"}
}
url = f"{DIFY_BASE_URL}/datasets/{DATASET_ID}/document/create-by-text"
response = requests.post(url, headers=headers, json=data, timeout=30)
if response.ok:
print("Dify登録成功:", response.status_code, response.json())
else:
print("Dify登録失敗:", response.status_code, response.text)備考:
DATASET_IDはDifyのナレッジベースごとに発行されるUUIDです。- レスポンスで
indexing_statusがwaitingやindexingの場合、インデックス化が完了するまで待つ必要があります。
メタデータ・インデックス設定
Difyでは、ドキュメントごとに**メタデータやインデックス手法(indexing_technique)**を指定できます。process_ruleでチャンク分割や前処理ルールも細かく設定可能です。
例:
"indexing_technique": "high_quality",
"process_rule": {
"mode": "custom",
"rules": {
"segmentation": {
"separator": "###",
"max_tokens": 500
}
}
}
メタデータ追加APIも利用可能(例:タイトルやチケットIDをメタデータとして付与)。
Pythonによる一連の自動化ワークフロー実装例
必要ライブラリと環境構築
requirements.txt例:
python-redmine>=2.5.0
ollama>=0.6.0
requests>=2.31.0
python-dotenv>=1.0.0
.env.example例:
REDMINE_URL=https://your-redmine.com
REDMINE_API_KEY=your_redmine_api_key
REDMINE_PROJECT_ID=my_project
OLLAMA_MODEL=gemma3:4b
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxxxxx
DIFY_BASE_URL=https://api.dify.ai/v1
DIFY_DATASET_ID=your_dataset_id
Ollama側の設定
Ollamaサーバ側でリモートからのアクセスを許可する必要があります。
listen: 0.0.0.0:11434
インストールコマンド:
$ pip install -r requirements.txtサンプルPythonコード(実行順・コメント付き)
設定ファイルの読み込み
import os
from dotenv import load_dotenv
load_dotenv()
REDMINE_URL = os.getenv("REDMINE_URL")
REDMINE_API_KEY = os.getenv("REDMINE_API_KEY")
REDMINE_PROJECT_ID = os.getenv("REDMINE_PROJECT_ID")
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL")
DIFY_API_KEY = os.getenv("DIFY_API_KEY")
DIFY_BASE_URL = os.getenv("DIFY_BASE_URL")
DIFY_DATASET_ID = os.getenv("DIFY_DATASET_ID")Redmineからチケット情報を取得
from redminelib import Redmine
def fetch_redmine_issues():
redmine = Redmine(REDMINE_URL, key=REDMINE_API_KEY)
issues = redmine.issue.filter(project_id=REDMINE_PROJECT_ID, status_id='*', limit=100)
tickets = []
for issue in issues:
# journals を取得するために issue を再取得
issue_full = redmine.issue.get(issue.id, include=['journals'])
# コメント一覧を抽出
comments = []
for j in issue_full.journals:
if j.notes: # コメントがある場合のみ
comments.append({
"id": j.id,
"user": j.user.name if hasattr(j, "user") else None,
"created_on": j.created_on,
"note": j.notes
})
ticket = {
"id": issue.id,
"subject": issue.subject,
"description": getattr(issue, "description", ""),
"status": issue.status.name,
"assigned_to": getattr(issue, "assigned_to", None),
"custom_fields": {cf.name: cf.value for cf in getattr(issue, "custom_fields", [])},
"comments": comments # ← journals.notes を追加
}
tickets.append(ticket)
return ticketsOllamaでチケット内容を要約
import ollama
client = ollama.Client(host="http://192.168.1.110:11434")
def summarize_ticket(ticket):
prompt = (
"以下のRedmineチケット内容を、3つの箇条書き(各200文字以内)で要約してください。\n"
f"タイトル: {ticket['subject']}\n"
f"説明: {ticket['description']}\n"
f"ステータス: {ticket['status']}\n"
f"担当者: {ticket['assigned_to']}\n"
)
response = client.chat(
model=OLLAMA_MODEL,
messages=[{"role": "user", "content": prompt}],
options={"num_ctx": 1024, "temperature": 0.2}
)
return response["message"]["content"]※Ollamaへは、デフォルトでは localhost:11434 にアクセスされるのですが、ollama.Clientの設定で指定することが出来ます。このほか OLLAMA_HOSTという環境変数に上記のURLを指定する事もできます。その場合は、5.2.1で、os.getenv("OLLAMA_HOST") の値を設定しておいてください。
Difyへ要約テキストを登録
import requests
def register_summary_to_dify(summary_text, ticket_id):
headers = {
"Authorization": f"Bearer {DIFY_API_KEY}",
"Content-Type": "application/json"
}
data = {
"name": f"Redmineチケット要約_{ticket_id}",
"text": summary_text,
"indexing_technique": "high_quality",
"process_rule": {"mode": "automatic"}
}
url = f"{DIFY_BASE_URL}/datasets/{DIFY_DATASET_ID}/document/create-by-text"
response = requests.post(url, headers=headers, json=data, timeout=30)
if response.ok:
print("Dify登録成功:", response.status_code, response.json())
else:
print("Dify登録失敗:", response.status_code, response.text)メイン処理の流れ
def main():
tickets = fetch_redmine_issues()
for ticket in tickets:
print(f"チケットID: {ticket['id']} の要約を生成中...")
summary = summarize_ticket(ticket)
print("要約結果:\n", summary)
register_summary_to_dify(summary, ticket["id"])
if __name__ == "__main__":
main()備考:
- チケット数が多い場合は、ページネーションやsleepによるレート制御を実装してください。
- エラー発生時はリトライやログ出力で運用保守性を高めます。
運用上の注意点とベストプラクティス
レート制限とパフォーマンス
- Redmine API:1回のリクエストで取得できる件数に上限あり(デフォルト25件)。大量データ取得時は
limitとoffsetで分割取得し、サーバ負荷に注意。 - Ollama:ローカルLLMは同時リクエスト数やメモリ消費に注意。モデルサイズに応じてマシンスペックを調整し、必要に応じて
OLLAMA_NUM_PARALLEL等の環境変数で制御。 - Dify API:無料プランやクラウド版ではAPIレート制限あり。429エラー時はリトライや待機処理を実装。
プライバシー・セキュリティ
- APIキー管理:APIキーは環境変数や.envファイルで管理し、コードやリポジトリに直書きしない。
- HTTPS通信:APIアクセス時は必ずHTTPSを利用し、通信経路の盗聴リスクを低減。
- アクセス権限:RedmineやDifyのAPIキーは必要最小限の権限で発行し、不要になったら即時失効。
データサイズ・RAG更新頻度
- 要約長の制御:Ollamaのプロンプトで出力長や形式を明示し、Difyのインデックス化負荷を軽減。
- RAGナレッジの更新:定期的なバッチ実行やcronによる自動化で、ナレッジベースの鮮度を維持。
エラーハンドリングとリトライ
- requestsのリトライ設定例:
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
session = requests.Session()
retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
session.mount("https://", HTTPAdapter(max_retries=retries))- OllamaやDify APIのタイムアウト・例外処理も適切に実装し、障害時の自動復旧を図ります。
Docker・Ollama・Difyの起動方法
- Ollama:
ollama serveでサーバ起動。外部からアクセスする場合はOLLAMA_HOST=0.0.0.0を設定し、systemdサービスファイルに環境変数を追加。 - Dify:Docker Composeで
docker compose up -d。初回は.envを編集し、管理画面でセットアップ。 - OllamaとDifyの連携:Difyの「モデルプロバイダー」設定でOllamaのBase URL(例:
http://host.docker.internal:11434)を指定し、モデル名を登録。
まとめと今後の展望
今回は、Redmine REST APIによるチケット情報の自動抽出、OllamaローカルLLMによる要約、Dify RAGシステムへのナレッジ登録という一連のPython自動化ワークフローを、設計・実装・運用の観点から詳細に解説しました。
- Redmine APIはpython-redmineやrequestsで柔軟に操作可能。APIキー認証やページネーション、カスタムフィールド対応も容易。
- OllamaはローカルLLMとして高性能な要約や埋め込み生成が可能。プロンプト設計やモデル選定で品質と速度を最適化。
- DifyはRAG型AIアプリのナレッジベースとして、API経由でのドキュメント登録やメタデータ管理が強力。APIキーやレート制限、インデックス設定に注意。
今後の展望:
- Redmineのチケット更新やコメント追加も自動化し、双方向連携を実現
- Ollamaの埋め込み生成機能とDifyのカスタムインデックスを組み合わせた高度なRAG構築
- DifyのワークフローAPIやコード実行ノードを活用した複雑な自動化パイプラインの設計
- セルフホスト環境でのセキュリティ強化や大規模運用へのスケーリング
付録:主要な設定ファイル例
requirements.txt
python-redmine>=2.5.0
ollama>=0.6.0
requests>=2.31.0
python-dotenv>=1.0.0
.env.example
REDMINE_URL=https://your-redmine.com
REDMINE_API_KEY=your_redmine_api_key
REDMINE_PROJECT_ID=my_project
OLLAMA_MODEL=gemma3:4b
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxxxxx
DIFY_BASE_URL=https://api.dify.ai/v1
DIFY_DATASET_ID=your_dataset_id
