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: