pull/17608/head
fuwx 1 year ago
parent c0358d8d0c
commit 8c269e061f
No known key found for this signature in database
GPG Key ID: C8FA8C18DFB4702B

@ -0,0 +1,66 @@
name: Build Docker Image
on:
push:
tags:
- v*
workflow_dispatch:
env:
DOCKERHUB_REPO: registry.cn-hangzhou.aliyuncs.com/kindlingx/dify-api
jobs:
build-image:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Generate App Version (AMD64)
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
if [ -n "$TAG" ]; then
echo "APP_VERSION=$TAG" >> $GITHUB_ENV
else
BRANCH=$(git rev-parse --abbrev-ref HEAD | sed 's#[^a-zA-Z0-9._-]#-#g')
COMMIT=$(git rev-parse --short HEAD)
echo "APP_VERSION=${BRANCH}-${COMMIT}" >> $GITHUB_ENV
fi
- name: Generate App Version (ARM64)
if: ${{ matrix.os == 'ubuntu-24.04-arm' }}
run: |
TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
if [ -n "$TAG" ]; then
echo "APP_VERSION=$TAG-arm64" >> $GITHUB_ENV
else
BRANCH=$(git rev-parse --abbrev-ref HEAD | sed 's#[^a-zA-Z0-9._-]#-#g')
COMMIT=$(git rev-parse --short HEAD)
echo "APP_VERSION=${BRANCH}-${COMMIT}-arm64" >> $GITHUB_ENV
fi
- name: Build and push
uses: docker/build-push-action@v6
with:
context: ./api
push: true
tags: |
${{ env.DOCKERHUB_REPO }}:${{ env.APP_VERSION }}

@ -17,6 +17,10 @@ APP_WEB_URL=http://127.0.0.1:3000
# Files URL
FILES_URL=http://127.0.0.1:5001
# APO Config
APO_BACKEND_URL=http://127.0.0.1:8080
APO_VM_URL=http://127.0.0.1:8080
# The time in seconds after the signature is rejected
FILES_ACCESS_TIMEOUT=300

@ -0,0 +1,17 @@
from pydantic import Field
from pydantic_settings import BaseSettings
class APOConfig(BaseSettings):
"""
Configuration settings for apo
"""
APO_BACKEND_URL: str = Field(
description="apo backend url",
default="http://localhost:8080",
)
APO_VM_URL: str = Field(
description="apo vm url",
default="http://localhost:8080",
)

@ -4,6 +4,7 @@ from typing import Any
from pydantic.fields import FieldInfo
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict
from .apo import APOConfig
from .deploy import DeploymentConfig
from .enterprise import EnterpriseFeatureConfig
from .extra import ExtraServiceConfig
@ -49,6 +50,8 @@ class RemoteSettingsSourceFactory(PydanticBaseSettingsSource):
class DifyConfig(
# APO config
APOConfig,
# Packaging info
PackagingInfo,
# Deployment configs

@ -562,6 +562,31 @@ class ToolBuiltinListApi(Resource):
)
class APOToolBuiltinListApi(Resource):
def get(self):
user = current_user
user_id = user.id
tenant_id = user.current_tenant_id
parser = reqparse.RequestParser()
parser.add_argument("tool_type", type=str, required=True, nullable=False, location="args")
parser.add_argument("query", type=str, required=False, nullable=True, location="args")
args = parser.parse_args()
return jsonable_encoder(
[
provider.to_dict()
for provider in BuiltinToolManageService.list_apo_tools(
user_id,
tenant_id,
args["tool_type"],
args.get("query", None),
)
]
)
class ToolApiListApi(Resource):
@setup_required
@login_required
@ -648,6 +673,7 @@ api.add_resource(ToolWorkflowProviderGetApi, "/workspaces/current/tool-provider/
api.add_resource(ToolWorkflowProviderListToolApi, "/workspaces/current/tool-provider/workflow/tools")
api.add_resource(ToolBuiltinListApi, "/workspaces/current/tools/builtin")
api.add_resource(APOToolBuiltinListApi, "/workspaces/current/tools/apo")
api.add_resource(ToolApiListApi, "/workspaces/current/tools/api")
api.add_resource(ToolWorkflowListApi, "/workspaces/current/tools/workflow")

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.666992 8.00008C0.666992 3.94999 3.95024 0.666748 8.00033 0.666748C12.0504 0.666748 15.3337 3.94999 15.3337 8.00008C15.3337 12.0502 12.0504 15.3334 8.00033 15.3334C3.95024 15.3334 0.666992 12.0502 0.666992 8.00008ZM8.66699 4.00008C8.66699 3.63189 8.36852 3.33341 8.00033 3.33341C7.63213 3.33341 7.33366 3.63189 7.33366 4.00008V8.00008C7.33366 8.2526 7.47633 8.48344 7.70218 8.59637L10.3688 9.9297C10.6982 10.0944 11.0986 9.96088 11.2633 9.63156C11.4279 9.30224 11.2945 8.90179 10.9651 8.73713L8.66699 7.58806V4.00008Z" fill="#EC4A0A"/>
</svg>

After

Width:  |  Height:  |  Size: 691 B

@ -0,0 +1,8 @@
from typing import Any
from core.tools.builtin_tool.provider import BuiltinToolProviderController
class APOAnalysisProvider(BuiltinToolProviderController):
def _validate_credentials(self, user_id: str, credentials: dict[str, Any]) -> None:
pass

@ -0,0 +1,14 @@
identity:
author: APO
name: apo_analysis
label:
en_US: APOAnalysis
zh_Hans: APO异常检测
pt_BR: APOSelect
description:
en_US: A tool for getting the current time.
zh_Hans: APO异常检测分析。
pt_BR: A tool for getting the current time.
icon: icon.svg
tags:
- utilities

@ -0,0 +1,121 @@
import json
from collections.abc import Generator
from typing import Any, Optional
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class AlertAssociateTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
metric = tool_parameters.get('topology_data')
topology_data = json.loads(metric)['data']
alert_data = json.loads(tool_parameters.get('alert_data'))['data']
alert = self.process_alert(alert_data)
topology = self.process_topology(topology_data, alert)
# res = json.dumps({
# "type": 'llm',
# "display": False,
# "data": topology
# }, ensure_ascii=False)
yield self.create_text_message(topology)
def _summary_delta_event(self, delta_data: list, stats: dict):
for event in delta_data:
service = event['serviceName']
endpoint = event['endpoint']
anormal_status = event['anormalStatus']
anormal_type = event['anormalType']
time = event['timestamp']
p = f'{service}_#"{endpoint}"'
if p not in stats:
stats[p] = {}
if anormal_type not in stats[p]:
stats[p][anormal_type] = {
"add": 0,
"duplicate": 0,
"resolve": 0,
"keep": 0,
"lastTime": time,
"firstTime": time
}
if time < stats[p][anormal_type]["firstTime"]:
stats[p][anormal_type]["firstTime"] = time
if time > stats[p][anormal_type]["lastTime"]:
stats[p][anormal_type]["lastTime"] = time
match anormal_status:
case "startFiring":
stats[p][anormal_type]["add"] += 1
case "resolved":
stats[p][anormal_type]["resolve"] += 1
if "resolveTime" not in stats[p][anormal_type]:
stats[p][anormal_type]["resolveTime"] = time
case "updatedFiring":
stats[p][anormal_type]["duplicate"] += 1
def _summary_keep_event(self, json_data: list, stats: dict):
for event in json_data:
service_name = event["serviceName"]
anormal_type = event["anormalType"]
endpoint = event["endpoint"]
p = service_name + '_#"' + endpoint + '"'
stats[p][anormal_type]["keep"] += 1
def process_alert(self, alert_data: dict) -> dict:
stats = {}
json_data = alert_data["deltaAnormalEvents"]
self._summary_delta_event(json_data, stats)
json_data = alert_data["finalAnormalEvents"]
self._summary_keep_event(json_data, stats)
return stats
def _build_tree(self, node: str, depth=0, relation_dict={}, list=[], alert_data={}):
indent = " " * depth
text = ""
text += f'{indent}{depth}──'
if node in alert_data:
alert_text = json.dumps(alert_data[node])
text += f"{node} {alert_text}\n"
else:
text += f"{node}\n"
list.append(text)
if node in relation_dict:
for child in relation_dict[node]:
self._build_tree(child, depth + 1, relation_dict, list, alert_data)
def process_topology(self, topology_data: dict, alert_data: dict) -> str:
relation_dict = {}
for relation in topology_data["childRelations"]:
p = relation['parentService']
c = relation['service']
parent_node = f'{p}_#"{relation['parentEndpoint']}"'
child_node = f'{c}_#"{relation['endpoint']}"'
if parent_node not in relation_dict:
relation_dict[parent_node] = []
relation_dict[parent_node].append(child_node)
text = []
root_nodes = set(relation_dict.keys()) - {child for children in relation_dict.values() for child in children}
for root in root_nodes:
self._build_tree(root, 0, relation_dict, text, alert_data)
res = "".join(text)
return res

@ -0,0 +1,40 @@
identity:
name: alert_associate
author: APO
label:
en_US: alert_associate
zh_Hans: 关联拓扑和告警数据
pt_BR: alert_associate
description:
human:
en_US: A tool for alert_associate
zh_Hans: 关联拓扑和告警数据
pt_BR: A tool for alert_associate
llm: A tool for alert_associate
parameters:
- name: topology_data
type: string
required: true
label:
en_US: topology_data
zh_Hans: 拓扑数据
pt_BR: topology_data
human_description:
en_US: topology_data
zh_Hans: 拓扑数据
pt_BR: topology_data
llm_description: topology data
form: llm
- name: alert_data
type: string
required: false
label:
en_US: alert_data
zh_Hans: 告警事件数据
pt_BR: alert_data
human_description:
en_US: alert_data
zh_Hans: 告警事件数据
pt_BR: alert_data
llm_description: alert event data
form: llm

@ -0,0 +1,31 @@
import json
from collections.abc import Generator
from typing import Any, Optional
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class ThresholdTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
metric = tool_parameters.get('metric_data')
metric_data = json.loads(metric)
threshold = float(tool_parameters.get('threshold'))
res = {}
for k, v in metric_data['data'].items():
if v > threshold:
res[str(k)] = v
res = json.dumps({
"type": 'llm',
"display": False,
"data": res
})
yield self.create_text_message(res)

@ -0,0 +1,40 @@
identity:
name: threshold
author: APO
label:
en_US: threshold
zh_Hans: 阈值判定
pt_BR: threshold
description:
human:
en_US: A tool for getting the current time
zh_Hans: 阈值判定
pt_BR: A tool for getting the current time
llm: A tool for getting the current time
parameters:
- name: metric_data
type: string
required: true
label:
en_US: metric_data
zh_Hans: 指标数据
pt_BR: metric_data
human_description:
en_US: Time format in strftime standard
zh_Hans: 时序指标数据
pt_BR: Time format in strftime standard
llm_description: metric type data
form: llm
- name: threshold
type: string
required: false
label:
en_US: threshold
zh_Hans: 阈值
pt_BR: threshold
human_description:
en_US: Time format in strftime standard
zh_Hans: 阈值判定
pt_BR: Time format in strftime standard
llm_description: data threshold judgement
form: llm

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.666992 8.00008C0.666992 3.94999 3.95024 0.666748 8.00033 0.666748C12.0504 0.666748 15.3337 3.94999 15.3337 8.00008C15.3337 12.0502 12.0504 15.3334 8.00033 15.3334C3.95024 15.3334 0.666992 12.0502 0.666992 8.00008ZM8.66699 4.00008C8.66699 3.63189 8.36852 3.33341 8.00033 3.33341C7.63213 3.33341 7.33366 3.63189 7.33366 4.00008V8.00008C7.33366 8.2526 7.47633 8.48344 7.70218 8.59637L10.3688 9.9297C10.6982 10.0944 11.0986 9.96088 11.2633 9.63156C11.4279 9.30224 11.2945 8.90179 10.9651 8.73713L8.66699 7.58806V4.00008Z" fill="#EC4A0A"/>
</svg>

After

Width:  |  Height:  |  Size: 691 B

@ -0,0 +1,8 @@
from typing import Any
from core.tools.builtin_tool.provider import BuiltinToolProviderController
class APORuleProvider(BuiltinToolProviderController):
def _validate_credentials(self, user_id: str, credentials: dict[str, Any]) -> None:
pass

@ -0,0 +1,14 @@
identity:
author: APO
name: apo_rule
label:
en_US: APORule
zh_Hans: APO规则执行
pt_BR: APOSelect
description:
en_US: APO execute rule.
zh_Hans: APO规则执行
pt_BR: APO execute rule.
icon: icon.svg
tags:
- utilities

@ -0,0 +1,29 @@
from collections.abc import Generator
from typing import Any, Optional
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class AlertRuleTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
"""
invoke tools
"""
# data = tool_parameters.get("data")
rule = tool_parameters.get("rule")
# res = f'{rule}\n{data}'
# list = json.dumps({
# 'type': 'alert',
# 'display': False,
# 'data': res,
# })
yield self.create_text_message(rule)

@ -0,0 +1,40 @@
identity:
name: alert_rule
author: APO
label:
en_US: alert_rule
zh_Hans: 告警规则执行
pt_BR: alert_rule
description:
human:
en_US: alert rule execute
zh_Hans: 告警规则执行
pt_BR: alert rule execute
llm: alert rule execute
parameters:
# - name: data
# type: string
# required: false
# label:
# en_US: data
# zh_Hans: 需要大模型分析的数据
# pt_BR: data
# human_description:
# en_US: llm analysis data
# zh_Hans: 大模型分析数据
# pt_BR: llm analysis data
# llm_description: llm analysis data
# form: llm
- name: rule
type: string
required: false
label:
en_US: rule
zh_Hans: 自然语言描述的规则
pt_BR: rule
human_description:
en_US: rule
zh_Hans: 自然语言描述的规则
pt_BR: rule
llm_description: rule
form: llm

@ -0,0 +1,53 @@
import json
from collections.abc import Generator
from typing import Any, Optional
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class RootCauseTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
"""
invoke tools
"""
llm_text = tool_parameters.get("llm_text")
node_lists, text = self._get_node_list(llm_text)
yield self.create_text_message(text)
yield self.create_json_message({'nodelists': node_lists})
def _get_node_list(self, data: str) -> tuple[list, str]:
data = data.strip('```')
data = data.strip('json')
text = ""
try:
nodeinfo = json.loads(data)
nodeList = []
if "nodeName" in nodeinfo:
service = nodeinfo["nodeName"].split('_#')[0]
ep = nodeinfo["nodeName"].split('_#')[1]
endpoint = ep.strip('"')
endpoint = endpoint.strip("'")
nodeList.append({'service': service, 'endpoint': endpoint, "isRoot": True})
text = text + f'造成根因的节点是{nodeinfo['nodeName']}\n'
if "otherNodeName" in data:
for node in nodeinfo["otherNodeName"]:
tmpservice = node.split('_#')[0]
ep = node.split('_#')[1]
tmpendpoint = ep.strip('"')
tmpendpoint = tmpendpoint.strip("'")
nodeList.append({'service': tmpservice, 'endpoint': tmpendpoint, "isRoot": False})
otherstr = ",".join(nodeinfo["otherNodeName"])
text = text + f'疑似根因节点有{otherstr}\n'
return nodeList, text
except Exception as e:
return [], ""

@ -0,0 +1,27 @@
identity:
name: root_cause
author: APO
label:
en_US: root_cause
zh_Hans: 提取根因节点
pt_BR: root_cause
description:
human:
en_US: get root cause
zh_Hans: 获取根因节点
pt_BR: get root cause
llm: get root cause node
parameters:
- name: llm_text
type: string
required: false
label:
en_US: llm_text
zh_Hans: 大模型返回结果
pt_BR: llm_text
human_description:
en_US: llm_text
zh_Hans: 大模型返回结果
pt_BR: llm_text
llm_description: rule
form: llm

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.666992 8.00008C0.666992 3.94999 3.95024 0.666748 8.00033 0.666748C12.0504 0.666748 15.3337 3.94999 15.3337 8.00008C15.3337 12.0502 12.0504 15.3334 8.00033 15.3334C3.95024 15.3334 0.666992 12.0502 0.666992 8.00008ZM8.66699 4.00008C8.66699 3.63189 8.36852 3.33341 8.00033 3.33341C7.63213 3.33341 7.33366 3.63189 7.33366 4.00008V8.00008C7.33366 8.2526 7.47633 8.48344 7.70218 8.59637L10.3688 9.9297C10.6982 10.0944 11.0986 9.96088 11.2633 9.63156C11.4279 9.30224 11.2945 8.90179 10.9651 8.73713L8.66699 7.58806V4.00008Z" fill="#EC4A0A"/>
</svg>

After

Width:  |  Height:  |  Size: 691 B

@ -0,0 +1,8 @@
from typing import Any
from core.tools.builtin_tool.provider import BuiltinToolProviderController
class APOSelectProvider(BuiltinToolProviderController):
def _validate_credentials(self, user_id: str, credentials: dict[str, Any]) -> None:
pass

@ -0,0 +1,14 @@
identity:
author: APO
name: apo_select
label:
en_US: APOSelect
zh_Hans: APO查询数据
pt_BR: APOSelect
description:
en_US: APO query data.
zh_Hans: APO查询数据的工具。
pt_BR: APO query data.
icon: icon.svg
tags:
- utilities

@ -0,0 +1,74 @@
import json
from collections.abc import Generator
from typing import Any, Optional
import requests
from configs import dify_config
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class AlertTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
service = tool_parameters.get("service")
endpoint = tool_parameters.get("endpoint")
start_time = tool_parameters.get("start_time")
end_time = tool_parameters.get("end_time")
params = {
'service': service,
'endpoint': endpoint,
'startTime': start_time,
'endTime': end_time,
'anormalTypes': "app,container,infra,network,error,appInstance",
'deltaStartTime': start_time,
'deltaEndTime': end_time,
'step': self.get_step(start_time, end_time),
}
resp = requests.post(dify_config.APO_BACKEND_URL + '/api/alerts/descendant/anormal/delta', json=params)
list = resp.json()
list = json.dumps({
'type': 'alert',
'display': True,
'data': list,
})
yield self.create_text_message(list)
def get_step(self, start_time, end_time):
time_diff = end_time - start_time
SECOND = 1000000 # microseconds
MINUTE = 60 * SECOND
HOUR = 60 * MINUTE
step = SECOND # default step is 1 second
if time_diff <= 15 * MINUTE:
step = 30 * SECOND
elif time_diff <= 30 * MINUTE:
step = 1 * MINUTE
elif time_diff <= 1 * HOUR:
step = 2 * MINUTE
elif time_diff <= 1.5 * HOUR:
step = 3 * MINUTE
elif time_diff <= 3 * HOUR:
step = 6 * MINUTE
elif time_diff <= 6 * HOUR:
step = 12 * MINUTE
elif time_diff <= 12 * HOUR:
step = 24 * MINUTE
elif time_diff <= 15 * HOUR:
step = 30 * MINUTE
elif time_diff <= 30 * HOUR:
step = 1 * HOUR
else:
step = ((time_diff + 30 * SECOND - 1) // (30 * SECOND)) * SECOND
return step

@ -0,0 +1,66 @@
identity:
name: alert
author: APO
label:
en_US: toplogy
zh_Hans: 获取当前入口拓扑的告警事件
pt_BR: toplogy
description:
human:
en_US: A tool for getting the current time.
zh_Hans: 获取当前入口拓扑的告警事件
pt_BR: A tool for getting the current time.
llm: A tool for getting the current time.
parameters:
- name: service
type: string
required: true
label:
en_US: service
zh_Hans: 服务名
pt_BR: service
human_description:
en_US: Time format in strftime standard.
zh_Hans: 拓扑入口服务名
pt_BR: Time format in strftime standard.
llm_description: toplogy service name
form: llm
- name: endpoint
type: string
required: true
label:
en_US: endpoint
zh_Hans: 服务端点
pt_BR: endpoint
human_description:
en_US: Time format in strftime standard.
zh_Hans: 拓扑入口服务端点
pt_BR: Time format in strftime standard.
llm_description: toplogy endpoint
form: llm
- name: start_time
type: number
required: false
label:
en_US: start_time
zh_Hans: 开始时间
pt_BR: start_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 查询开始时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: end_time
type: number
required: false
label:
en_US: end_time
zh_Hans: 结束时间
pt_BR: end_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询的结束时间
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm

@ -0,0 +1,42 @@
import json
from collections.abc import Generator
from typing import Any, Optional
import requests
from configs import dify_config
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class SelectContainerCPUTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
container_name = tool_parameters.get("container_name")
start_time = tool_parameters.get("start_time")
end_time = tool_parameters.get("end_time")
pmql = 'rate(container_cpu_usage_seconds_total{container="' + container_name + '"}[1m])'
params = {
'query': pmql,
'start': start_time,
'end': end_time,
'step': '1m'
}
url = dify_config.APO_VM_URL + "/api/v1/query_range"
resp = requests.get(url, params=params)
list = resp.json()['data']['result'][0]
res = {}
for item in list['values']:
res[str(item[0] * 1000)] = float(item[1])
list = json.dumps({
'type': 'cpu',
'display': True,
'data': res,
})
yield self.create_text_message(list)

@ -0,0 +1,53 @@
identity:
name: select_container_cpu
author: APO
label:
en_US: select_container_cpu
zh_Hans: 查询容器CPU占用
pt_BR: select_container_cpu
description:
human:
en_US: A tool for getting container cpu.
zh_Hans: 查询容器CPU占用
pt_BR: A tool for getting container cpu.
llm: A tool for getting container cpu.
parameters:
- name: container_name
type: string
required: true
label:
en_US: container_name
zh_Hans: 容器名
pt_BR: container_name
human_description:
en_US: Time format in strftime standard.
zh_Hans: 查询指定Pod的CPU占用。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: start_time
type: number
required: false
label:
en_US: start_time
zh_Hans: 开始时间
pt_BR: start_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的开始时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: end_time
type: number
required: false
label:
en_US: end_time
zh_Hans: 结束时间
pt_BR: end_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的结束时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm

@ -0,0 +1,41 @@
import json
from collections.abc import Generator
from typing import Any, Optional
import requests
from configs import dify_config
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class SelectContainerRSSTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
container_name = tool_parameters.get("container_name")
start_time = tool_parameters.get("start_time")
end_time = tool_parameters.get("end_time")
pmql = 'container_memory_rss{container="' + container_name + '"}'
params = {
'query': pmql,
'start': start_time,
'end': end_time,
'step': '1m'
}
resp = requests.get(dify_config.APO_VM_URL + '/api/v1/query_range', params=params)
list = resp.json()['data']['result'][0]
res = {}
for item in list['values']:
res[str(item[0] * 1000)] = float(item[1])
list = json.dumps({
'type': 'memory',
'display': True,
'data': res,
})
yield self.create_text_message(list)

@ -0,0 +1,53 @@
identity:
name: select_container_rss
author: APO
label:
en_US: select_container_rss
zh_Hans: 查询容器驻留内存
pt_BR: select_container_rss
description:
human:
en_US: A tool for getting container rss.
zh_Hans: 查询容器CPU占用
pt_BR: A tool for getting container rss.
llm: A tool for getting container rss.
parameters:
- name: container_name
type: string
required: true
label:
en_US: container_name
zh_Hans: 容器名
pt_BR: container_name
human_description:
en_US: Time format in strftime standard.
zh_Hans: 查询指定Pod的CPU占用。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: start_time
type: number
required: false
label:
en_US: start_time
zh_Hans: 开始时间
pt_BR: start_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的开始时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: end_time
type: number
required: false
label:
en_US: end_time
zh_Hans: 结束时间
pt_BR: end_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的结束时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm

@ -0,0 +1,42 @@
import json
from collections.abc import Generator
from typing import Any, Optional
import requests
from configs import dify_config
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class FaultLogTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
service = tool_parameters.get("service")
start_time = tool_parameters.get("start_time")
end_time = tool_parameters.get("end_time")
params = {
'service': [service],
'startTime': start_time,
'endTime': end_time,
'pageNum': 1,
'pageSize': 10
}
url = dify_config.APO_BACKEND_URL + "/api/log/fault/pagelist"
resp = requests.post(url, json=params)
list = resp.json()['list'][0]
content_url = dify_config.APO_BACKEND_URL + "/api/log/fault/content"
content = requests.post(content_url, json=list).json()
list = json.dumps({
'type': 'log',
'display': True,
'data': content,
})
yield self.create_text_message(list)

@ -0,0 +1,53 @@
identity:
name: fault_log
author: APO
label:
en_US: fault_log
zh_Hans: 获取故障现场日志
pt_BR: fault_log
description:
human:
en_US: A tool for getting fault log.
zh_Hans: 查询容器CPU占用
pt_BR: A tool for getting fault log.
llm: A tool for getting fault log.
parameters:
- name: service
type: string
required: true
label:
en_US: service
zh_Hans: 服务名
pt_BR: service
human_description:
en_US: Time format in strftime standard.
zh_Hans: 查询指定Pod的CPU占用。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: start_time
type: number
required: false
label:
en_US: start_time
zh_Hans: 开始时间
pt_BR: start_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的开始时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: end_time
type: number
required: false
label:
en_US: end_time
zh_Hans: 结束时间
pt_BR: end_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的结束时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm

@ -0,0 +1,88 @@
import json
from collections.abc import Generator
from typing import Any, Optional
import requests
from configs import dify_config
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class FaultLogTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
service = tool_parameters.get("service")
endpoint = tool_parameters.get("endpoint")
entryService = tool_parameters.get("entryService")
entryEndpoint = tool_parameters.get("entryEndpoint")
start_time = tool_parameters.get("start_time")
end_time = tool_parameters.get("end_time")
type = tool_parameters.get("type")
params = {
"anormalTypes": "app,container,infra,network,error,appInstance",
"endTime": end_time,
"endpoint": endpoint,
"entryEndpoint": entryEndpoint,
"entryService": entryService,
"service": service,
"startTime": start_time,
"step": self.get_step(start_time, end_time),
"type": [type],
"withTopology": True,
}
url = "http://192.168.1.6:13680/api/nodeinfo"
res = {}
with requests.post(url, json=params, stream=True) as response:
# 逐行读取流式响应
for line in response.iter_lines():
if line: # 过滤空行
# 解码字节为字符串
decoded_line = line.decode('utf-8')
res = json.loads(decoded_line[5:].strip())
list = json.dumps({
'type': 'nodeinfo',
'display': True,
'data': res,
})
yield self.create_text_message(list)
def get_step(self, start_time, end_time):
time_diff = end_time - start_time
SECOND = 1000000 # microseconds
MINUTE = 60 * SECOND
HOUR = 60 * MINUTE
step = SECOND # default step is 1 second
if time_diff <= 15 * MINUTE:
step = 30 * SECOND
elif time_diff <= 30 * MINUTE:
step = 1 * MINUTE
elif time_diff <= 1 * HOUR:
step = 2 * MINUTE
elif time_diff <= 1.5 * HOUR:
step = 3 * MINUTE
elif time_diff <= 3 * HOUR:
step = 6 * MINUTE
elif time_diff <= 6 * HOUR:
step = 12 * MINUTE
elif time_diff <= 12 * HOUR:
step = 24 * MINUTE
elif time_diff <= 15 * HOUR:
step = 30 * MINUTE
elif time_diff <= 30 * HOUR:
step = 1 * HOUR
else:
step = ((time_diff + 30 * SECOND - 1) // (30 * SECOND)) * SECOND
return step

@ -0,0 +1,105 @@
identity:
name: node_info
author: APO
label:
en_US: node_info
zh_Hans: 获取节点信息
pt_BR: node_info
description:
human:
en_US: A tool for getting fault log.
zh_Hans: 获取节点信息
pt_BR: A tool for getting fault log.
llm: A tool for getting fault log.
parameters:
- name: entryService
type: string
required: true
label:
en_US: entryService
zh_Hans: 入口服务名
pt_BR: entryService
human_description:
en_US: Time format in strftime standard.
zh_Hans: 拓扑入口服务名
pt_BR: Time format in strftime standard.
llm_description: toplogy service name
form: llm
- name: entryEndpoint
type: string
required: true
label:
en_US: entryEndpoint
zh_Hans: 入口服务端点
pt_BR: entryEndpoint
human_description:
en_US: Time format in strftime standard.
zh_Hans: 拓扑入口服务端点
pt_BR: Time format in strftime standard.
llm_description: toplogy endpoint
form: llm
- name: service
type: string
required: true
label:
en_US: service
zh_Hans: 服务名
pt_BR: service
human_description:
en_US: Time format in strftime standard.
zh_Hans: 查询指定服务的名称
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: endpoint
type: string
required: true
label:
en_US: endpoint
zh_Hans: 服务端点
pt_BR: endpoint
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定服务的端点
pt_BR: Time format in strftime standard.
llm_description: toplogy endpoint
form: llm
- name: start_time
type: number
required: false
label:
en_US: start_time
zh_Hans: 开始时间
pt_BR: start_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的开始时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: end_time
type: number
required: false
label:
en_US: end_time
zh_Hans: 结束时间
pt_BR: end_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的结束时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: type
type: string
required: false
label:
en_US: type
zh_Hans: 查询数据类型(log, slow, error)
pt_BR: type
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询数据类型(log, slow, error)。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm

@ -0,0 +1,42 @@
import json
from collections.abc import Generator
from typing import Any, Optional
import requests
from configs import dify_config
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class SelectCPUTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
node_name = tool_parameters.get("node_name")
start_time = tool_parameters.get("start_time")
end_time = tool_parameters.get("end_time")
sumql = 'sum by (instance_name) (avg by (mode, instance_name)'
pmql = sumql + ' (rate(node_cpu_seconds_total{mode!="idle", instance_name="' + node_name + '"}[1m])))'
params = {
'query': pmql,
'start': start_time,
'end': end_time,
'step': '1m'
}
resp = requests.get(dify_config.APO_VM_URL + '/api/v1/query_range', params=params)
list = resp.json()['data']['result'][0]
res = {}
for item in list['values']:
res[str(item[0] * 1000)] = float(item[1])
list = json.dumps({
'type': 'cpu',
'display': True,
'data': res,
})
yield self.create_text_message(list)

@ -0,0 +1,53 @@
identity:
name: select_cpu
author: APO
label:
en_US: select_cpu
zh_Hans: 查询CPU主机指标
pt_BR: select_cpu
description:
human:
en_US: A tool for getting the current time.
zh_Hans: 查询CPU主机指标
pt_BR: A tool for getting the current time.
llm: A tool for getting the current time.
parameters:
- name: node_name
type: string
required: true
label:
en_US: node_name
zh_Hans: 主机名
pt_BR: node_name
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU所在的主机名。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: start_time
type: number
required: false
label:
en_US: start_time
zh_Hans: 开始时间
pt_BR: start_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的开始时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: end_time
type: number
required: false
label:
en_US: end_time
zh_Hans: 结束时间
pt_BR: end_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询CPU指标的结束时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm

@ -0,0 +1,42 @@
import json
from collections.abc import Generator
from typing import Any, Optional
import requests
from configs import dify_config
from core.tools.builtin_tool.tool import BuiltinTool
from core.tools.entities.tool_entities import ToolInvokeMessage
class TopologyTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: Optional[str] = None,
app_id: Optional[str] = None,
message_id: Optional[str] = None,
) -> Generator[ToolInvokeMessage, None, None]:
service = tool_parameters.get("service")
endpoint = tool_parameters.get("endpoint")
start_time = tool_parameters.get("start_time")
end_time = tool_parameters.get("end_time")
params = {
'service': service,
'endpoint': endpoint,
'startTime': start_time,
'endTime': end_time,
'entryService': service,
'entryEndpoint': endpoint,
'withTopology': True,
'removeClientCall': True,
}
resp = requests.get(dify_config.APO_BACKEND_URL + '/api/service/relation', params=params)
list = resp.json()
list = json.dumps({
'type': 'topology',
'display': True,
'data': list,
})
yield self.create_text_message(list)

@ -0,0 +1,66 @@
identity:
name: topology
author: APO
label:
en_US: topology
zh_Hans: 获取当前入口的拓扑结构
pt_BR: topology
description:
human:
en_US: A tool for getting the current time.
zh_Hans: 获取当前入口的拓扑结构
pt_BR: A tool for getting the current time.
llm: A tool for getting the current time.
parameters:
- name: service
type: string
required: true
label:
en_US: service
zh_Hans: 服务名
pt_BR: service
human_description:
en_US: Time format in strftime standard.
zh_Hans: 拓扑入口服务名
pt_BR: Time format in strftime standard.
llm_description: toplogy service name
form: llm
- name: endpoint
type: string
required: true
label:
en_US: endpoint
zh_Hans: 服务端点
pt_BR: endpoint
human_description:
en_US: Time format in strftime standard.
zh_Hans: 拓扑入口服务端点
pt_BR: Time format in strftime standard.
llm_description: toplogy endpoint
form: llm
- name: start_time
type: number
required: false
label:
en_US: start_time
zh_Hans: 开始时间
pt_BR: start_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 查询开始时间。
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm
- name: end_time
type: number
required: false
label:
en_US: end_time
zh_Hans: 结束时间
pt_BR: end_time
human_description:
en_US: Time format in strftime standard.
zh_Hans: 指定查询的结束时间
pt_BR: Time format in strftime standard.
llm_description: Time format in strftime standard.
form: llm

@ -2,6 +2,7 @@ import json
import logging
from pathlib import Path
from rapidfuzz import process
from sqlalchemy.orm import Session
from configs import dify_config
@ -286,6 +287,68 @@ class BuiltinToolManageService:
raise e
return BuiltinToolProviderSort.sort(result)
@staticmethod
def list_apo_tools(user_id: str, tenant_id: str, tool_type: str, query: str|None) -> list[ToolProviderApiEntity]:
"""
list apo tools
"""
# get all builtin providers
provider_controllers = ToolManager.list_builtin_providers(tenant_id)
with db.session.no_autoflush:
# get all user added providers
db_providers: list[BuiltinToolProvider] = (
db.session.query(BuiltinToolProvider).filter(BuiltinToolProvider.tenant_id == tenant_id).all() or []
)
# rewrite db_providers
for db_provider in db_providers:
db_provider.provider = str(ToolProviderID(db_provider.provider))
# find provider
def find_provider(provider):
return next(filter(lambda db_provider: db_provider.provider == provider, db_providers), None)
result: list[ToolProviderApiEntity] = []
for provider_controller in provider_controllers:
try:
# handle include, exclude
if is_filtered(
include_set=dify_config.POSITION_TOOL_INCLUDES_SET, # type: ignore
exclude_set=dify_config.POSITION_TOOL_EXCLUDES_SET, # type: ignore
data=provider_controller,
name_func=lambda x: x.identity.name,
):
continue
# convert provider controller to user provider
user_builtin_provider = ToolTransformService.builtin_provider_to_user_provider(
provider_controller=provider_controller,
db_provider=find_provider(provider_controller.entity.identity.name),
decrypt_credentials=True,
)
# add icon
ToolTransformService.repack_provider(tenant_id=tenant_id, provider=user_builtin_provider)
tools = provider_controller.get_tools()
for tool in tools or []:
user_builtin_provider.tools.append(
ToolTransformService.convert_tool_entity_to_api_entity(
tenant_id=tenant_id,
tool=tool,
credentials=user_builtin_provider.original_credentials,
labels=ToolLabelManager.get_tool_labels(provider_controller),
)
)
result.append(user_builtin_provider)
except Exception as e:
raise e
return BuiltinToolProviderSort.sort(result)
@staticmethod
def _fetch_builtin_provider(provider_name: str, tenant_id: str) -> BuiltinToolProvider | None:

Loading…
Cancel
Save