Skip to content
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
5 changes: 3 additions & 2 deletions biz/llm/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 39 additions & 30 deletions biz/queue/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -38,27 +39,29 @@ 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']
deletions += item['deletions']
# 将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()}'
Expand Down Expand Up @@ -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}')
Expand Down Expand Up @@ -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()
Expand All @@ -188,27 +195,29 @@ 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)
deletions += item.get('deletions', 0)
# 将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()}'
Expand Down Expand Up @@ -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}')
Expand Down
43 changes: 30 additions & 13 deletions biz/utils/code_reviewer.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -60,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
Expand All @@ -80,20 +90,27 @@ 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 代码并返回结果"""
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 = (
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)
Expand Down
31 changes: 31 additions & 0 deletions doc/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 注意事项

- 如果未配置项目专属提示词,系统将使用默认提示词模板
- 项目提示词支持热加载,修改提示词文件后无需重启服务
- 环境变量配置后需要重启服务才能生效
- 项目名称中包含 `-` 时,请使用 `_` 替换