fix(assistant): resolve dashboard refs via LLM entities and remove deterministic parser fallback
This commit is contained in:
@@ -548,6 +548,37 @@ def _resolve_dashboard_id_by_ref(
|
|||||||
# [/DEF:_resolve_dashboard_id_by_ref:Function]
|
# [/DEF:_resolve_dashboard_id_by_ref:Function]
|
||||||
|
|
||||||
|
|
||||||
|
# [DEF:_resolve_dashboard_id_entity:Function]
|
||||||
|
# @PURPOSE: Resolve dashboard id from intent entities using numeric id or dashboard_ref fallback.
|
||||||
|
# @PRE: entities may contain dashboard_id as int/str and optional dashboard_ref.
|
||||||
|
# @POST: Returns resolved dashboard id or None when ambiguous/unresolvable.
|
||||||
|
def _resolve_dashboard_id_entity(
|
||||||
|
entities: Dict[str, Any],
|
||||||
|
config_manager: ConfigManager,
|
||||||
|
env_hint: Optional[str] = None,
|
||||||
|
) -> Optional[int]:
|
||||||
|
raw_dashboard_id = entities.get("dashboard_id")
|
||||||
|
dashboard_ref = entities.get("dashboard_ref")
|
||||||
|
|
||||||
|
if isinstance(raw_dashboard_id, int):
|
||||||
|
return raw_dashboard_id
|
||||||
|
|
||||||
|
if isinstance(raw_dashboard_id, str):
|
||||||
|
token = raw_dashboard_id.strip()
|
||||||
|
if token.isdigit():
|
||||||
|
return int(token)
|
||||||
|
if token and not dashboard_ref:
|
||||||
|
dashboard_ref = token
|
||||||
|
|
||||||
|
if not dashboard_ref:
|
||||||
|
return None
|
||||||
|
|
||||||
|
env_token = env_hint or entities.get("environment") or entities.get("source_env") or entities.get("target_env")
|
||||||
|
env_id = _resolve_env_id(env_token, config_manager) if env_token else _get_default_environment_id(config_manager)
|
||||||
|
return _resolve_dashboard_id_by_ref(str(dashboard_ref), env_id, config_manager)
|
||||||
|
# [/DEF:_resolve_dashboard_id_entity:Function]
|
||||||
|
|
||||||
|
|
||||||
# [DEF:_parse_command:Function]
|
# [DEF:_parse_command:Function]
|
||||||
# @PURPOSE: Deterministically parse RU/EN command text into intent payload.
|
# @PURPOSE: Deterministically parse RU/EN command text into intent payload.
|
||||||
# @PRE: message contains raw user text and config manager resolves environments.
|
# @PRE: message contains raw user text and config manager resolves environments.
|
||||||
@@ -793,53 +824,53 @@ def _build_tool_catalog(current_user: User, config_manager: ConfigManager, db: S
|
|||||||
{
|
{
|
||||||
"operation": "create_branch",
|
"operation": "create_branch",
|
||||||
"domain": "git",
|
"domain": "git",
|
||||||
"description": "Create git branch for dashboard",
|
"description": "Create git branch for dashboard by id/slug/title",
|
||||||
"required_entities": ["dashboard_id", "branch_name"],
|
"required_entities": ["branch_name"],
|
||||||
"optional_entities": [],
|
"optional_entities": ["dashboard_id", "dashboard_ref"],
|
||||||
"risk_level": "guarded",
|
"risk_level": "guarded",
|
||||||
"requires_confirmation": False,
|
"requires_confirmation": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"operation": "commit_changes",
|
"operation": "commit_changes",
|
||||||
"domain": "git",
|
"domain": "git",
|
||||||
"description": "Commit dashboard repository changes",
|
"description": "Commit dashboard repository changes by dashboard id/slug/title",
|
||||||
"required_entities": ["dashboard_id"],
|
"required_entities": [],
|
||||||
"optional_entities": ["message"],
|
"optional_entities": ["dashboard_id", "dashboard_ref", "message"],
|
||||||
"risk_level": "guarded",
|
"risk_level": "guarded",
|
||||||
"requires_confirmation": False,
|
"requires_confirmation": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"operation": "deploy_dashboard",
|
"operation": "deploy_dashboard",
|
||||||
"domain": "git",
|
"domain": "git",
|
||||||
"description": "Deploy dashboard to target environment",
|
"description": "Deploy dashboard (id/slug/title) to target environment",
|
||||||
"required_entities": ["dashboard_id", "environment"],
|
"required_entities": ["environment"],
|
||||||
"optional_entities": [],
|
"optional_entities": ["dashboard_id", "dashboard_ref"],
|
||||||
"risk_level": "guarded",
|
"risk_level": "guarded",
|
||||||
"requires_confirmation": False,
|
"requires_confirmation": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"operation": "execute_migration",
|
"operation": "execute_migration",
|
||||||
"domain": "migration",
|
"domain": "migration",
|
||||||
"description": "Run dashboard migration between environments",
|
"description": "Run dashboard migration (id/slug/title) between environments",
|
||||||
"required_entities": ["dashboard_id", "source_env", "target_env"],
|
"required_entities": ["source_env", "target_env"],
|
||||||
"optional_entities": [],
|
"optional_entities": ["dashboard_id", "dashboard_ref"],
|
||||||
"risk_level": "guarded",
|
"risk_level": "guarded",
|
||||||
"requires_confirmation": False,
|
"requires_confirmation": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"operation": "run_backup",
|
"operation": "run_backup",
|
||||||
"domain": "backup",
|
"domain": "backup",
|
||||||
"description": "Run backup for environment or specific dashboard",
|
"description": "Run backup for environment or specific dashboard by id/slug/title",
|
||||||
"required_entities": ["environment"],
|
"required_entities": ["environment"],
|
||||||
"optional_entities": ["dashboard_id"],
|
"optional_entities": ["dashboard_id", "dashboard_ref"],
|
||||||
"risk_level": "guarded",
|
"risk_level": "guarded",
|
||||||
"requires_confirmation": False,
|
"requires_confirmation": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"operation": "run_llm_validation",
|
"operation": "run_llm_validation",
|
||||||
"domain": "llm",
|
"domain": "llm",
|
||||||
"description": "Run LLM dashboard validation",
|
"description": "Run LLM dashboard validation by dashboard id/slug/title",
|
||||||
"required_entities": ["dashboard_id"],
|
"required_entities": [],
|
||||||
"optional_entities": ["dashboard_ref", "environment", "provider"],
|
"optional_entities": ["dashboard_ref", "environment", "provider"],
|
||||||
"defaults": {"environment": default_env_id, "provider": validation_provider},
|
"defaults": {"environment": default_env_id, "provider": validation_provider},
|
||||||
"risk_level": "guarded",
|
"risk_level": "guarded",
|
||||||
@@ -900,11 +931,11 @@ def _clarification_text_for_intent(intent: Optional[Dict[str, Any]], detail_text
|
|||||||
"run_llm_documentation": (
|
"run_llm_documentation": (
|
||||||
"Нужно уточнение для генерации документации: Укажите dataset_id, окружение и провайдер LLM."
|
"Нужно уточнение для генерации документации: Укажите dataset_id, окружение и провайдер LLM."
|
||||||
),
|
),
|
||||||
"create_branch": "Нужно уточнение: укажите dashboard_id и имя ветки.",
|
"create_branch": "Нужно уточнение: укажите дашборд (id/slug/title) и имя ветки.",
|
||||||
"commit_changes": "Нужно уточнение: укажите dashboard_id для коммита.",
|
"commit_changes": "Нужно уточнение: укажите дашборд (id/slug/title) для коммита.",
|
||||||
"deploy_dashboard": "Нужно уточнение: укажите dashboard_id и целевое окружение.",
|
"deploy_dashboard": "Нужно уточнение: укажите дашборд (id/slug/title) и целевое окружение.",
|
||||||
"execute_migration": "Нужно уточнение: укажите dashboard_id, source_env и target_env.",
|
"execute_migration": "Нужно уточнение: укажите дашборд (id/slug/title), source_env и target_env.",
|
||||||
"run_backup": "Нужно уточнение: укажите окружение для бэкапа.",
|
"run_backup": "Нужно уточнение: укажите окружение и при необходимости дашборд (id/slug/title).",
|
||||||
}
|
}
|
||||||
return guidance_by_operation.get(operation, detail_text)
|
return guidance_by_operation.get(operation, detail_text)
|
||||||
# [/DEF:_clarification_text_for_intent:Function]
|
# [/DEF:_clarification_text_for_intent:Function]
|
||||||
@@ -958,6 +989,7 @@ async def _plan_intent_with_llm(
|
|||||||
"Rules:\n"
|
"Rules:\n"
|
||||||
"- Use only operation names from available_tools.\n"
|
"- Use only operation names from available_tools.\n"
|
||||||
"- If input is ambiguous, operation must be \"clarify\" with low confidence.\n"
|
"- If input is ambiguous, operation must be \"clarify\" with low confidence.\n"
|
||||||
|
"- If dashboard is provided as name/slug (e.g., COVID), put it into entities.dashboard_ref.\n"
|
||||||
"- Keep entities minimal and factual.\n"
|
"- Keep entities minimal and factual.\n"
|
||||||
)
|
)
|
||||||
payload = {
|
payload = {
|
||||||
@@ -1091,35 +1123,35 @@ async def _dispatch_intent(
|
|||||||
|
|
||||||
if operation == "create_branch":
|
if operation == "create_branch":
|
||||||
_check_any_permission(current_user, [("plugin:git", "EXECUTE")])
|
_check_any_permission(current_user, [("plugin:git", "EXECUTE")])
|
||||||
dashboard_id = entities.get("dashboard_id")
|
dashboard_id = _resolve_dashboard_id_entity(entities, config_manager)
|
||||||
branch_name = entities.get("branch_name")
|
branch_name = entities.get("branch_name")
|
||||||
if not dashboard_id or not branch_name:
|
if not dashboard_id or not branch_name:
|
||||||
raise HTTPException(status_code=400, detail="Missing dashboard_id or branch_name")
|
raise HTTPException(status_code=422, detail="Missing dashboard_id/dashboard_ref or branch_name")
|
||||||
git_service.create_branch(int(dashboard_id), branch_name, "main")
|
git_service.create_branch(dashboard_id, branch_name, "main")
|
||||||
return f"Ветка `{branch_name}` создана для дашборда {dashboard_id}.", None, []
|
return f"Ветка `{branch_name}` создана для дашборда {dashboard_id}.", None, []
|
||||||
|
|
||||||
if operation == "commit_changes":
|
if operation == "commit_changes":
|
||||||
_check_any_permission(current_user, [("plugin:git", "EXECUTE")])
|
_check_any_permission(current_user, [("plugin:git", "EXECUTE")])
|
||||||
dashboard_id = entities.get("dashboard_id")
|
dashboard_id = _resolve_dashboard_id_entity(entities, config_manager)
|
||||||
commit_message = entities.get("message")
|
commit_message = entities.get("message")
|
||||||
if not dashboard_id:
|
if not dashboard_id:
|
||||||
raise HTTPException(status_code=400, detail="Missing dashboard_id")
|
raise HTTPException(status_code=422, detail="Missing dashboard_id/dashboard_ref")
|
||||||
git_service.commit_changes(int(dashboard_id), commit_message, None)
|
git_service.commit_changes(dashboard_id, commit_message, None)
|
||||||
return "Коммит выполнен успешно.", None, []
|
return "Коммит выполнен успешно.", None, []
|
||||||
|
|
||||||
if operation == "deploy_dashboard":
|
if operation == "deploy_dashboard":
|
||||||
_check_any_permission(current_user, [("plugin:git", "EXECUTE")])
|
_check_any_permission(current_user, [("plugin:git", "EXECUTE")])
|
||||||
dashboard_id = entities.get("dashboard_id")
|
|
||||||
env_token = entities.get("environment")
|
env_token = entities.get("environment")
|
||||||
env_id = _resolve_env_id(env_token, config_manager)
|
env_id = _resolve_env_id(env_token, config_manager)
|
||||||
|
dashboard_id = _resolve_dashboard_id_entity(entities, config_manager, env_hint=env_token)
|
||||||
if not dashboard_id or not env_id:
|
if not dashboard_id or not env_id:
|
||||||
raise HTTPException(status_code=400, detail="Missing dashboard_id or environment")
|
raise HTTPException(status_code=422, detail="Missing dashboard_id/dashboard_ref or environment")
|
||||||
|
|
||||||
task = await task_manager.create_task(
|
task = await task_manager.create_task(
|
||||||
plugin_id="git-integration",
|
plugin_id="git-integration",
|
||||||
params={
|
params={
|
||||||
"operation": "deploy",
|
"operation": "deploy",
|
||||||
"dashboard_id": int(dashboard_id),
|
"dashboard_id": dashboard_id,
|
||||||
"environment_id": env_id,
|
"environment_id": env_id,
|
||||||
},
|
},
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
@@ -1135,16 +1167,17 @@ async def _dispatch_intent(
|
|||||||
|
|
||||||
if operation == "execute_migration":
|
if operation == "execute_migration":
|
||||||
_check_any_permission(current_user, [("plugin:migration", "EXECUTE"), ("plugin:superset-migration", "EXECUTE")])
|
_check_any_permission(current_user, [("plugin:migration", "EXECUTE"), ("plugin:superset-migration", "EXECUTE")])
|
||||||
dashboard_id = entities.get("dashboard_id")
|
src_token = entities.get("source_env")
|
||||||
src = _resolve_env_id(entities.get("source_env"), config_manager)
|
dashboard_id = _resolve_dashboard_id_entity(entities, config_manager, env_hint=src_token)
|
||||||
|
src = _resolve_env_id(src_token, config_manager)
|
||||||
tgt = _resolve_env_id(entities.get("target_env"), config_manager)
|
tgt = _resolve_env_id(entities.get("target_env"), config_manager)
|
||||||
if not dashboard_id or not src or not tgt:
|
if not dashboard_id or not src or not tgt:
|
||||||
raise HTTPException(status_code=400, detail="Missing dashboard_id/source_env/target_env")
|
raise HTTPException(status_code=422, detail="Missing dashboard_id/dashboard_ref/source_env/target_env")
|
||||||
|
|
||||||
task = await task_manager.create_task(
|
task = await task_manager.create_task(
|
||||||
plugin_id="superset-migration",
|
plugin_id="superset-migration",
|
||||||
params={
|
params={
|
||||||
"selected_ids": [int(dashboard_id)],
|
"selected_ids": [dashboard_id],
|
||||||
"source_env_id": src,
|
"source_env_id": src,
|
||||||
"target_env_id": tgt,
|
"target_env_id": tgt,
|
||||||
"replace_db_config": False,
|
"replace_db_config": False,
|
||||||
@@ -1162,13 +1195,17 @@ async def _dispatch_intent(
|
|||||||
|
|
||||||
if operation == "run_backup":
|
if operation == "run_backup":
|
||||||
_check_any_permission(current_user, [("plugin:superset-backup", "EXECUTE"), ("plugin:backup", "EXECUTE")])
|
_check_any_permission(current_user, [("plugin:superset-backup", "EXECUTE"), ("plugin:backup", "EXECUTE")])
|
||||||
env_id = _resolve_env_id(entities.get("environment"), config_manager)
|
env_token = entities.get("environment")
|
||||||
|
env_id = _resolve_env_id(env_token, config_manager)
|
||||||
if not env_id:
|
if not env_id:
|
||||||
raise HTTPException(status_code=400, detail="Missing or unknown environment")
|
raise HTTPException(status_code=400, detail="Missing or unknown environment")
|
||||||
|
|
||||||
params: Dict[str, Any] = {"environment_id": env_id}
|
params: Dict[str, Any] = {"environment_id": env_id}
|
||||||
if entities.get("dashboard_id"):
|
if entities.get("dashboard_id") or entities.get("dashboard_ref"):
|
||||||
params["dashboard_ids"] = [int(entities["dashboard_id"])]
|
dashboard_id = _resolve_dashboard_id_entity(entities, config_manager, env_hint=env_token)
|
||||||
|
if not dashboard_id:
|
||||||
|
raise HTTPException(status_code=422, detail="Missing dashboard_id/dashboard_ref")
|
||||||
|
params["dashboard_ids"] = [dashboard_id]
|
||||||
|
|
||||||
task = await task_manager.create_task(
|
task = await task_manager.create_task(
|
||||||
plugin_id="superset-backup",
|
plugin_id="superset-backup",
|
||||||
@@ -1186,14 +1223,9 @@ async def _dispatch_intent(
|
|||||||
|
|
||||||
if operation == "run_llm_validation":
|
if operation == "run_llm_validation":
|
||||||
_check_any_permission(current_user, [("plugin:llm_dashboard_validation", "EXECUTE")])
|
_check_any_permission(current_user, [("plugin:llm_dashboard_validation", "EXECUTE")])
|
||||||
env_id = _resolve_env_id(entities.get("environment"), config_manager) or _get_default_environment_id(config_manager)
|
env_token = entities.get("environment")
|
||||||
dashboard_id = entities.get("dashboard_id")
|
env_id = _resolve_env_id(env_token, config_manager) or _get_default_environment_id(config_manager)
|
||||||
if not dashboard_id:
|
dashboard_id = _resolve_dashboard_id_entity(entities, config_manager, env_hint=env_token)
|
||||||
dashboard_id = _resolve_dashboard_id_by_ref(
|
|
||||||
entities.get("dashboard_ref"),
|
|
||||||
env_id,
|
|
||||||
config_manager,
|
|
||||||
)
|
|
||||||
provider_id = _resolve_provider_id(
|
provider_id = _resolve_provider_id(
|
||||||
entities.get("provider"),
|
entities.get("provider"),
|
||||||
db,
|
db,
|
||||||
@@ -1296,7 +1328,14 @@ async def send_message(
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(f"[assistant.planner][fallback] Planner error: {exc}")
|
logger.warning(f"[assistant.planner][fallback] Planner error: {exc}")
|
||||||
if not intent:
|
if not intent:
|
||||||
intent = _parse_command(request.message, config_manager)
|
intent = {
|
||||||
|
"domain": "unknown",
|
||||||
|
"operation": "clarify",
|
||||||
|
"entities": {},
|
||||||
|
"confidence": 0.0,
|
||||||
|
"risk_level": "safe",
|
||||||
|
"requires_confirmation": False,
|
||||||
|
}
|
||||||
confidence = float(intent.get("confidence", 0.0))
|
confidence = float(intent.get("confidence", 0.0))
|
||||||
|
|
||||||
if intent.get("domain") == "unknown" or confidence < 0.6:
|
if intent.get("domain") == "unknown" or confidence < 0.6:
|
||||||
|
|||||||
Reference in New Issue
Block a user