From e0896fa53b85a226126e92f47c92bfb6a5ffeaa9 Mon Sep 17 00:00:00 2001 From: duanxufu Date: Sun, 3 Aug 2025 11:40:11 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fixed:=20=E8=B0=83=E7=94=A8=20LLM=20?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E6=97=B6=E6=B2=A1=E6=9C=89=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E7=9A=84=E5=A0=86=E6=A0=88=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/llm/client/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/biz/llm/client/base.py b/biz/llm/client/base.py index 69ea86910..bcfc2acf4 100644 --- a/biz/llm/client/base.py +++ b/biz/llm/client/base.py @@ -13,8 +13,9 @@ def ping(self) -> bool: try: result = self.completions(messages=[{"role": "user", "content": '请仅返回 "ok"。'}]) return result and result.strip() == "ok" - except Exception: - logger.error("尝试连接LLM失败, {e}") + + except Exception as e: + logger.error(f'尝试连接LLM失败, {e}') return False @abstractmethod From 272c7945005c3658304eecd25ae152f38009608d Mon Sep 17 00:00:00 2001 From: duanxufu Date: Sun, 3 Aug 2025 11:44:46 +0800 Subject: [PATCH 2/5] =?UTF-8?q?pref:=20=E5=B0=86=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E7=9A=84=E8=B7=AF=E5=BE=84=E5=92=8C?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E9=A3=8E=E6=A0=BC=E4=BD=9C=E4=B8=BA=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/utils/code_reviewer.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/biz/utils/code_reviewer.py b/biz/utils/code_reviewer.py index a277ac59e..389d1f286 100644 --- a/biz/utils/code_reviewer.py +++ b/biz/utils/code_reviewer.py @@ -1,7 +1,7 @@ import abc import os import re -from typing import Dict, Any, List +from typing import Dict, Any, List, Optional import yaml from jinja2 import Template @@ -16,11 +16,20 @@ class BaseReviewer(abc.ABC): def __init__(self, prompt_key: str): self.client = Factory().getClient() - self.prompts = self._load_prompts(prompt_key, os.getenv("REVIEW_STYLE", "professional")) + self.prompts = self._load_prompts(prompt_key) - def _load_prompts(self, prompt_key: str, style="professional") -> Dict[str, Any]: + def _load_prompts( + self, prompt_key: str, style: Optional[str] = None, prompt_templates_file: Optional[str] = None + ) -> Dict[str, Any]: """加载提示词配置""" - prompt_templates_file = "conf/prompt_templates.yml" + if not style: + # 如果未提供, 从环境变量中获取审查风格,默认为 "professional" + style = os.getenv("REVIEW_STYLE", "professional") + + if not prompt_templates_file: + # 如果未提供, 使用默认的提示词配置文件路径 + prompt_templates_file = "conf/prompt_templates.yml" + try: # 在打开 YAML 文件时显式指定编码为 UTF-8,避免使用系统默认的 GBK 编码。 with open(prompt_templates_file, "r", encoding="utf-8") as file: From 9af5f2faab8ca961f6212c271df8cb3955447fdb Mon Sep 17 00:00:00 2001 From: duanxufu Date: Sun, 3 Aug 2025 11:46:13 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BA=A7=E5=88=AB=E7=9A=84=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/queue/worker.py | 69 +++++++++++++++++++++----------------- biz/utils/code_reviewer.py | 25 +++++++++----- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/biz/queue/worker.py b/biz/queue/worker.py index a58cdef34..12f774510 100644 --- a/biz/queue/worker.py +++ b/biz/queue/worker.py @@ -27,6 +27,7 @@ def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gi score = 0 additions = 0 deletions = 0 + project_name = webhook_data['project']['name'] if push_review_enabled: # 获取PUSH的changes changes = handler.get_push_changes() @@ -38,7 +39,7 @@ def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gi if len(changes) > 0: commits_text = ';'.join(commit.get('message', '').strip() for commit in commits) - review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text) + review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text, project_name) score = CodeReviewer.parse_review_score(review_text=review_result) for item in changes: additions += item['additions'] @@ -46,19 +47,21 @@ def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gi # 将review结果提交到Gitlab的 notes handler.add_push_notes(f'Auto Review Result: \n{review_result}') - event_manager['push_reviewed'].send(PushReviewEntity( - project_name=webhook_data['project']['name'], - author=webhook_data['user_username'], - branch=webhook_data.get('ref', '').replace('refs/heads/', ''), - updated_at=int(datetime.now().timestamp()), # 当前时间 - commits=commits, - score=score, - review_result=review_result, - url_slug=gitlab_url_slug, - webhook_data=webhook_data, - additions=additions, - deletions=deletions, - )) + event_manager['push_reviewed'].send( + PushReviewEntity( + project_name=project_name, + author=webhook_data['user_username'], + branch=webhook_data.get('ref', '').replace('refs/heads/', ''), + updated_at=int(datetime.now().timestamp()), # 当前时间 + commits=commits, + score=score, + review_result=review_result, + url_slug=gitlab_url_slug, + webhook_data=webhook_data, + additions=additions, + deletions=deletions, + ) + ) except Exception as e: error_message = f'服务出现未知错误: {str(e)}\n{traceback.format_exc()}' @@ -133,7 +136,7 @@ def handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_url # review 代码 commits_text = ';'.join(commit['title'] for commit in commits) - review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text) + review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text, project_name) # 将review结果提交到Gitlab的 notes handler.add_merge_request_notes(f'Auto Review Result: \n{review_result}') @@ -177,6 +180,10 @@ def handle_github_push_event(webhook_data: dict, github_token: str, github_url: score = 0 additions = 0 deletions = 0 + + # 获取项目名称 + project_name = webhook_data['repository']['name'] + if push_review_enabled: # 获取PUSH的changes changes = handler.get_push_changes() @@ -188,7 +195,7 @@ def handle_github_push_event(webhook_data: dict, github_token: str, github_url: if len(changes) > 0: commits_text = ';'.join(commit.get('message', '').strip() for commit in commits) - review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text) + review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text, project_name) score = CodeReviewer.parse_review_score(review_text=review_result) for item in changes: additions += item.get('additions', 0) @@ -196,19 +203,21 @@ def handle_github_push_event(webhook_data: dict, github_token: str, github_url: # 将review结果提交到GitHub的 notes handler.add_push_notes(f'Auto Review Result: \n{review_result}') - event_manager['push_reviewed'].send(PushReviewEntity( - project_name=webhook_data['repository']['name'], - author=webhook_data['sender']['login'], - branch=webhook_data['ref'].replace('refs/heads/', ''), - updated_at=int(datetime.now().timestamp()), # 当前时间 - commits=commits, - score=score, - review_result=review_result, - url_slug=github_url_slug, - webhook_data=webhook_data, - additions=additions, - deletions=deletions, - )) + event_manager['push_reviewed'].send( + PushReviewEntity( + project_name=project_name, + author=webhook_data['sender']['login'], + branch=webhook_data['ref'].replace('refs/heads/', ''), + updated_at=int(datetime.now().timestamp()), # 当前时间 + commits=commits, + score=score, + review_result=review_result, + url_slug=github_url_slug, + webhook_data=webhook_data, + additions=additions, + deletions=deletions, + ) + ) except Exception as e: error_message = f'服务出现未知错误: {str(e)}\n{traceback.format_exc()}' @@ -273,7 +282,7 @@ def handle_github_pull_request_event(webhook_data: dict, github_token: str, gith # review 代码 commits_text = ';'.join(commit['title'] for commit in commits) - review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text) + review_result = CodeReviewer().review_and_strip_code(str(changes), commits_text, project_name) # 将review结果提交到GitHub的 notes handler.add_pull_request_notes(f'Auto Review Result: \n{review_result}') diff --git a/biz/utils/code_reviewer.py b/biz/utils/code_reviewer.py index 389d1f286..3dd79baf2 100644 --- a/biz/utils/code_reviewer.py +++ b/biz/utils/code_reviewer.py @@ -69,12 +69,13 @@ class CodeReviewer(BaseReviewer): def __init__(self): super().__init__("code_review_prompt") - def review_and_strip_code(self, changes_text: str, commits_text: str = "") -> str: + def review_and_strip_code(self, changes_text: str, commits_text: str = "", project_name: str = "") -> str: """ Review判断changes_text超出取前REVIEW_MAX_TOKENS个token,超出则截断changes_text, 调用review_code方法,返回review_result,如果review_result是markdown格式,则去掉头尾的``` - :param changes_text: - :param commits_text: + :param changes_text: 代码变更内容 + :param commits_text: 提交信息 + :param project_name: 项目名称 :return: """ # 如果超长,取前REVIEW_MAX_TOKENS个token @@ -89,20 +90,26 @@ def review_and_strip_code(self, changes_text: str, commits_text: str = "") -> st if tokens_count > review_max_tokens: changes_text = truncate_text_by_tokens(changes_text, review_max_tokens) - review_result = self.review_code(changes_text, commits_text).strip() + review_result = self.review_code(changes_text, commits_text, project_name).strip() if review_result.startswith("```markdown") and review_result.endswith("```"): return review_result[11:-3].strip() return review_result - def review_code(self, diffs_text: str, commits_text: str = "") -> str: + def review_code(self, diffs_text: str, commits_text: str = "", project_name: str = "") -> str: """Review 代码并返回结果""" + project_prompts_path = os.getenv(f"{project_name.upper}_PROMPT", None) + + # 按需重新加载 prompts 配置, 同时也可以支持项目级别提示词的热加载 + prompts = ( + self._load_prompts(prompt_key="code_review_prompt", prompt_templates_file=project_prompts_path) + if project_prompts_path + else self.prompts + ) messages = [ - self.prompts["system_message"], + prompts["system_message"], { "role": "user", - "content": self.prompts["user_message"]["content"].format( - diffs_text=diffs_text, commits_text=commits_text - ), + "content": prompts["user_message"]["content"].format(diffs_text=diffs_text, commits_text=commits_text), }, ] return self.call_llm(messages) From c0fe8b9ae5b05fb0230a9c91b89f0889c2c8b38f Mon Sep 17 00:00:00 2001 From: duanxufu Date: Sun, 3 Aug 2025 13:09:38 +0800 Subject: [PATCH 4/5] =?UTF-8?q?doc:=20=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=BA=A7=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=A8=A1=E6=9D=BF=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/faq.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/faq.md b/doc/faq.md index 8826b18e7..3c73780eb 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -160,3 +160,34 @@ WORKER_QUEUE=gitlab_test_cn GITHUB_ACCESS_TOKEN=your-access-token #替换为你的Access Token ``` +### 同一个实例中是否支持给多个项目配置Review的提示词模板? + + +同一个实例中,可以配置多个项目Review的提示词模板。具体操作如下: +#### 1. 创建项目提示词文件 + +首先,为特定项目创建自定义提示词文件,例如 `conf/myproject_prompt.yml`。 + +提示词文件格式可参考 [prompt_templates.yml](./conf/prompt_templates.yml) + +#### 2. 配置环境变量 + +在 `.env` 文件中添加以下内容: + +```shell +# 配置规则:项目名全大写 + _PROMPT = 提示词文件路径 +PROJECT_NAME_PROMPT=conf/project_prompt.yml +``` + +例如,如果您的项目名为 `payment_service`,则应在 .env 文件中添加: + +```shell +PAYMENT_SERVICE_PROMPT=conf/payment_service_prompt.yml +``` + +#### 3. 注意事项 + +- 如果未配置项目专属提示词,系统将使用默认提示词模板 +- 项目提示词支持热加载,修改提示词文件后无需重启服务 +- 环境变量配置后需要重启服务才能生效 + From 8f6235dc4b659d2a9b88fb33a67a21cab326f9b7 Mon Sep 17 00:00:00 2001 From: duanxufu Date: Mon, 4 Aug 2025 16:38:11 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=90=8D=E7=A7=B0=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E6=97=B6=E4=B8=8D=E6=94=AF=E6=8C=81=E8=BF=9E=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=20`-`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/utils/code_reviewer.py | 3 ++- doc/faq.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/biz/utils/code_reviewer.py b/biz/utils/code_reviewer.py index 3dd79baf2..d8e3e7d00 100644 --- a/biz/utils/code_reviewer.py +++ b/biz/utils/code_reviewer.py @@ -97,7 +97,8 @@ def review_and_strip_code(self, changes_text: str, commits_text: str = "", proje def review_code(self, diffs_text: str, commits_text: str = "", project_name: str = "") -> str: """Review 代码并返回结果""" - project_prompts_path = os.getenv(f"{project_name.upper}_PROMPT", None) + normalized_project_name = project_name.replace("-", "_") if project_name else project_name + project_prompts_path = os.getenv(f"{normalized_project_name.upper()}_PROMPT", None) # 按需重新加载 prompts 配置, 同时也可以支持项目级别提示词的热加载 prompts = ( diff --git a/doc/faq.md b/doc/faq.md index 3c73780eb..185dae66f 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -190,4 +190,4 @@ PAYMENT_SERVICE_PROMPT=conf/payment_service_prompt.yml - 如果未配置项目专属提示词,系统将使用默认提示词模板 - 项目提示词支持热加载,修改提示词文件后无需重启服务 - 环境变量配置后需要重启服务才能生效 - +- 项目名称中包含 `-` 时,请使用 `_` 替换