Skip to content

Commit 55ddbe1

Browse files
committed
Merge branch 'main_upstream' into feature/github-actions-docker-build
2 parents e87616d + f64f4db commit 55ddbe1

File tree

10 files changed

+119
-27
lines changed

10 files changed

+119
-27
lines changed

Dockerfile

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ COPY requirements.txt .
1313
# 安装依赖
1414
RUN pip install --no-cache-dir -r requirements.txt
1515

16-
COPY biz .
17-
COPY core .
18-
COPY api.py .
19-
COPY ui.py .
20-
COPY prompt_templates.yml .
16+
COPY biz ./biz
17+
COPY core ./core
18+
COPY api.py ./api.py
19+
COPY ui.py ./ui.py
20+
COPY prompt_templates.yml ./prompt_templates.yml
2121
RUN mkdir -p log data
2222

2323
# 暴露 Flask 和 Streamlit 的端口

api.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,20 @@ def handle_webhook():
157157
else:
158158
return jsonify({'message': 'Invalid data format'}), 400
159159

160+
def transform_gitlab_url(url):
161+
# 去掉 http:// 或 https://
162+
if url.startswith("http://"):
163+
url = url[len("http://"):]
164+
elif url.startswith("https://"):
165+
url = url[len("https://"):]
166+
167+
if url.endswith('/'):
168+
url = url[:-1]
169+
170+
# 替换 . 和 / 为 _
171+
transformed_url = url.replace('.', '_').replace('/', '_')
172+
173+
return transformed_url
160174

161175
def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
162176
try:
@@ -176,6 +190,7 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
176190
changes = filter_changes(changes)
177191
if not changes:
178192
logger.info('未检测到PUSH代码的修改,修改文件可能不满足SUPPORTED_EXTENSIONS。')
193+
return
179194
review_result = "关注的文件没有修改"
180195

181196
if len(changes) > 0:
@@ -185,6 +200,8 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
185200
# 将review结果提交到Gitlab的 notes
186201
handler.add_push_notes(f'Auto Review Result: \n{review_result}')
187202

203+
url_base = transform_gitlab_url(gitlab_url)
204+
188205
event_manager['push_reviewed'].send(PushReviewEntity(
189206
project_name=webhook_data['project']['name'],
190207
author=webhook_data['user_username'],
@@ -193,6 +210,7 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
193210
commits=commits,
194211
score=score,
195212
review_result=review_result,
213+
gitlab_url = url_base,
196214
))
197215

198216
except Exception as e:
@@ -233,9 +251,14 @@ def __handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_u
233251
commits_text = ';'.join(commit['title'] for commit in commits)
234252
review_result = review_code(str(changes), commits_text)
235253

254+
if "COT ABORT!" in review_result:
255+
logger.error('COT ABORT!')
256+
return
257+
236258
# 将review结果提交到Gitlab的 notes
237259
handler.add_merge_request_notes(f'Auto Review Result: \n{review_result}')
238260

261+
url_base = transform_gitlab_url(gitlab_url)
239262
# dispatch merge_request_reviewed event
240263
event_manager['merge_request_reviewed'].send(
241264
MergeRequestReviewEntity(
@@ -247,7 +270,8 @@ def __handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_u
247270
commits=commits,
248271
score=CodeReviewer.parse_review_score(review_text=review_result),
249272
url=webhook_data['object_attributes']['url'],
250-
review_result=review_result
273+
review_result=review_result,
274+
gitlab_url = url_base,
251275
)
252276
)
253277

biz/entity/review_entity.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class MergeRequestReviewEntity:
22
def __init__(self, project_name: str, author: str, source_branch: str, target_branch: str, updated_at: int,
3-
commits: list, score: float, url: str, review_result: str):
3+
commits: list, score: float, url: str, review_result: str, gitlab_url: str):
44
self.project_name = project_name
55
self.author = author
66
self.source_branch = source_branch
@@ -10,6 +10,7 @@ def __init__(self, project_name: str, author: str, source_branch: str, target_br
1010
self.score = score
1111
self.url = url
1212
self.review_result = review_result
13+
self.gitlab_url = gitlab_url
1314

1415
@property
1516
def commit_messages(self):
@@ -19,14 +20,15 @@ def commit_messages(self):
1920

2021
class PushReviewEntity:
2122
def __init__(self, project_name: str, author: str, branch: str, updated_at: int, commits: list, score: float,
22-
review_result: str):
23+
review_result: str, gitlab_url: str):
2324
self.project_name = project_name
2425
self.author = author
2526
self.branch = branch
2627
self.updated_at = updated_at
2728
self.commits = commits
2829
self.score = score
2930
self.review_result = review_result
31+
self.gitlab_url = gitlab_url
3032

3133
@property
3234
def commit_messages(self):

biz/event/event_manager.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def on_merge_request_reviewed(mr_review_entity: MergeRequestReviewEntity):
3232
{mr_review_entity.review_result}
3333
"""
3434
im_notifier.send_notification(content=im_msg, msg_type='markdown', title='Merge Request Review',
35-
project_name=mr_review_entity.project_name)
35+
project_name=mr_review_entity.project_name, url_base=mr_review_entity.gitlab_url)
3636

3737
# 记录到数据库
3838
ReviewService().insert_mr_review_log(mr_review_entity)
@@ -58,7 +58,7 @@ def on_push_reviewed(entity: PushReviewEntity):
5858
if entity.review_result:
5959
im_msg += f"#### AI Review 结果: \n {entity.review_result}\n\n"
6060
im_notifier.send_notification(content=im_msg, msg_type='markdown',
61-
title=f"{entity.project_name} Push Event", project_name=entity.project_name)
61+
title=f"{entity.project_name} Push Event", project_name=entity.project_name, url_base=entity.gitlab_url)
6262

6363
# 记录到数据库
6464
ReviewService().insert_push_review_log(entity)

biz/utils/im/dingtalk.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self, webhook_url=None):
1616
self.enabled = os.environ.get('DINGTALK_ENABLED', '0') == '1'
1717
self.default_webhook_url = webhook_url or os.environ.get('DINGTALK_WEBHOOK_URL')
1818

19-
def _get_webhook_url(self, project_name=None):
19+
def _get_webhook_url(self, project_name=None, url_base=None):
2020
"""
2121
获取项目对应的 Webhook URL
2222
:param project_name: 项目名称
@@ -35,6 +35,12 @@ def _get_webhook_url(self, project_name=None):
3535
for env_key, env_value in os.environ.items():
3636
if env_key.upper() == target_key:
3737
return env_value # 找到匹配项,直接返回
38+
39+
# url_base 优先级次之
40+
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
41+
for env_key, env_value in os.environ.items():
42+
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
43+
return env_value # 找到匹配项,直接返回
3844

3945
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
4046
if self.default_webhook_url:
@@ -43,13 +49,13 @@ def _get_webhook_url(self, project_name=None):
4349
# 如果既未找到匹配项,也没有默认值,抛出异常
4450
raise ValueError(f"未找到项目 '{project_name}' 对应的钉钉Webhook URL,且未设置默认的 Webhook URL。")
4551

46-
def send_message(self, content: str, msg_type='text', title='通知', is_at_all=False, project_name=None):
52+
def send_message(self, content: str, msg_type='text', title='通知', is_at_all=False, project_name=None, url_base = None):
4753
if not self.enabled:
4854
logger.info("钉钉推送未启用")
4955
return
5056

5157
try:
52-
post_url = self._get_webhook_url(project_name=project_name)
58+
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
5359
headers = {
5460
"Content-Type": "application/json",
5561
"Charset": "UTF-8"

biz/utils/im/feishu.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self, webhook_url=None):
1414
self.default_webhook_url = webhook_url or os.environ.get('FEISHU_WEBHOOK_URL', '')
1515
self.enabled = os.environ.get('FEISHU_ENABLED', '0') == '1'
1616

17-
def _get_webhook_url(self, project_name=None):
17+
def _get_webhook_url(self, project_name=None, url_base=None):
1818
"""
1919
获取项目对应的 Webhook URL
2020
:param project_name: 项目名称
@@ -33,6 +33,12 @@ def _get_webhook_url(self, project_name=None):
3333
for env_key, env_value in os.environ.items():
3434
if env_key.upper() == target_key:
3535
return env_value # 找到匹配项,直接返回
36+
37+
# url_base 优先级次之
38+
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
39+
for env_key, env_value in os.environ.items():
40+
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
41+
return env_value # 找到匹配项,直接返回
3642

3743
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
3844
if self.default_webhook_url:
@@ -41,7 +47,7 @@ def _get_webhook_url(self, project_name=None):
4147
# 如果既未找到匹配项,也没有默认值,抛出异常
4248
raise ValueError(f"未找到项目 '{project_name}' 对应的 Feishu Webhook URL,且未设置默认的 Webhook URL。")
4349

44-
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None):
50+
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None, url_base=None):
4551
"""
4652
发送飞书消息
4753
:param content: 消息内容
@@ -55,7 +61,7 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr
5561
return
5662

5763
try:
58-
post_url = self._get_webhook_url(project_name=project_name)
64+
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
5965
if msg_type == 'markdown':
6066
data = {
6167
"msg_type": "interactive",

biz/utils/im/im_notifier.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,26 @@
33
from biz.utils.im.wecom import WeComNotifier
44

55

6-
def send_notification(content, msg_type='text', title="通知", is_at_all=False, project_name=None):
6+
def send_notification(content, msg_type='text', title="通知", is_at_all=False, project_name=None, url_base=None):
77
"""
88
发送通知消息到配置的平台(钉钉和企业微信)
99
:param content: 消息内容
1010
:param msg_type: 消息类型,支持text和markdown
1111
:param title: 消息标题(markdown类型时使用)
1212
:param is_at_all: 是否@所有人
13+
:param url_base: gitlab服务器的url地址 http://www.gitlab.com 传递进来自动移除http和https,转换成 www_gitlab_com
1314
"""
1415
# 钉钉推送
1516
notifier = DingTalkNotifier()
1617
notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
17-
project_name=project_name)
18+
project_name=project_name, url_base=url_base)
1819

1920
# 企业微信推送
2021
wecom_notifier = WeComNotifier()
2122
wecom_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
22-
project_name=project_name)
23+
project_name=project_name, url_base=url_base)
2324

2425
# 飞书推送
2526
feishu_notifier = FeishuNotifier()
2627
feishu_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
27-
project_name=project_name)
28+
project_name=project_name, url_base=url_base)

biz/utils/im/wecom.py

+38-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self, webhook_url=None):
1414
self.default_webhook_url = webhook_url or os.environ.get('WECOM_WEBHOOK_URL', '')
1515
self.enabled = os.environ.get('WECOM_ENABLED', '0') == '1'
1616

17-
def _get_webhook_url(self, project_name=None):
17+
def _get_webhook_url(self, project_name=None, url_base=None):
1818
"""
1919
获取项目对应的 Webhook URL
2020
:param project_name: 项目名称
@@ -27,11 +27,19 @@ def _get_webhook_url(self, project_name=None):
2727
return self.default_webhook_url
2828
else:
2929
raise ValueError("未提供项目名称,且未设置默认的企业微信 Webhook URL。")
30-
30+
3131
# 遍历所有环境变量(忽略大小写),找到项目对应的 Webhook URL
3232
target_key = f"WECOM_WEBHOOK_URL_{project_name.upper()}"
33+
34+
# 仓库名称优先级高,先匹配
35+
for env_key, env_value in os.environ.items():
36+
if env_key.upper() == target_key :
37+
return env_value # 找到匹配项,直接返回
38+
39+
# url_base 优先级次之
40+
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
3341
for env_key, env_value in os.environ.items():
34-
if env_key.upper() == target_key:
42+
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
3543
return env_value # 找到匹配项,直接返回
3644

3745
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
@@ -60,7 +68,7 @@ def format_markdown_content(self, content, title=None):
6068
formatted_content += content
6169
return formatted_content
6270

63-
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None):
71+
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None, url_base=None):
6472
"""
6573
发送企业微信消息
6674
:param content: 消息内容
@@ -73,7 +81,7 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr
7381
return
7482

7583
try:
76-
post_url = self._get_webhook_url(project_name=project_name)
84+
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
7785
if msg_type == 'markdown':
7886
formatted_content = self.format_markdown_content(content, title)
7987
data = {
@@ -99,6 +107,31 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr
99107

100108
if response.status_code != 200:
101109
logger.error(f"企业微信消息发送失败! webhook_url:{post_url}, error_msg:{response.text}")
110+
111+
try:
112+
data = json.loads(response.text)
113+
errmsg = data.get("errmsg")
114+
if errmsg and "markdown.content exceed max length" in errmsg:
115+
# markdown渲染过长了,尝试text发送
116+
data = {
117+
"msgtype": "text",
118+
"text": {
119+
"content": content,
120+
"mentioned_list": ["@all"] if is_at_all else []
121+
}
122+
}
123+
124+
response = requests.post(
125+
url=post_url,
126+
json=data,
127+
headers={'Content-Type': 'application/json'}
128+
)
129+
130+
except json.JSONDecodeError as e:
131+
print(f"JSON 解析失败: {e}")
132+
except Exception as e:
133+
logger.error(f"企业微信消息发送失败! ", e)
134+
102135
return
103136

104137
result = response.json()

core/llm/client/ollama_client.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ def _extract_content(self, content: str) -> str:
2727
Returns:
2828
str: 提取后的内容。
2929
"""
30-
if re.search(r'<think>.*?</think>', content, re.DOTALL):
30+
if "<think>" in content and "</think>" not in content:
31+
# 大模型回复的时候,思考链有可能截断,那么果断忽略回复,返回空
32+
return "COT ABORT!"
33+
elif "<think>" not in content and "</think>" in content:
34+
return content.split("</think>", 1)[1].strip()
35+
elif re.search(r'<think>.*?</think>', content, re.DOTALL):
3136
return re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
3237
return content
3338

doc/faq.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,19 @@ DINGTALK_WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token={access_t
6666
```
6767
OLLAMA_API_BASE_URL=http://127.0.0.1:11434 # 错误
6868
OLLAMA_API_BASE_URL=http://{宿主机/外网IP地址}:11434 # 正确
69-
```
69+
```
70+
71+
#### 5.如何支持多个git服务器及对应的不通webhook地址
72+
73+
在项目的 .env 文件中,配置不同GIT项目对应的群机器人的 Webhook 地址。替换
74+
以 DingTalk 为例,配置如下:
75+
76+
```
77+
DINGTALK_ENABLED=1
78+
# git服务器A实际地址 http://192.168.30.164/
79+
DINGTALK_WEBHOOK_192_168_30_164=https://oapi.dingtalk.com/robot/send?access_token={access_token_of_project_a}
80+
# git服务器A实际地址 http://192.168.35.164/
81+
DINGTALK_WEBHOOK_192_168_35_164=https://oapi.dingtalk.com/robot/send?access_token={access_token_of_project_a}
82+
```
83+
84+
飞书和企业微信的配置方式类似,GIT服务器的群机器人优先级,仓库名字>特定服务器地址>默认服务器地址

0 commit comments

Comments
 (0)