From 3e8f47fd9715880de9ec7cc3d5d01c9d0a28a426 Mon Sep 17 00:00:00 2001 From: fdc310 <82008029+fdc310@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:44:09 +0800 Subject: [PATCH] feat: judge and send runner category (local or cloud) for telemetry * feat(chat): add runner_url to payload for telemetry tracking * feat(telemetry): add runner_url to sanitized fields in telemetry payload * feat(telemetry): replace runner_url with runner_category in telemetry payload and add runner utility functions * fix:ruff --- .../pkg/pipeline/process/handlers/chat.py | 7 +- src/langbot/pkg/telemetry/telemetry.py | 2 +- src/langbot/pkg/utils/runner.py | 105 ++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/langbot/pkg/utils/runner.py diff --git a/src/langbot/pkg/pipeline/process/handlers/chat.py b/src/langbot/pkg/pipeline/process/handlers/chat.py index 242fc78c..7e130b04 100644 --- a/src/langbot/pkg/pipeline/process/handlers/chat.py +++ b/src/langbot/pkg/pipeline/process/handlers/chat.py @@ -12,7 +12,7 @@ from ... import entities from ....provider import runner as runner_module import langbot_plugin.api.entities.events as events -from ....utils import importutil, constants +from ....utils import importutil, constants, runner as runner_utils from ....provider import runners import langbot_plugin.api.entities.builtin.provider.session as provider_session import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query @@ -185,10 +185,15 @@ class ChatMessageHandler(handler.MessageHandler): pipeline_plugins = query.variables.get('_pipeline_bound_plugins', None) + runner_category = runner_utils.get_runner_category_from_runner( + runner_name, runner, query.pipeline_config + ) + payload = { 'query_id': query.query_id, 'adapter': adapter_name, 'runner': runner_name, + 'runner_category': runner_category, 'duration_ms': duration_ms, 'model_name': model_name, 'version': constants.semantic_version, diff --git a/src/langbot/pkg/telemetry/telemetry.py b/src/langbot/pkg/telemetry/telemetry.py index b78a3dbe..d0849a3d 100644 --- a/src/langbot/pkg/telemetry/telemetry.py +++ b/src/langbot/pkg/telemetry/telemetry.py @@ -60,7 +60,7 @@ class TelemetryManager: except Exception: sanitized['query_id'] = str(sanitized.get('query_id', '')) - for sfield in ('adapter', 'runner', 'model_name', 'version', 'error', 'timestamp'): + for sfield in ('adapter', 'runner', 'runner_category', 'model_name', 'version', 'error', 'timestamp'): v = sanitized.get(sfield) sanitized[sfield] = '' if v is None else str(v) diff --git a/src/langbot/pkg/utils/runner.py b/src/langbot/pkg/utils/runner.py new file mode 100644 index 00000000..43aecc06 --- /dev/null +++ b/src/langbot/pkg/utils/runner.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +from urllib.parse import urlparse + + +class RunnerCategory: + LOCAL = 'local' + CLOUD = 'cloud' + UNKNOWN = 'unknown' + + +CLOUD_DOMAINS = [ + '.n8n.cloud', + '.n8n.io', + 'api.dify.ai', + 'cloud.dify.ai', + '.coze.com', + '.coze.cn', + 'cloud.langflow.ai', + '.langflow.org', +] + +LOCAL_PATTERNS = [ + 'localhost', + '127.0.0.1', + '0.0.0.0', + '192.168.', + '10.', + '172.16.', + '172.17.', + '172.18.', + '172.19.', + '172.20.', + '172.21.', + '172.22.', + '172.23.', + '172.24.', + '172.25.', + '172.26.', + '172.27.', + '172.28.', + '172.29.', + '172.30.', + '172.31.', +] + + +def get_runner_category(runner_name: str, runner_url: str) -> str: + if not runner_url: + return RunnerCategory.UNKNOWN + + try: + parsed_url = urlparse(runner_url) + host = parsed_url.hostname.lower() if parsed_url.hostname else '' + except Exception: + return RunnerCategory.UNKNOWN + + for pattern in LOCAL_PATTERNS: + if host.startswith(pattern): + return RunnerCategory.LOCAL + + for domain in CLOUD_DOMAINS: + if host.endswith(domain): + return RunnerCategory.CLOUD + + return RunnerCategory.CLOUD + + +def get_runner_info(runner_name: str, runner_url: str) -> dict: + return { + 'name': runner_name, + 'url': runner_url, + 'category': get_runner_category(runner_name, runner_url), + } + + +def is_cloud_runner(runner_name: str, runner_url: str) -> bool: + return get_runner_category(runner_name, runner_url) == RunnerCategory.CLOUD + + +def is_local_runner(runner_name: str, runner_url: str) -> bool: + return get_runner_category(runner_name, runner_url) == RunnerCategory.LOCAL + + +def extract_runner_url(runner_name: str, runner, pipeline_config: dict | None) -> str | None: + if not runner or not hasattr(runner, 'pipeline_config'): + return None + + ai_config = pipeline_config.get('ai', {}) if pipeline_config else {} + + if runner_name == 'dify-service-api': + return ai_config.get('dify-service-api', {}).get('base-url') + elif runner_name == 'n8n-service-api': + return ai_config.get('n8n-service-api', {}).get('webhook-url') + elif runner_name == 'coze-api': + return ai_config.get('coze-api', {}).get('api-base') + elif runner_name == 'langflow-api': + return ai_config.get('langflow-api', {}).get('base-url') + + return None + + +def get_runner_category_from_runner(runner_name: str, runner, pipeline_config: dict | None) -> str: + runner_url = extract_runner_url(runner_name, runner, pipeline_config) + return get_runner_category(runner_name, runner_url)