Skip to content

Commit f281d40

Browse files
committed
feat(other): 开发单据值守的mcp工具 #15182
1 parent e2605f1 commit f281d40

File tree

14 files changed

+564
-23
lines changed

14 files changed

+564
-23
lines changed

dbm-ui/backend/core/notify/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
specific language governing permissions and limitations under the License.
1010
"""
1111

12-
from .handlers import NotifyAdapter, send_msg
12+
from .handlers import NotifyAdapter, send_msg, send_msg_for_ai_task_guardian

dbm-ui/backend/core/notify/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ class MsgType(StrStructuredEnum):
2727

2828
# 默认通知:企业微信
2929
DEFAULT_BIZ_NOTIFY_CONFIG = {status: {MsgType.RTX.value: True} for status in TicketStatus.get_values()}
30+
31+
# 单据值守默认通知方式: 企业微信
32+
DEFAULT_BIZ_AI_NOTIFY_CONFIG = {"AI_TASK_GUARDIAN": {MsgType.RTX.value: True}}

dbm-ui/backend/core/notify/handlers.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@
2222
from backend.components.bkchat.client import BkChatApi
2323
from backend.configuration.constants import BizSettingsEnum
2424
from backend.configuration.models import BizSettings
25-
from backend.core.notify.constants import DEFAULT_BIZ_NOTIFY_CONFIG, MsgType
25+
from backend.core.notify.constants import DEFAULT_BIZ_AI_NOTIFY_CONFIG, DEFAULT_BIZ_NOTIFY_CONFIG, MsgType
2626
from backend.core.notify.exceptions import NotifyBaseException
27-
from backend.core.notify.template import FAILED_TEMPLATE, FINISHED_TEMPLATE, TERMINATE_TEMPLATE, TODO_TEMPLATE
27+
from backend.core.notify.template import (
28+
AI_TASK_GUARDIAN_TEMPLATE,
29+
FAILED_TEMPLATE,
30+
FINISHED_TEMPLATE,
31+
TERMINATE_TEMPLATE,
32+
TODO_TEMPLATE,
33+
)
2834
from backend.db_meta.models import AppCache
2935
from backend.env import DEFAULT_USERNAME
3036
from backend.exceptions import ApiResultError
@@ -344,6 +350,29 @@ def render_msg_template(self, msg_type: str):
344350
content = textwrap.dedent(template.render(payload))
345351
return title, content
346352

353+
def render_msg_template_for_ai_task(self, ai_result: str):
354+
"""
355+
拼装单据值守推送的信息内容,以及标题
356+
"""
357+
biz = AppCache.objects.get(bk_biz_id=self.bk_biz_id)
358+
title = _("「DBM」:检测到您的{ticket_type}单据「{ticket_id}」有风险").format(
359+
ticket_type=TicketType.get_choice_label(self.ticket.ticket_type),
360+
ticket_id=self.ticket.id,
361+
)
362+
# 渲染通知内容
363+
jinja_env = Environment()
364+
template = jinja_env.from_string(AI_TASK_GUARDIAN_TEMPLATE)
365+
payload = {
366+
"creator": self.ticket.creator,
367+
"biz_name": f"{biz.bk_biz_name}(#{self.bk_biz_id}, {biz.db_app_abbr})",
368+
"cluster_domains": ",".join(self.clusters),
369+
"submit_time": self.ticket.create_at.astimezone().strftime("%Y-%m-%d %H:%M:%S%z"),
370+
"running_time": f"{self.ticket.get_cost_time()}s",
371+
"ai_result": ai_result,
372+
}
373+
content = textwrap.dedent(template.render(payload))
374+
return title, content
375+
347376
def send_msg(self):
348377
# 获取单据通知设置,优先: 单据配置 > 业务配置 > 默认业务配置
349378
if self.phase in self.ticket.msg_config:
@@ -378,8 +407,41 @@ def send_msg(self):
378407
except (ApiResultError, Exception) as e:
379408
logger.error(_("[{}]消息发送失败,错误信息: {}").format(MsgType.get_choice_label(msg_type), e))
380409

410+
def send_msg_of_ai_task_guardian(self, ai_result: str):
411+
"""
412+
定义AI单据值守推送消息的逻辑
413+
@param ai_result: 调用智能体之后的结果信息
414+
"""
415+
biz_notify_config = BizSettings.get_setting_value(
416+
self.bk_biz_id, key=BizSettingsEnum.NOTIFY_CONFIG, default=DEFAULT_BIZ_AI_NOTIFY_CONFIG
417+
)
418+
send_msg_config = biz_notify_config["AI_TASK_GUARDIAN"]
419+
420+
send_msg_types = [msg_type for msg_type in send_msg_config if send_msg_config.get(msg_type)]
421+
for msg_type in send_msg_types:
422+
notify_class, context = self.get_notify_class(msg_type)
423+
424+
# 如果是群机器人通知,则接受者为群ID
425+
if msg_type == MsgType.WECOM_ROBOT:
426+
self.receivers = send_msg_config.get(MsgType.WECOM_ROBOT.value, [])
427+
428+
# 拼接信息通知
429+
title, content = self.render_msg_template_for_ai_task(ai_result=ai_result)
430+
431+
try:
432+
notify_class(title, content, self.receivers).send_msg(msg_type, context=context)
433+
except (ApiResultError, Exception) as e:
434+
logger.error(_("[{}]消息发送失败,错误信息: {}").format(MsgType.get_choice_label(msg_type), e))
435+
381436

382437
@shared_task
383438
def send_msg(ticket_id: int, deadline: int = None):
384439
# 可异步发送消息,非阻塞路径默认不抛出异常
385440
NotifyAdapter(ticket_id, deadline).send_msg()
441+
442+
443+
@shared_task
444+
def send_msg_for_ai_task_guardian(ticket_id: int, ai_result: str, deadline: int = None):
445+
# 可异步发送消息,非阻塞路径默认不抛出异常
446+
# AI单据值守消息通道专属
447+
NotifyAdapter(ticket_id, deadline).send_msg_of_ai_task_guardian(ai_result=ai_result)

dbm-ui/backend/core/notify/template.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,15 @@
6161
查看详情: {{detail_address}}\
6262
"""
6363
)
64+
65+
# 定义单据值守的通知模板
66+
AI_TASK_GUARDIAN_TEMPLATE = _(
67+
"""\
68+
申请人: {{creator}}
69+
业务: {{biz_name}}
70+
相关域名: {{cluster_domains}}
71+
申请时间: {{submit_time}}
72+
执行时间: {{running_time}}
73+
AI检测详情: {{ai_result}}\
74+
"""
75+
)

dbm-ui/backend/db_monitor/views/event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def search(self, request):
114114
conditions.append(f'({" OR ".join(cluster_type_conditions)})')
115115

116116
params["query_string"] = " AND ".join(conditions)
117-
117+
print(params)
118118
data = BKMonitorV3Api.search_alert(params)
119119
# 对于维度不包含appid的,暂时标记,无法做到鉴权和屏蔽
120120
for alert in data["alerts"]:
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
3+
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
4+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at https://opensource.org/licenses/MIT
6+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
7+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
8+
specific language governing permissions and limitations under the License.
9+
"""
10+
from datetime import datetime
11+
from typing import Dict, List
12+
13+
from django.utils.translation import gettext as _
14+
15+
from backend import env
16+
from backend.components import BKMonitorV3Api
17+
18+
19+
class QueryMonitorAlarm(object):
20+
"""
21+
处理查询监控数据的类
22+
"""
23+
24+
@staticmethod
25+
def filter_alarm_by_create_time(alerts: List[Dict], start_time: datetime, end_time: datetime):
26+
"""
27+
感觉蓝鲸监控返回的告警条数,在做二次过滤,过滤出某个时间范围中,生成的告警记录
28+
因为search_alert返回告警条目的时候,只要某类告警记录,它的未恢复的时间区间,和你传入的时间区间有交集,则也会返回过来
29+
但往往我们可能需要这个时间区间所产生的告警记录,所以设计这个方法,根据每条记录的 create_time, 做二次过滤
30+
@param alerts: 蓝鲸监控返回的告警记录列表
31+
@param start_time: 起始时间点
32+
@param end_time: 截止时间点
33+
"""
34+
filter_alerts = []
35+
for alert in alerts:
36+
if int(start_time.timestamp()) <= alert["create_time"] <= int(end_time.timestamp()):
37+
filter_alerts.append(alert)
38+
return filter_alerts
39+
40+
@staticmethod
41+
def query_alarm_for_cluster_ids(
42+
bk_biz_id: int, cluster_domains: List[str], start_time: datetime, end_time: datetime
43+
):
44+
"""
45+
根据传入的时间范围,查询这段时间内的这批集群ID的告警信息
46+
@param cluster_domains: 查询的集群域名列表
47+
@param start_time: 查询的起始时间点
48+
@param end_time: 查询的截止时间点
49+
"""
50+
query_param = {
51+
"bk_biz_ids": [env.DBA_APP_BK_BIZ_ID],
52+
"start_time": int(start_time.timestamp()),
53+
"end_time": int(end_time.timestamp()),
54+
"page": 1,
55+
"page_size": 2,
56+
"status": ["ABNORMAL"],
57+
"show_aggs": False,
58+
"show_overview": False,
59+
"query_string": "",
60+
}
61+
# 通用查询条件拼接 querystring
62+
conditions = [
63+
f'tags.appid:"{bk_biz_id}"',
64+
" OR ".join([f'tags.cluster_domain:"{c}"' for c in cluster_domains]),
65+
]
66+
query_param["query_string"] = " AND ".join(conditions)
67+
68+
# 获取所有告警记录
69+
all_alerts = []
70+
while True:
71+
try:
72+
data = BKMonitorV3Api.search_alert(query_param)
73+
all_alerts.extend(data["alerts"])
74+
if len(data["alerts"]) == int(data["total"]):
75+
# 代表返回的实际条数,与匹配到总条数相等,则证明这次的返回,已经全部返回,无需进入下一轮请求
76+
break
77+
if len(data["alerts"]) == 0:
78+
# 代表这次分页调用,已经到最后一步,无需进入下一轮请求
79+
break
80+
81+
# 修改query_param的分页参数,进入下一轮请求
82+
query_param["page"] += 1
83+
84+
except Exception as err:
85+
raise Exception(_("调用BKMonitorV3Api失败:{}".format(err)))
86+
87+
# 查询出来的结果
88+
return [
89+
{
90+
"alarm_id": alart["id"],
91+
"alert_name": alart["alert_name"],
92+
"alert_status": alart["status"],
93+
"alert_create_time": int(alart["create_time"]),
94+
"tags": alart["tags"],
95+
}
96+
for alart in QueryMonitorAlarm.filter_alarm_by_create_time(all_alerts, start_time, end_time)
97+
]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
3+
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
4+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at https://opensource.org/licenses/MIT
6+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
7+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
8+
specific language governing permissions and limitations under the License.
9+
"""
10+
11+
from django.utils.translation import gettext_lazy as _
12+
from rest_framework import serializers
13+
14+
15+
class AlertTagsSerializer(serializers.Serializer):
16+
key = serializers.CharField(help_text=_("标签名称"))
17+
value = serializers.CharField(help_text=_("标签值"))
18+
19+
20+
class AlertInfoSerializer(serializers.Serializer):
21+
alarm_id = serializers.CharField(help_text=_("告警记录Id"))
22+
alert_name = serializers.CharField(help_text=_("告警记录名称"))
23+
alert_status = serializers.CharField(help_text=_("告警记录状态"))
24+
alert_create_time = serializers.IntegerField(help_text=_("告警记录创建时间戳"))
25+
tags = serializers.ListField(child=AlertTagsSerializer(), help_text=_("告警记录的标签信息"))
26+
27+
28+
class SearchAlertInputSerializer(serializers.Serializer):
29+
bk_biz_id = serializers.IntegerField(help_text=_("业务Id"))
30+
cluster_domains = serializers.ListField(child=serializers.CharField(), help_text=_("待查询的集群域名列表"))
31+
start_time = serializers.DateTimeField(help_text=_("查询的起始时间"))
32+
end_time = serializers.DateTimeField(help_text=_("查询的截止时间"))
33+
34+
35+
class SearchAlertOutputSerializer(serializers.Serializer):
36+
alert_infos = serializers.ListField(child=AlertInfoSerializer(), help_text=_("告警记录信息"))

dbm-ui/backend/dbm_aiagent/mcp_tools/common/urls.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
"""
1111
from rest_framework.routers import DefaultRouter
1212

13+
from backend.dbm_aiagent.mcp_tools.common.views import DBMetaQueryMcpToolsViewSet
14+
from backend.dbm_aiagent.mcp_tools.common.views.alram_query import MonitorQueryMcpToolsViewSet
1315
from backend.dbm_aiagent.mcp_tools.common.views import BillQueryMcpToolsViewSet, DBMetaQueryMcpToolsViewSet
1416

1517
routers = DefaultRouter(trailing_slash=True)
1618

1719
routers.register(r"", DBMetaQueryMcpToolsViewSet, basename="mcp-dbmeta-query")
1820
routers.register(r"", BillQueryMcpToolsViewSet, basename="mcp-bill-query")
21+
routers.register(r"", MonitorQueryMcpToolsViewSet, basename="mcp-monitor-query")
1922
urlpatterns = routers.urls
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
3+
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
4+
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at https://opensource.org/licenses/MIT
6+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
7+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
8+
specific language governing permissions and limitations under the License.
9+
"""
10+
from django.utils.translation import gettext_lazy as _
11+
from rest_framework.response import Response
12+
13+
from backend.dbm_aiagent.mcp_tools.common.impl.query_moitor_alarm_info import QueryMonitorAlarm
14+
from backend.dbm_aiagent.mcp_tools.common.serializers.alarm_query import (
15+
SearchAlertInputSerializer,
16+
SearchAlertOutputSerializer,
17+
)
18+
from backend.dbm_aiagent.mcp_tools.constants import DBMAMcpTools, DBMMCPTags
19+
from backend.dbm_aiagent.mcp_tools.decorators import mcp_tools_api_decorator
20+
from backend.dbm_aiagent.mcp_tools.views import McpToolsViewSet
21+
from backend.iam_app.handlers.drf_perm.base import RejectPermission
22+
23+
24+
class MonitorQueryMcpToolsViewSet(McpToolsViewSet):
25+
default_permission_class = [RejectPermission()]
26+
27+
@mcp_tools_api_decorator(
28+
description=str(_("查询某个时间区间, 某批集群ID对应的产生的告警记录")),
29+
request_slz=SearchAlertInputSerializer,
30+
response_slz=SearchAlertOutputSerializer,
31+
tags=[DBMMCPTags.READ],
32+
mcp=[DBMAMcpTools.ALARM_QUERY],
33+
name_prefix="alarm_query",
34+
)
35+
def query_monitor_alarm_info(self, request, *args, **kwargs):
36+
bk_biz_id = int(self.get_param("bk_biz_id"))
37+
cluster_domains = self.get_param("cluster_domains")
38+
start_time = self.get_param("start_time")
39+
end_time = self.get_param("end_time")
40+
return Response(
41+
QueryMonitorAlarm.query_alarm_for_cluster_ids(
42+
bk_biz_id=bk_biz_id, cluster_domains=cluster_domains, start_time=start_time, end_time=end_time
43+
)
44+
)

dbm-ui/backend/dbm_aiagent/mcp_tools/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class DBMAMcpTools(StrStructuredEnum):
2121
MYSQL_SLOWLOG = EnumField("mysql-slowlog", "mysql-slowlog")
2222
SQLSERVER_QUERY = EnumField("sqlserver-query", "sqlserver-query")
2323
BILL_QUERY = EnumField("bill-query", "bill-query")
24+
ALARM_QUERY = EnumField("alarm-query", "alarm-query")
2425

2526

2627
class DBMMCPTags(StrStructuredEnum):

0 commit comments

Comments
 (0)