From 0743322519b3e8b3be10bf78e1f7cf194f75bae2 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 4 Jul 2024 22:27:25 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=94=A7=20(#188,=20#304)[chore]=20?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA=E3=81=AE=E4=BE=9D?= =?UTF-8?q?=E5=AD=98=E9=96=A2=E4=BF=82=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - python-dotenv, loguru, pydantic-settingsを追加し、環境設定とログ管理を強化 --- .github/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/requirements.txt b/.github/requirements.txt index f10c9bb..ffd5718 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -2,3 +2,6 @@ openai==1.35.3 PyGithub==2.3.0 qdrant-client==1.9.2 regex==2024.5.15 +python-dotenv==1.0.1 +loguru==0.7.2 +pydantic-settings==2.2.1 \ No newline at end of file From ee9e8390e7579cc135d372c0201502b91e7a2b19 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 4 Jul 2024 22:28:49 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=94=84=20(#188,=20#304)[refactor]=20?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=A8=E3=83=AD=E3=82=AE=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dotenvの使用を廃止し、pydantic_settingsで環境設定のモデルを組み立て - loguruを導入して詳細なログ出力を実現 - GithubHandler, ContentModerator, QdrantHandlerのクラスを再設計し、設定情報の取り扱いを改善 - コンテンツモデレーターでの画像の検証プロセスを改善し、OpenAI APIを利用する部分でエラーハンドリングを追加 --- .github/scripts/review_issue.py | 176 ++++++++++++++++++-------------- 1 file changed, 101 insertions(+), 75 deletions(-) diff --git a/.github/scripts/review_issue.py b/.github/scripts/review_issue.py index 6c0b2b3..10f4420 100644 --- a/.github/scripts/review_issue.py +++ b/.github/scripts/review_issue.py @@ -7,84 +7,83 @@ from qdrant_client import QdrantClient from qdrant_client.models import PointStruct import openai - -# GitHub Actions環境で実行されていない場合のみ.envファイルを読み込む -if not os.getenv('GITHUB_ACTIONS'): - from dotenv import load_dotenv - load_dotenv() - -# 定数 -EMBEDDING_MODEL = "text-embedding-3-small" -COLLECTION_NAME = "issue_collection" -GPT_MODEL = "gpt-4o" -MAX_RESULTS = 3 +from pydantic_settings import BaseSettings +from loguru import logger + +class Settings(BaseSettings): + github_token: str + qd_api_key: str + qd_url: str + github_repository: str + github_event_issue_number: int + embedding_model: str = "text-embedding-3-small" + collection_name: str = "issue_collection" + gpt_model: str = "gpt-4" + max_results: int = 3 + openai_api_key: str # 新しく追加 + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + extra = 'ignore' # 定義されていない環境変数を無視 class Config: def __init__(self): - print("設定の初期化を開始します...") - self.github_token = os.getenv("GITHUB_TOKEN") - if self.github_token is None: - print("GITHUB_TOKENが見つかりません ...") - else: - print("GITHUB_TOKENからトークンを正常に取得しました。") - - self.qd_api_key = os.getenv("QD_API_KEY") - print("QD_API_KEYの状態:", "取得済み" if self.qd_api_key else "見つかりません") - - self.qd_url = os.getenv("QD_URL") - print("QD_URLの状態:", "取得済み" if self.qd_url else "見つかりません") - - self.github_repo = os.getenv("GITHUB_REPOSITORY") - print("GITHUB_REPOSITORYの状態:", "取得済み" if self.github_repo else "見つかりません") - - self.issue_number = os.getenv("GITHUB_EVENT_ISSUE_NUMBER") - if self.issue_number: - self.issue_number = int(self.issue_number) - print(f"GITHUB_EVENT_ISSUE_NUMBER: {self.issue_number}") - else: - print("GITHUB_EVENT_ISSUE_NUMBERが見つかりません") - print("設定の初期化が完了しました。") - + logger.info("設定の初期化を開始します...") + try: + self.settings = Settings() + logger.success("設定の初期化が成功しました。") + except Exception as e: + logger.error(f"設定の初期化に失敗しました: {e}") + raise + class GithubHandler: def __init__(self, config: Config): - self.github = Github(config.github_token) - self.repo = self.github.get_repo(config.github_repo) - self.issue = self.repo.get_issue(config.issue_number) + logger.info("GitHubハンドラの初期化中...") + self.github = Github(config.settings.github_token) + self.repo = self.github.get_repo(config.settings.github_repository) + self.issue = self.repo.get_issue(config.settings.github_event_issue_number) + logger.success("GitHubハンドラの初期化が成功しました。") def create_labels(self): - """ラベルを作成する(既に存在する場合は無視)""" + logger.info("ラベルの作成中...") try: self.repo.create_label(name="toxic", color="ff0000") self.repo.create_label(name="duplicated", color="708090") - except: - pass + logger.success("ラベルの作成が成功しました。") + except Exception as e: + logger.warning(f"ラベルの作成に失敗しました: {e}") def add_label(self, label: str): - """Issueにラベルを追加する""" + logger.info(f"'{label}'ラベルをissueに追加中...") self.issue.add_to_labels(label) + logger.success(f"'{label}'ラベルの追加が成功しました。") def close_issue(self): - """Issueをクローズする""" + logger.info("issueをクローズ中...") self.issue.edit(state="closed") + logger.success("issueのクローズが成功しました。") def add_comment(self, comment: str): - """Issueにコメントを追加する""" + logger.info("issueにコメントを追加中...") self.issue.create_comment(comment) + logger.success("コメントの追加が成功しました。") class ContentModerator: def __init__(self, openai_client: openai.Client): self.openai_client = openai_client def validate_image(self, text: str) -> bool: - """画像の内容が不適切かどうかを判断する""" + logger.info("画像コンテンツの検証中...") image_url = self._extract_image_url(text) if not image_url: + logger.info("テキスト内に画像URLが見つかりませんでした。") return False prompt = "この画像が暴力的、もしくは性的な画像の場合trueと返してください。" try: response = self.openai_client.chat.completions.create( - model=GPT_MODEL, + model=Settings().gpt_model, messages=[ { "role": "user", @@ -96,20 +95,27 @@ def validate_image(self, text: str) -> bool: ], max_tokens=1200, ) - return "true" in response.choices[0].message.content.lower() - except: + result = "true" in response.choices[0].message.content.lower() + logger.info(f"画像検証結果: {'不適切' if result else '適切'}") + return result + except Exception as e: + logger.error(f"画像検証中にエラーが発生しました: {e}") return True def judge_violation(self, text: str) -> bool: - """テキストと画像の内容が不適切かどうかを判断する""" + logger.info("コンテンツ違反の判定中...") response = self.openai_client.moderations.create(input=text) - return response.results[0].flagged or self.validate_image(text) + result = response.results[0].flagged or self.validate_image(text) + logger.info(f"コンテンツ違反判定結果: {'違反あり' if result else '違反なし'}") + return result @staticmethod def _extract_image_url(text: str) -> str: - """テキストから画像URLを抽出する""" + logger.info("テキストから画像URLを抽出中...") match = re.search(r"!\[[^\s]+\]\((https://[^\s]+)\)", text) - return match[1] if match and len(match) > 1 else "" + url = match[1] if match and len(match) > 1 else "" + logger.info(f"抽出された画像URL: {url}") + return url class QdrantHandler: def __init__(self, client: QdrantClient, openai_client: openai.Client): @@ -117,20 +123,23 @@ def __init__(self, client: QdrantClient, openai_client: openai.Client): self.openai_client = openai_client def add_issue(self, text: str, issue_number: int): - """新しい問題をQdrantに追加する""" + logger.info(f"issue #{issue_number}をQdrantに追加中...") embedding = self._create_embedding(text) point = PointStruct(id=issue_number, vector=embedding, payload={"text": text}) - self.client.upsert(COLLECTION_NAME, [point]) + self.client.upsert(Settings().collection_name, [point]) + logger.success(f"issue #{issue_number}のQdrantへの追加が成功しました。") def search_similar_issues(self, text: str) -> List[Dict[str, Any]]: - """類似の問題を検索する""" + logger.info("類似issueの検索中...") embedding = self._create_embedding(text) - results = self.client.search(collection_name=COLLECTION_NAME, query_vector=embedding) - return results[:MAX_RESULTS] + results = self.client.search(collection_name=Settings().collection_name, query_vector=embedding) + logger.info(f"{len(results)}件の類似issueが見つかりました。") + return results[:Settings().max_results] def _create_embedding(self, text: str) -> List[float]: - """テキストのembeddingを作成する""" - result = self.openai_client.embeddings.create(input=[text], model=EMBEDDING_MODEL) + logger.info("テキストのembedding作成中...") + result = self.openai_client.embeddings.create(input=[text], model=Settings().embedding_model) + logger.success("embeddingの作成が成功しました。") return result.data[0].embedding class IssueProcessor: @@ -141,51 +150,59 @@ def __init__(self, github_handler: GithubHandler, content_moderator: ContentMode self.openai_client = openai_client def process_issue(self, issue_content: str): - """Issueを処理する""" + logger.info("issueの処理を開始します...") if self.content_moderator.judge_violation(issue_content): + logger.warning("issueの内容がガイドラインに違反しています。") self._handle_violation() return similar_issues = self.qdrant_handler.search_similar_issues(issue_content) if not similar_issues: + logger.info("類似issueが見つかりませんでした。新しいissueをQdrantに追加します。") self.qdrant_handler.add_issue(issue_content, self.github_handler.issue.number) return duplicate_id = self._check_duplication(issue_content, similar_issues) if duplicate_id: + logger.info(f"重複issueが見つかりました: #{duplicate_id}") self._handle_duplication(duplicate_id) else: + logger.info("重複は見つかりませんでした。新しいissueをQdrantに追加します。") self.qdrant_handler.add_issue(issue_content, self.github_handler.issue.number) def _handle_violation(self): - """違反を処理する""" + logger.info("コンテンツ違反の処理を開始します...") self.github_handler.add_label("toxic") self.github_handler.add_comment("不適切な投稿です。アカウントBANの危険性があります。") self.github_handler.close_issue() + logger.success("違反の処理が完了しました。") def _check_duplication(self, issue_content: str, similar_issues: List[Dict[str, Any]]) -> int: - """重複をチェックする""" + logger.info("重複チェックを開始します...") prompt = self._create_duplication_check_prompt(issue_content, similar_issues) completion = self.openai_client.chat.completions.create( - model=GPT_MODEL, + model=Settings().gpt_model, max_tokens=1024, messages=[{"role": "system", "content": prompt}] ) review = completion.choices[0].message.content if ":" in review: review = review.split(":")[-1] - return int(review) if review.isdecimal() and review != "0" else 0 + result = int(review) if review.isdecimal() and review != "0" else 0 + logger.info(f"重複チェック結果: {result}") + return result def _handle_duplication(self, duplicate_id: int): - """重複を処理する""" + logger.info(f"issue #{duplicate_id}との重複を処理しています...") self.github_handler.add_label("duplicated") self.github_handler.add_comment(f"#{duplicate_id} と重複しているかもしれません") + logger.success("重複の処理が完了しました。") @staticmethod def _create_duplication_check_prompt(issue_content: str, similar_issues: List[Dict[str, Any]]) -> str: - """重複チェック用のプロンプトを作成する""" + logger.info("重複チェック用のプロンプトを作成しています...") similar_issues_text = "\n".join([f'id:{issue.id}\n内容:{issue.payload["text"]}' for issue in similar_issues]) - return f""" + prompt = f""" 以下は市民から寄せられた政策提案です。 {issue_content} この投稿を読み、以下の過去提案の中に重複する提案があるかを判断してください。 @@ -196,26 +213,35 @@ def _create_duplication_check_prompt(issue_content: str, similar_issues: List[Di [出力形式] id:0 """ + logger.success("重複チェック用のプロンプトの作成が完了しました。") + return prompt def setup(): - """セットアップを行い、必要なオブジェクトを返す""" + logger.info("アプリケーションのセットアップを開始します...") config = Config() github_handler = GithubHandler(config) github_handler.create_labels() - openai_client = openai.Client() + openai_client = openai.Client(api_key=config.settings.openai_api_key) # OpenAI クライアントの初期化を修正 content_moderator = ContentModerator(openai_client) - qdrant_client = QdrantClient(url=config.qd_url, api_key=config.qd_api_key) + qdrant_client = QdrantClient(url=config.settings.qd_url, api_key=config.settings.qd_api_key) qdrant_handler = QdrantHandler(qdrant_client, openai_client) + logger.success("アプリケーションのセットアップが完了しました。") return github_handler, content_moderator, qdrant_handler, openai_client def main(): - github_handler, content_moderator, qdrant_handler, openai_client = setup() - issue_processor = IssueProcessor(github_handler, content_moderator, qdrant_handler, openai_client) - issue_content = f"{github_handler.issue.title}\n{github_handler.issue.body}" - issue_processor.process_issue(issue_content) + logger.info("メインプロセスを開始します...") + try: + github_handler, content_moderator, qdrant_handler, openai_client = setup() + issue_processor = IssueProcessor(github_handler, content_moderator, qdrant_handler, openai_client) + issue_content = f"{github_handler.issue.title}\n{github_handler.issue.body}" + issue_processor.process_issue(issue_content) + logger.success("メインプロセスが正常に完了しました。") + except Exception as e: + logger.error(f"メインプロセス中にエラーが発生しました: {e}") + raise if __name__ == "__main__": - main() + main() \ No newline at end of file From 85f76b115e1babe355648427dc102550da98d80a Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 4 Jul 2024 22:31:49 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=84=20(#188,=20#304)[chore]=20?= =?UTF-8?q?=E4=BE=9D=E5=AD=98=E9=96=A2=E4=BF=82=E3=81=AE=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=96=E3=83=A9=E3=83=AA=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pydantic-settingsのバージョンを更新し、python-dotenvを追加することで環境設定の管理を改善 --- .github/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index ffd5718..4470c03 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -4,4 +4,5 @@ qdrant-client==1.9.2 regex==2024.5.15 python-dotenv==1.0.1 loguru==0.7.2 -pydantic-settings==2.2.1 \ No newline at end of file +pydantic-settings==2.2.1 +python-dotenv==1.0.1 From 8fdfcdd5ad71e40be423bbf14d30c84298357b15 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 4 Jul 2024 22:45:53 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=A7(#188,=20#304)[refactor]=20.env?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=8B=E3=82=89=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=82=80=E3=82=AF?= =?UTF-8?q?=E3=83=A9=E3=82=B9=E3=81=AE=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `.env` ファイルから設定を読み込む `Settings` クラス内の `openai_api_key` 属性に関するコメントをクリーンアップ。 - ファイル末尾に新しい行を追加してフォーマットを改善。 --- .github/scripts/review_issue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/review_issue.py b/.github/scripts/review_issue.py index 10f4420..8e3ce36 100644 --- a/.github/scripts/review_issue.py +++ b/.github/scripts/review_issue.py @@ -20,7 +20,7 @@ class Settings(BaseSettings): collection_name: str = "issue_collection" gpt_model: str = "gpt-4" max_results: int = 3 - openai_api_key: str # 新しく追加 + openai_api_key: str class Config: env_file = ".env" @@ -244,4 +244,4 @@ def main(): raise if __name__ == "__main__": - main() \ No newline at end of file + main() From 8928ac66e8dc4e45d5331f1a4eec4f21f4925921 Mon Sep 17 00:00:00 2001 From: Maki Date: Sat, 6 Jul 2024 13:13:08 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=A7=B9=20[chore]=20=E5=86=97=E9=95=B7?= =?UTF-8?q?=E3=81=AA=E3=83=91=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - python-dotenv の重複エントリーを削除し、依存関係リストをクリーンアップ --- .github/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 4470c03..8648eca 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -5,4 +5,3 @@ regex==2024.5.15 python-dotenv==1.0.1 loguru==0.7.2 pydantic-settings==2.2.1 -python-dotenv==1.0.1