This commit is contained in:
2026-02-25 10:34:30 +03:00
parent bc0367ab72
commit d32d85556f
5 changed files with 217 additions and 87 deletions

View File

@@ -918,6 +918,46 @@ def _coerce_intent_entities(intent: Dict[str, Any]) -> Dict[str, Any]:
# [/DEF:_coerce_intent_entities:Function]
# Operations that are read-only and do not require confirmation.
_SAFE_OPS = {"show_capabilities", "get_task_status"}
# [DEF:_confirmation_summary:Function]
# @PURPOSE: Build human-readable confirmation prompt for an intent before execution.
# @PRE: intent contains operation and entities fields.
# @POST: Returns descriptive Russian-language text ending with confirmation prompt.
def _confirmation_summary(intent: Dict[str, Any]) -> str:
operation = intent.get("operation", "")
entities = intent.get("entities", {})
descriptions: Dict[str, str] = {
"create_branch": "создание ветки{branch} для дашборда{dashboard}",
"commit_changes": "коммит изменений для дашборда{dashboard}",
"deploy_dashboard": "деплой дашборда{dashboard} в окружение{env}",
"execute_migration": "миграция дашборда{dashboard} с{src} на{tgt}",
"run_backup": "бэкап окружения{env}{dashboard}",
"run_llm_validation": "LLM-валидация дашборда{dashboard}{env}",
"run_llm_documentation": "генерация документации для датасета{dataset}{env}",
}
template = descriptions.get(operation)
if not template:
return "Подтвердите выполнение операции или отмените."
def _label(value: Any, prefix: str = " ") -> str:
return f"{prefix}{value}" if value else ""
dashboard = entities.get("dashboard_id") or entities.get("dashboard_ref")
text = template.format(
branch=_label(entities.get("branch_name")),
dashboard=_label(dashboard),
env=_label(entities.get("environment") or entities.get("target_env")),
src=_label(entities.get("source_env")),
tgt=_label(entities.get("target_env")),
dataset=_label(entities.get("dataset_id")),
)
return f"Выполнить: {text}. Подтвердите или отмените."
# [/DEF:_confirmation_summary:Function]
# [DEF:_clarification_text_for_intent:Function]
# @PURPOSE: Convert technical missing-parameter errors into user-facing clarification prompts.
# @PRE: state was classified as needs_clarification for current intent/error combination.
@@ -1328,14 +1368,7 @@ async def send_message(
except Exception as exc:
logger.warning(f"[assistant.planner][fallback] Planner error: {exc}")
if not intent:
intent = {
"domain": "unknown",
"operation": "clarify",
"entities": {},
"confidence": 0.0,
"risk_level": "safe",
"requires_confirmation": False,
}
intent = _parse_command(request.message, config_manager)
confidence = float(intent.get("confidence", 0.0))
if intent.get("domain") == "unknown" or confidence < 0.6:
@@ -1358,7 +1391,8 @@ async def send_message(
try:
_authorize_intent(intent, current_user)
if intent.get("requires_confirmation"):
operation = intent.get("operation")
if operation not in _SAFE_OPS:
confirmation_id = str(uuid.uuid4())
confirm = ConfirmationRecord(
id=confirmation_id,
@@ -1371,7 +1405,7 @@ async def send_message(
)
CONFIRMATIONS[confirmation_id] = confirm
_persist_confirmation(db, confirm)
text = "Операция рискованная. Подтвердите выполнение или отмените."
text = _confirmation_summary(intent)
_append_history(
user_id,
conversation_id,
@@ -1388,7 +1422,13 @@ async def send_message(
text,
state="needs_confirmation",
confirmation_id=confirmation_id,
metadata={"intent": intent},
metadata={
"intent": intent,
"actions": [
{"type": "confirm", "label": "✅ Подтвердить", "target": confirmation_id},
{"type": "cancel", "label": "❌ Отменить", "target": confirmation_id},
],
},
)
audit_payload = {
"decision": "needs_confirmation",
@@ -1406,12 +1446,13 @@ async def send_message(
intent=intent,
confirmation_id=confirmation_id,
actions=[
AssistantAction(type="confirm", label="Confirm", target=confirmation_id),
AssistantAction(type="cancel", label="Cancel", target=confirmation_id),
AssistantAction(type="confirm", label="✅ Подтвердить", target=confirmation_id),
AssistantAction(type="cancel", label="❌ Отменить", target=confirmation_id),
],
created_at=datetime.utcnow(),
)
# Read-only operations execute immediately
text, task_id, actions = await _dispatch_intent(intent, current_user, task_manager, config_manager, db)
state = "started" if task_id else "success"
_append_history(user_id, conversation_id, "assistant", text, state=state, task_id=task_id)