Skip to content
This repository has been archived by the owner on Jul 6, 2024. It is now read-only.

🛠️ 環境変数バリデーションの強化とログ出力の改善 #305

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
174 changes: 100 additions & 74 deletions .github/scripts/review_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -96,41 +95,51 @@ 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):
self.client = 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:
Expand All @@ -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}
この投稿を読み、以下の過去提案の中に重複する提案があるかを判断してください。
Expand All @@ -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()