Похоже работает
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
# [DEF:backend.src.plugins.llm_analysis.plugin:Module]
|
||||
# [DEF:backend/src/plugins/llm_analysis/plugin.py:Module]
|
||||
# @TIER: STANDARD
|
||||
# @SEMANTICS: plugin, llm, analysis, documentation
|
||||
# @PURPOSE: Implements DashboardValidationPlugin and DocumentationPlugin.
|
||||
# @LAYER: Domain
|
||||
# @RELATION: INHERITS_FROM -> backend.src.core.plugin_base.PluginBase
|
||||
# @RELATION: INHERITS -> backend.src.core.plugin_base.PluginBase
|
||||
# @RELATION: CALLS -> backend.src.plugins.llm_analysis.service.ScreenshotService
|
||||
# @RELATION: CALLS -> backend.src.plugins.llm_analysis.service.LLMClient
|
||||
# @RELATION: CALLS -> backend.src.services.llm_provider.LLMProviderService
|
||||
@@ -12,6 +12,7 @@
|
||||
from typing import Dict, Any, Optional, List
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from ...core.plugin_base import PluginBase
|
||||
from ...core.logger import belief_scope, logger
|
||||
@@ -54,6 +55,11 @@ class DashboardValidationPlugin(PluginBase):
|
||||
"required": ["dashboard_id", "environment_id", "provider_id"]
|
||||
}
|
||||
|
||||
# [DEF:DashboardValidationPlugin.execute:Function]
|
||||
# @PURPOSE: Executes the dashboard validation task.
|
||||
# @PRE: params contains dashboard_id, environment_id, and provider_id.
|
||||
# @POST: Returns a dictionary with validation results and persists them to the database.
|
||||
# @SIDE_EFFECT: Captures a screenshot, calls LLM API, and writes to the database.
|
||||
async def execute(self, params: Dict[str, Any]):
|
||||
with belief_scope("execute", f"plugin_id={self.id}"):
|
||||
logger.info(f"Executing {self.name} with params: {params}")
|
||||
@@ -88,12 +94,35 @@ class DashboardValidationPlugin(PluginBase):
|
||||
if not db_provider:
|
||||
raise ValueError(f"LLM Provider {provider_id} not found")
|
||||
|
||||
logger.info(f"[DashboardValidationPlugin.execute] Retrieved provider config:")
|
||||
logger.info(f"[DashboardValidationPlugin.execute] Provider ID: {db_provider.id}")
|
||||
logger.info(f"[DashboardValidationPlugin.execute] Provider Name: {db_provider.name}")
|
||||
logger.info(f"[DashboardValidationPlugin.execute] Provider Type: {db_provider.provider_type}")
|
||||
logger.info(f"[DashboardValidationPlugin.execute] Base URL: {db_provider.base_url}")
|
||||
logger.info(f"[DashboardValidationPlugin.execute] Default Model: {db_provider.default_model}")
|
||||
logger.info(f"[DashboardValidationPlugin.execute] Is Active: {db_provider.is_active}")
|
||||
|
||||
api_key = llm_service.get_decrypted_api_key(provider_id)
|
||||
logger.info(f"[DashboardValidationPlugin.execute] API Key decrypted (first 8 chars): {api_key[:8] if api_key and len(api_key) > 8 else 'EMPTY_OR_NONE'}...")
|
||||
logger.info(f"[DashboardValidationPlugin.execute] API Key Length: {len(api_key) if api_key else 0}")
|
||||
|
||||
# Check if API key was successfully decrypted
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
f"Failed to decrypt API key for provider {provider_id}. "
|
||||
f"The provider may have been encrypted with a different encryption key. "
|
||||
f"Please update the provider with a new API key through the UI."
|
||||
)
|
||||
|
||||
# 3. Capture Screenshot
|
||||
screenshot_service = ScreenshotService(env)
|
||||
os.makedirs("ss-tools-storage/screenshots", exist_ok=True)
|
||||
screenshot_path = f"ss-tools-storage/screenshots/{dashboard_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
|
||||
|
||||
storage_root = config_mgr.get_config().settings.storage.root_path
|
||||
screenshots_dir = os.path.join(storage_root, "screenshots")
|
||||
os.makedirs(screenshots_dir, exist_ok=True)
|
||||
|
||||
filename = f"{dashboard_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
|
||||
screenshot_path = os.path.join(screenshots_dir, filename)
|
||||
|
||||
await screenshot_service.capture_dashboard(dashboard_id, screenshot_path)
|
||||
|
||||
@@ -109,8 +138,8 @@ class DashboardValidationPlugin(PluginBase):
|
||||
# Note: We filter by dashboard_id matching the object
|
||||
query_params = {
|
||||
"filters": [
|
||||
{"col": "dashboard_id", "op": "eq", "value": dashboard_id},
|
||||
{"col": "dttm", "op": "gt", "value": start_time}
|
||||
{"col": "dashboard_id", "opr": "eq", "value": dashboard_id},
|
||||
{"col": "dttm", "opr": "gt", "value": start_time}
|
||||
],
|
||||
"order_column": "dttm",
|
||||
"order_direction": "desc",
|
||||
@@ -149,11 +178,11 @@ class DashboardValidationPlugin(PluginBase):
|
||||
analysis = await llm_client.analyze_dashboard(screenshot_path, logs)
|
||||
|
||||
# Log analysis summary to task logs for better visibility
|
||||
logger.info(f"[ANALYSIS_SUMMARY] Status: {analysis['status']}")
|
||||
logger.info(f"[ANALYSIS_SUMMARY] Summary: {analysis['summary']}")
|
||||
task_log("INFO", f"[ANALYSIS_SUMMARY] Status: {analysis['status']}")
|
||||
task_log("INFO", f"[ANALYSIS_SUMMARY] Summary: {analysis['summary']}")
|
||||
if analysis.get("issues"):
|
||||
for i, issue in enumerate(analysis["issues"]):
|
||||
logger.info(f"[ANALYSIS_ISSUE][{i+1}] {issue.get('severity')}: {issue.get('message')} (Location: {issue.get('location', 'N/A')})")
|
||||
task_log("INFO", f"[ANALYSIS_ISSUE][{i+1}] {issue.get('severity')}: {issue.get('message')} (Location: {issue.get('location', 'N/A')})")
|
||||
|
||||
# 6. Persist Result
|
||||
validation_result = ValidationResult(
|
||||
@@ -178,7 +207,7 @@ class DashboardValidationPlugin(PluginBase):
|
||||
|
||||
# 7. Notification on failure (US1 / FR-015)
|
||||
if validation_result.status == ValidationStatus.FAIL:
|
||||
logger.warning(f"Dashboard {dashboard_id} validation FAILED. Summary: {validation_result.summary}")
|
||||
task_log("WARNING", f"Dashboard {dashboard_id} validation FAILED. Summary: {validation_result.summary}")
|
||||
# Placeholder for Email/Pulse notification dispatch
|
||||
# In a real implementation, we would call a NotificationService here
|
||||
# with a payload containing the summary and a link to the report.
|
||||
@@ -190,6 +219,7 @@ class DashboardValidationPlugin(PluginBase):
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
# [/DEF:DashboardValidationPlugin.execute:Function]
|
||||
# [/DEF:DashboardValidationPlugin:Class]
|
||||
|
||||
# [DEF:DocumentationPlugin:Class]
|
||||
@@ -223,12 +253,7 @@ class DocumentationPlugin(PluginBase):
|
||||
"required": ["dataset_id", "environment_id", "provider_id"]
|
||||
}
|
||||
|
||||
# [DEF:execute:Function]
|
||||
# @PURPOSE: Executes the dashboard validation task.
|
||||
# @PRE: params contains dashboard_id, environment_id, and provider_id.
|
||||
# @POST: Returns a dictionary with validation results and persists them to the database.
|
||||
# @SIDE_EFFECT: Captures a screenshot, calls LLM API, and writes to the database.
|
||||
# [DEF:execute:Function]
|
||||
# [DEF:DocumentationPlugin.execute:Function]
|
||||
# @PURPOSE: Executes the dataset documentation task.
|
||||
# @PRE: params contains dataset_id, environment_id, and provider_id.
|
||||
# @POST: Returns generated documentation and updates the dataset in Superset.
|
||||
@@ -256,7 +281,25 @@ class DocumentationPlugin(PluginBase):
|
||||
if not db_provider:
|
||||
raise ValueError(f"LLM Provider {provider_id} not found")
|
||||
|
||||
logger.info(f"[DocumentationPlugin.execute] Retrieved provider config:")
|
||||
logger.info(f"[DocumentationPlugin.execute] Provider ID: {db_provider.id}")
|
||||
logger.info(f"[DocumentationPlugin.execute] Provider Name: {db_provider.name}")
|
||||
logger.info(f"[DocumentationPlugin.execute] Provider Type: {db_provider.provider_type}")
|
||||
logger.info(f"[DocumentationPlugin.execute] Base URL: {db_provider.base_url}")
|
||||
logger.info(f"[DocumentationPlugin.execute] Default Model: {db_provider.default_model}")
|
||||
logger.info(f"[DocumentationPlugin.execute] Is Active: {db_provider.is_active}")
|
||||
|
||||
api_key = llm_service.get_decrypted_api_key(provider_id)
|
||||
logger.info(f"[DocumentationPlugin.execute] API Key decrypted (first 8 chars): {api_key[:8] if api_key and len(api_key) > 8 else 'EMPTY_OR_NONE'}...")
|
||||
logger.info(f"[DocumentationPlugin.execute] API Key Length: {len(api_key) if api_key else 0}")
|
||||
|
||||
# Check if API key was successfully decrypted
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
f"Failed to decrypt API key for provider {provider_id}. "
|
||||
f"The provider may have been encrypted with a different encryption key. "
|
||||
f"Please update the provider with a new API key through the UI."
|
||||
)
|
||||
|
||||
# 3. Fetch Metadata (US2 / T024)
|
||||
from ...core.superset_client import SupersetClient
|
||||
@@ -328,6 +371,7 @@ class DocumentationPlugin(PluginBase):
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
# [/DEF:DocumentationPlugin.execute:Function]
|
||||
# [/DEF:DocumentationPlugin:Class]
|
||||
|
||||
# [/DEF:backend.src.plugins.llm_analysis.plugin:Module]
|
||||
# [/DEF:backend/src/plugins/llm_analysis/plugin.py:Module]
|
||||
|
||||
Reference in New Issue
Block a user