diff --git a/.github/workflows/api-tests.yml b/.github/workflows.bak/api-tests.yml
similarity index 100%
rename from .github/workflows/api-tests.yml
rename to .github/workflows.bak/api-tests.yml
diff --git a/.github/workflows/build-push.yml b/.github/workflows.bak/build-push.yml
similarity index 100%
rename from .github/workflows/build-push.yml
rename to .github/workflows.bak/build-push.yml
diff --git a/.github/workflows/db-migration-test.yml b/.github/workflows.bak/db-migration-test.yml
similarity index 100%
rename from .github/workflows/db-migration-test.yml
rename to .github/workflows.bak/db-migration-test.yml
diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows.bak/deploy-dev.yml
similarity index 100%
rename from .github/workflows/deploy-dev.yml
rename to .github/workflows.bak/deploy-dev.yml
diff --git a/.github/workflows/docker-build.yml b/.github/workflows.bak/docker-build.yml
similarity index 100%
rename from .github/workflows/docker-build.yml
rename to .github/workflows.bak/docker-build.yml
diff --git a/.github/workflows/expose_service_ports.sh b/.github/workflows.bak/expose_service_ports.sh
similarity index 100%
rename from .github/workflows/expose_service_ports.sh
rename to .github/workflows.bak/expose_service_ports.sh
diff --git a/.github/workflows/stale.yml b/.github/workflows.bak/stale.yml
similarity index 100%
rename from .github/workflows/stale.yml
rename to .github/workflows.bak/stale.yml
diff --git a/.github/workflows/style.yml b/.github/workflows.bak/style.yml
similarity index 100%
rename from .github/workflows/style.yml
rename to .github/workflows.bak/style.yml
diff --git a/.github/workflows/tool-test-sdks.yaml b/.github/workflows.bak/tool-test-sdks.yaml
similarity index 100%
rename from .github/workflows/tool-test-sdks.yaml
rename to .github/workflows.bak/tool-test-sdks.yaml
diff --git a/.github/workflows/translate-i18n-base-on-english.yml b/.github/workflows.bak/translate-i18n-base-on-english.yml
similarity index 100%
rename from .github/workflows/translate-i18n-base-on-english.yml
rename to .github/workflows.bak/translate-i18n-base-on-english.yml
diff --git a/.github/workflows/vdb-tests.yml b/.github/workflows.bak/vdb-tests.yml
similarity index 100%
rename from .github/workflows/vdb-tests.yml
rename to .github/workflows.bak/vdb-tests.yml
diff --git a/.github/workflows/web-tests.yml b/.github/workflows.bak/web-tests.yml
similarity index 100%
rename from .github/workflows/web-tests.yml
rename to .github/workflows.bak/web-tests.yml
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000000..f7100d71dd
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -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 }}
\ No newline at end of file
diff --git a/api/.env.example b/api/.env.example
index 39eb7ad766..6880e682e5 100644
--- a/api/.env.example
+++ b/api/.env.example
@@ -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
diff --git a/api/configs/apo/__init__.py b/api/configs/apo/__init__.py
new file mode 100644
index 0000000000..7d21a27594
--- /dev/null
+++ b/api/configs/apo/__init__.py
@@ -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",
+ )
\ No newline at end of file
diff --git a/api/configs/app_config.py b/api/configs/app_config.py
index ac1ce9db10..3c99d3ae4e 100644
--- a/api/configs/app_config.py
+++ b/api/configs/app_config.py
@@ -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
diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py
index 39ab454922..4816f52396 100644
--- a/api/controllers/console/workspace/tool_providers.py
+++ b/api/controllers/console/workspace/tool_providers.py
@@ -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")
diff --git a/api/core/tools/builtin_tool/providers/apo_analysis/_assets/icon.svg b/api/core/tools/builtin_tool/providers/apo_analysis/_assets/icon.svg
new file mode 100644
index 0000000000..6d7118aed9
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_analysis/_assets/icon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_analysis/apo_analysis.py b/api/core/tools/builtin_tool/providers/apo_analysis/apo_analysis.py
new file mode 100644
index 0000000000..641f82b8c9
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_analysis/apo_analysis.py
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_analysis/apo_analysis.yaml b/api/core/tools/builtin_tool/providers/apo_analysis/apo_analysis.yaml
new file mode 100644
index 0000000000..359ba38799
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_analysis/apo_analysis.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_analysis/tools/alert_ass.py b/api/core/tools/builtin_tool/providers/apo_analysis/tools/alert_ass.py
new file mode 100644
index 0000000000..a6b40707f8
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_analysis/tools/alert_ass.py
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_analysis/tools/alert_ass.yaml b/api/core/tools/builtin_tool/providers/apo_analysis/tools/alert_ass.yaml
new file mode 100644
index 0000000000..0bb7449aeb
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_analysis/tools/alert_ass.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_analysis/tools/threshold.py b/api/core/tools/builtin_tool/providers/apo_analysis/tools/threshold.py
new file mode 100644
index 0000000000..3bb4c97ff9
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_analysis/tools/threshold.py
@@ -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)
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_analysis/tools/threshold.yaml b/api/core/tools/builtin_tool/providers/apo_analysis/tools/threshold.yaml
new file mode 100644
index 0000000000..e577fbd53a
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_analysis/tools/threshold.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_rule/_assets/icon.svg b/api/core/tools/builtin_tool/providers/apo_rule/_assets/icon.svg
new file mode 100644
index 0000000000..6d7118aed9
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_rule/_assets/icon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_rule/apo_rule.py b/api/core/tools/builtin_tool/providers/apo_rule/apo_rule.py
new file mode 100644
index 0000000000..5dd3201ebc
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_rule/apo_rule.py
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_rule/apo_rule.yaml b/api/core/tools/builtin_tool/providers/apo_rule/apo_rule.yaml
new file mode 100644
index 0000000000..506fd95df5
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_rule/apo_rule.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_rule/tools/alert_rule.py b/api/core/tools/builtin_tool/providers/apo_rule/tools/alert_rule.py
new file mode 100644
index 0000000000..01f42976db
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_rule/tools/alert_rule.py
@@ -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)
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_rule/tools/alert_rule.yaml b/api/core/tools/builtin_tool/providers/apo_rule/tools/alert_rule.yaml
new file mode 100644
index 0000000000..0cdc6f5c7c
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_rule/tools/alert_rule.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_rule/tools/root_cause.py b/api/core/tools/builtin_tool/providers/apo_rule/tools/root_cause.py
new file mode 100644
index 0000000000..d122e19982
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_rule/tools/root_cause.py
@@ -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 [], ""
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_rule/tools/root_cause.yaml b/api/core/tools/builtin_tool/providers/apo_rule/tools/root_cause.yaml
new file mode 100644
index 0000000000..0916bf6ae3
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_rule/tools/root_cause.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/_assets/icon.svg b/api/core/tools/builtin_tool/providers/apo_select/_assets/icon.svg
new file mode 100644
index 0000000000..6d7118aed9
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/_assets/icon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/apo_select.py b/api/core/tools/builtin_tool/providers/apo_select/apo_select.py
new file mode 100644
index 0000000000..029fd388ae
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/apo_select.py
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/apo_select.yaml b/api/core/tools/builtin_tool/providers/apo_select/apo_select.yaml
new file mode 100644
index 0000000000..84e9300758
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/apo_select.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/alert.py b/api/core/tools/builtin_tool/providers/apo_select/tools/alert.py
new file mode 100644
index 0000000000..f465bf0539
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/alert.py
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/alert.yaml b/api/core/tools/builtin_tool/providers/apo_select/tools/alert.yaml
new file mode 100644
index 0000000000..7d66bc07a8
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/alert.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/container_cpu.py b/api/core/tools/builtin_tool/providers/apo_select/tools/container_cpu.py
new file mode 100644
index 0000000000..e522974be8
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/container_cpu.py
@@ -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)
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/container_cpu.yaml b/api/core/tools/builtin_tool/providers/apo_select/tools/container_cpu.yaml
new file mode 100644
index 0000000000..9521f29f3c
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/container_cpu.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/container_rss.py b/api/core/tools/builtin_tool/providers/apo_select/tools/container_rss.py
new file mode 100644
index 0000000000..b31891a362
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/container_rss.py
@@ -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)
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/container_rss.yaml b/api/core/tools/builtin_tool/providers/apo_select/tools/container_rss.yaml
new file mode 100644
index 0000000000..715a1e7769
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/container_rss.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/fault_log.py b/api/core/tools/builtin_tool/providers/apo_select/tools/fault_log.py
new file mode 100644
index 0000000000..a32231a0b0
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/fault_log.py
@@ -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)
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/fault_log.yaml b/api/core/tools/builtin_tool/providers/apo_select/tools/fault_log.yaml
new file mode 100644
index 0000000000..dc94a14584
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/fault_log.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/node_info.py b/api/core/tools/builtin_tool/providers/apo_select/tools/node_info.py
new file mode 100644
index 0000000000..98ab5858c9
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/node_info.py
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/node_info.yaml b/api/core/tools/builtin_tool/providers/apo_select/tools/node_info.yaml
new file mode 100644
index 0000000000..d14ef21453
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/node_info.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/select_cpu.py b/api/core/tools/builtin_tool/providers/apo_select/tools/select_cpu.py
new file mode 100644
index 0000000000..43697a0e5a
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/select_cpu.py
@@ -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)
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/select_cpu.yaml b/api/core/tools/builtin_tool/providers/apo_select/tools/select_cpu.yaml
new file mode 100644
index 0000000000..b662bff628
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/select_cpu.yaml
@@ -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
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/topology.py b/api/core/tools/builtin_tool/providers/apo_select/tools/topology.py
new file mode 100644
index 0000000000..e78427627f
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/topology.py
@@ -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)
\ No newline at end of file
diff --git a/api/core/tools/builtin_tool/providers/apo_select/tools/topology.yaml b/api/core/tools/builtin_tool/providers/apo_select/tools/topology.yaml
new file mode 100644
index 0000000000..99d697a328
--- /dev/null
+++ b/api/core/tools/builtin_tool/providers/apo_select/tools/topology.yaml
@@ -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
\ No newline at end of file
diff --git a/api/services/tools/builtin_tools_manage_service.py b/api/services/tools/builtin_tools_manage_service.py
index 51b56ab586..d0ec100e58 100644
--- a/api/services/tools/builtin_tools_manage_service.py
+++ b/api/services/tools/builtin_tools_manage_service.py
@@ -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: