### Bugfixes — Agent Chat 'Думаю' State Leak - fix(agent-chat): loadHistory() now resets streamingState/idle + cancels stale submission — prevents 'Думаю' state leak across conversation switches - fix(agent-chat): onDisconnected/onDisconnectedPermanent cascade to streamingState — prevents permanent hang on connection loss during stream - fix(agent-chat): guard on isLoadingHistory — prevents false commit of 'agent unavailable' fallback when switching conversations - fix(agent-chat): remove race in _sendNow empty-response check vs Svelte microtask (duplicate logic removed, handles correctly) - fix(stream-processor): confirm_resolved now appends msg.text to partialText instead of dropping it ### Bugfixes — Backend PDF Upload - fix(document-parser): _detect_format_by_magic() — reads file header magic bytes as fallback when Gradio loses filename - fix(document-parser): improved name extraction — tries orig_name, path stem - fix(document-parser): @RELATION AgentChatTypes -> AgentChat.Types ### HITL Flow & Agent Chat Improvements - feat(agent): HITL resume confirm/deny with userId/userJwt/envId propagation - feat(agent): confirm_required metadata fallback via aget_state() after 'Event loop is closed' error during interrupt - feat(agent): interrupt_before re-enabled via AGENT_CONFIRM_TOOLS env var - feat(frontend): debug panel with connection/stream state monitoring - feat(frontend): AgentChatModel constructor options + onBeforeSend callback - feat(frontend): crypto.randomUUID() for local conversation ID on first send ### Backend Agent Refactoring - refactor(agent): langgraph_setup — monkey-patch for PydanticSerializationError - refactor(agent): tools.py — dual identity headers, expanded tool set - refactor(agent): run.py — _find_free_port, Gradio server port fallback - refactor(agent): app.py — file size validation, message truncation, HITL path ### Frontend - feat(dashboard-hub): DashboardHubModel with filters, pagination, git actions - feat(ui): DateRangeFilter component - feat(i18n): new dashboard keys; cache tooltips fix - fix(i18n): full run tooltips — cache is NOT ignored ### Semantic Protocol - chore(agents): update all agents with canonical format - chore(skills): sync semantics-core, semantics-contracts, molecular-cot-logging ### Housekeeping - chore: remove stale semantic reports (10 files, Jan 2026) - chore: update 033-gradio-agent-chat specs, contracts, UX, tasks, tests - chore: add .agents/ directory (mirrors .opencode/ agent layouts) - chore: update run.sh with DEV_MODE, port management
553 lines
20 KiB
Python
553 lines
20 KiB
Python
# backend/src/agent/tools.py
|
|
# #region AgentChat.Tools [C:4] [TYPE Module] [SEMANTICS agent-chat,tools,langchain]
|
|
# @DEFGROUP AgentChat Native LangChain @tool functions.
|
|
# @REJECTED StructuredTool wrapping — native @tool is the single source of truth.
|
|
# @REJECTED StructuredTool wrapping — native @tool is the single source of truth.
|
|
# @RATIONALE LangChain @tool decorator chosen over direct FastAPI calls for LangGraph compatibility — tools are auto-registered in the agent's tool-calling loop.
|
|
|
|
import json
|
|
import os
|
|
from typing import Any
|
|
|
|
import httpx
|
|
from langchain_core.tools import tool
|
|
from pydantic import BaseModel, Field
|
|
|
|
from src.agent.context import get_service_jwt, get_user_jwt
|
|
|
|
FASTAPI_URL = os.getenv("FASTAPI_URL", "http://localhost:8000")
|
|
TOOL_RESPONSE_LIMIT = 4000
|
|
|
|
|
|
def _dual_auth_headers() -> dict[str, str]:
|
|
"""Build dual-identity auth headers for tool→FastAPI calls per FR-007/FR-019.
|
|
|
|
Authorization: Bearer {service_jwt} — authenticates the agent container.
|
|
X-User-JWT: {user_jwt} — authorizes the operation as the end user.
|
|
Falls back to user JWT in Authorization if service JWT unavailable.
|
|
"""
|
|
user_jwt = get_user_jwt() or ""
|
|
svc_jwt = get_service_jwt() or os.getenv("SERVICE_JWT", "")
|
|
headers = {}
|
|
if svc_jwt:
|
|
headers["Authorization"] = f"Bearer {svc_jwt}"
|
|
if user_jwt:
|
|
headers["X-User-JWT"] = user_jwt
|
|
elif user_jwt:
|
|
headers["Authorization"] = f"Bearer {user_jwt}"
|
|
return headers
|
|
|
|
|
|
def _trim_response(text: str, limit: int = TOOL_RESPONSE_LIMIT) -> str:
|
|
if len(text) <= limit:
|
|
return text
|
|
return f"{text[:limit]}\n... response truncated ..."
|
|
|
|
|
|
async def _get(path: str, params: dict[str, Any] | None = None) -> httpx.Response:
|
|
async with httpx.AsyncClient() as client:
|
|
return await client.get(
|
|
f"{FASTAPI_URL}{path}",
|
|
params=params,
|
|
headers=_dual_auth_headers(),
|
|
)
|
|
|
|
|
|
async def _post(
|
|
path: str,
|
|
payload: dict[str, Any] | None = None,
|
|
params: dict[str, Any] | None = None,
|
|
) -> httpx.Response:
|
|
async with httpx.AsyncClient() as client:
|
|
return await client.post(
|
|
f"{FASTAPI_URL}{path}",
|
|
json=payload or {},
|
|
params=params,
|
|
headers=_dual_auth_headers(),
|
|
)
|
|
|
|
|
|
def _api_result(resp: httpx.Response, ok_statuses: set[int] | None = None) -> str:
|
|
ok_statuses = ok_statuses or {200, 201, 202}
|
|
if resp.status_code not in ok_statuses:
|
|
return f"Error {resp.status_code}: {resp.text}"
|
|
return _trim_response(resp.text)
|
|
|
|
|
|
async def _resolve_active_provider_id(provider_id: str | None = None) -> str | None:
|
|
if provider_id:
|
|
return provider_id
|
|
resp = await _get("/api/llm/providers")
|
|
if resp.status_code != 200:
|
|
return None
|
|
try:
|
|
providers = resp.json()
|
|
except json.JSONDecodeError:
|
|
return None
|
|
active = next((p for p in providers if p.get("is_active")), None)
|
|
if active:
|
|
return str(active.get("id"))
|
|
if providers:
|
|
return str(providers[0].get("id"))
|
|
return None
|
|
|
|
|
|
def _dashboard_ids(dashboard_id: int | str | None, dashboard_ids: list[int] | list[str] | None) -> list[int]:
|
|
values: list[int] = []
|
|
for raw in dashboard_ids or []:
|
|
values.append(int(raw))
|
|
if dashboard_id is not None and str(dashboard_id).strip():
|
|
values.append(int(dashboard_id))
|
|
return list(dict.fromkeys(values))
|
|
|
|
|
|
# ── Tool: search_dashboards ──
|
|
class SearchDashboardsInput(BaseModel):
|
|
query: str = Field(description="Search query for dashboard name")
|
|
env_id: str | None = Field(default=None, description="Environment ID (e.g. 'prod', 'ss-dev')")
|
|
|
|
|
|
# @ingroup AgentChat
|
|
# @PRE User authenticated via dual-identity JWT
|
|
# @POST Returns JSON string result from FastAPI
|
|
# @SIDE_EFFECT HTTP call to FastAPI backend
|
|
|
|
@tool(args_schema=SearchDashboardsInput)
|
|
async def search_dashboards(query: str, env_id: str | None = None) -> str:
|
|
"""Search and list dashboards by name, with optional environment filter.
|
|
Pass env_id like 'prod', 'ss-dev', or 'ss-preprod' to filter by environment.
|
|
Returns a formatted list of dashboards with their details.
|
|
"""
|
|
params = {"q": query, "env_id": env_id or ""}
|
|
resp = await _get("/api/dashboards", params=params)
|
|
if resp.status_code != 200:
|
|
return f"Error {resp.status_code}: Unable to fetch dashboards. {resp.text}"
|
|
|
|
try:
|
|
data = resp.json()
|
|
dashboards = data.get("dashboards", [])
|
|
total = data.get("total", 0)
|
|
|
|
if total == 0:
|
|
return f"No dashboards found matching '{query}' in environment '{env_id or 'default'}'."
|
|
|
|
lines = [f"Found {total} dashboard(s) in environment '{env_id or 'default'}':"]
|
|
for db in dashboards:
|
|
title = db.get("title", "Untitled")
|
|
owners = ", ".join(db.get("owners", [])) or "N/A"
|
|
modified = db.get("last_modified", "")[:10] if db.get("last_modified") else "unknown"
|
|
lines.append(f" - {title}")
|
|
lines.append(f" Owners: {owners}")
|
|
lines.append(f" Last modified: {modified}")
|
|
return "\n".join(lines)
|
|
except (json.JSONDecodeError, KeyError) as e:
|
|
return f"Could not parse dashboards response: {e}"
|
|
|
|
|
|
# ── Tool: get_health_summary ──
|
|
class HealthSummaryInput(BaseModel):
|
|
env_id: str | None = Field(default=None, description="Environment ID (e.g. 'prod', 'ss-dev')")
|
|
|
|
|
|
# @ingroup AgentChat
|
|
# @PRE User authenticated via dual-identity JWT
|
|
# @POST Returns JSON string result from FastAPI
|
|
# @SIDE_EFFECT HTTP call to FastAPI backend
|
|
@tool(args_schema=HealthSummaryInput)
|
|
async def get_health_summary(env_id: str | None = None) -> str:
|
|
"""Get system health summary — dashboard validation status, recent failures.
|
|
Pass env_id like 'prod', 'ss-dev', or 'ss-preprod' to filter by environment.
|
|
"""
|
|
resp = await _get("/api/health/summary", params={"environment_id": env_id or ""})
|
|
if resp.status_code != 200:
|
|
return f"Health check failed: HTTP {resp.status_code}"
|
|
return _trim_response(resp.text, 2000)
|
|
|
|
|
|
# ── Tool: list_environments ──
|
|
# @ingroup AgentChat
|
|
# @PRE User authenticated via dual-identity JWT
|
|
# @POST Returns JSON string result from FastAPI
|
|
# @SIDE_EFFECT HTTP call to FastAPI backend
|
|
@tool
|
|
async def list_environments() -> str:
|
|
"""List configured deployment environments."""
|
|
resp = await _get("/api/settings/environments")
|
|
return _api_result(resp)
|
|
|
|
|
|
# ── Tool: get_task_status ──
|
|
# @ingroup AgentChat
|
|
# @PRE User authenticated via dual-identity JWT
|
|
# @POST Returns JSON string result from FastAPI
|
|
# @SIDE_EFFECT HTTP call to FastAPI backend
|
|
@tool
|
|
async def get_task_status(task_id: str) -> str:
|
|
"""Check the status of a background task by its task_id."""
|
|
resp = await _get(f"/api/tasks/{task_id}")
|
|
return _api_result(resp)
|
|
|
|
|
|
@tool
|
|
async def show_capabilities() -> str:
|
|
"""Show all tools available to the agent chat."""
|
|
lines = ["Available LangChain tools:"]
|
|
for tool_obj in get_all_tools():
|
|
lines.append(f"- {tool_obj.name}: {tool_obj.description or ''}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
@tool
|
|
async def list_llm_providers() -> str:
|
|
"""List configured LLM providers."""
|
|
resp = await _get("/api/llm/providers")
|
|
return _api_result(resp)
|
|
|
|
|
|
@tool
|
|
async def get_llm_status() -> str:
|
|
"""Check whether the LLM runtime is configured and usable."""
|
|
resp = await _get("/api/llm/status")
|
|
return _api_result(resp)
|
|
|
|
|
|
class RunBackupInput(BaseModel):
|
|
environment_id: str = Field(description="Environment ID for the backup")
|
|
dashboard_id: int | None = Field(default=None, description="Optional single dashboard ID")
|
|
dashboard_ids: list[int] | None = Field(default=None, description="Optional list of dashboard IDs")
|
|
|
|
|
|
@tool(args_schema=RunBackupInput)
|
|
async def run_backup(
|
|
environment_id: str,
|
|
dashboard_id: int | None = None,
|
|
dashboard_ids: list[int] | None = None,
|
|
) -> str:
|
|
"""Run a Superset backup for an environment, optionally scoped to dashboard IDs."""
|
|
params: dict[str, Any] = {"environment_id": environment_id}
|
|
ids = _dashboard_ids(dashboard_id, dashboard_ids)
|
|
if ids:
|
|
params["dashboard_ids"] = ids
|
|
resp = await _post("/api/tasks", {"plugin_id": "superset-backup", "params": params})
|
|
return _api_result(resp, {201})
|
|
|
|
|
|
class ExecuteMigrationInput(BaseModel):
|
|
source_env_id: str = Field(description="Source environment ID")
|
|
target_env_id: str = Field(description="Target environment ID")
|
|
selected_ids: list[int] = Field(description="Dashboard IDs to migrate")
|
|
replace_db_config: bool = Field(default=False, description="Replace DB configuration during migration")
|
|
fix_cross_filters: bool = Field(default=True, description="Fix cross filters during migration")
|
|
|
|
|
|
@tool(args_schema=ExecuteMigrationInput)
|
|
async def execute_migration(
|
|
source_env_id: str,
|
|
target_env_id: str,
|
|
selected_ids: list[int],
|
|
replace_db_config: bool = False,
|
|
fix_cross_filters: bool = True,
|
|
) -> str:
|
|
"""Execute dashboard migration between two environments."""
|
|
payload = {
|
|
"source_env_id": source_env_id,
|
|
"target_env_id": target_env_id,
|
|
"selected_ids": selected_ids,
|
|
"replace_db_config": replace_db_config,
|
|
"fix_cross_filters": fix_cross_filters,
|
|
}
|
|
resp = await _post("/api/migration/execute", payload)
|
|
return _api_result(resp)
|
|
|
|
|
|
class GitDashboardInput(BaseModel):
|
|
dashboard_ref: str = Field(description="Dashboard ID, slug, or title resolvable by the Git API")
|
|
env_id: str | None = Field(default=None, description="Optional environment ID used to resolve dashboard_ref")
|
|
|
|
|
|
class CreateBranchInput(GitDashboardInput):
|
|
branch_name: str = Field(description="New branch name")
|
|
from_branch: str = Field(default="dev", description="Source branch")
|
|
|
|
|
|
@tool(args_schema=CreateBranchInput)
|
|
async def create_branch(
|
|
dashboard_ref: str,
|
|
branch_name: str,
|
|
from_branch: str = "dev",
|
|
env_id: str | None = None,
|
|
) -> str:
|
|
"""Create a branch in a dashboard Git repository."""
|
|
resp = await _post(
|
|
f"/api/git/repositories/{dashboard_ref}/branches",
|
|
{"name": branch_name, "from_branch": from_branch},
|
|
params={"env_id": env_id} if env_id else None,
|
|
)
|
|
return _api_result(resp)
|
|
|
|
|
|
class CommitChangesInput(GitDashboardInput):
|
|
message: str = Field(description="Commit message")
|
|
files: list[str] = Field(default_factory=list, description="Files to stage and commit; empty means backend default")
|
|
|
|
|
|
@tool(args_schema=CommitChangesInput)
|
|
async def commit_changes(
|
|
dashboard_ref: str,
|
|
message: str,
|
|
files: list[str] | None = None,
|
|
env_id: str | None = None,
|
|
) -> str:
|
|
"""Stage and commit changes in a dashboard Git repository."""
|
|
resp = await _post(
|
|
f"/api/git/repositories/{dashboard_ref}/commit",
|
|
{"message": message, "files": files or []},
|
|
params={"env_id": env_id} if env_id else None,
|
|
)
|
|
return _api_result(resp)
|
|
|
|
|
|
class DeployDashboardInput(GitDashboardInput):
|
|
environment_id: str = Field(description="Target deployment environment ID")
|
|
|
|
|
|
@tool(args_schema=DeployDashboardInput)
|
|
async def deploy_dashboard(
|
|
dashboard_ref: str,
|
|
environment_id: str,
|
|
env_id: str | None = None,
|
|
) -> str:
|
|
"""Deploy a dashboard from Git to a target environment."""
|
|
resp = await _post(
|
|
f"/api/git/repositories/{dashboard_ref}/deploy",
|
|
{"environment_id": environment_id},
|
|
params={"env_id": env_id} if env_id else None,
|
|
)
|
|
return _api_result(resp)
|
|
|
|
|
|
class RunLlmDocumentationInput(BaseModel):
|
|
dataset_id: str = Field(description="Dataset ID to document")
|
|
environment_id: str = Field(description="Environment ID")
|
|
provider_id: str | None = Field(default=None, description="Optional LLM provider ID; active provider is used if omitted")
|
|
|
|
|
|
@tool(args_schema=RunLlmDocumentationInput)
|
|
async def run_llm_documentation(
|
|
dataset_id: str,
|
|
environment_id: str,
|
|
provider_id: str | None = None,
|
|
) -> str:
|
|
"""Generate dataset documentation via the LLM documentation task."""
|
|
resolved_provider = await _resolve_active_provider_id(provider_id)
|
|
params = {
|
|
"dataset_id": str(dataset_id),
|
|
"environment_id": environment_id,
|
|
}
|
|
if resolved_provider:
|
|
params["provider_id"] = resolved_provider
|
|
resp = await _post("/api/tasks", {"plugin_id": "llm_documentation", "params": params})
|
|
return _api_result(resp, {201})
|
|
|
|
|
|
class RunLlmValidationInput(BaseModel):
|
|
environment_id: str = Field(description="Environment ID")
|
|
dashboard_id: int | None = Field(default=None, description="Single dashboard ID")
|
|
dashboard_ids: list[int] | None = Field(default=None, description="Dashboard IDs to validate")
|
|
dashboard_url: str | None = Field(default=None, description="Optional full Superset dashboard URL")
|
|
provider_id: str | None = Field(default=None, description="Optional LLM provider ID; active provider is used if omitted")
|
|
name: str | None = Field(default=None, description="Optional validation policy name")
|
|
policy_id: str | None = Field(default=None, description="Existing validation policy ID to run immediately")
|
|
|
|
|
|
@tool(args_schema=RunLlmValidationInput)
|
|
async def run_llm_validation(
|
|
environment_id: str,
|
|
dashboard_id: int | None = None,
|
|
dashboard_ids: list[int] | None = None,
|
|
dashboard_url: str | None = None,
|
|
provider_id: str | None = None,
|
|
name: str | None = None,
|
|
policy_id: str | None = None,
|
|
) -> str:
|
|
"""Create and immediately run an LLM dashboard validation policy, or run an existing policy."""
|
|
if policy_id:
|
|
resp = await _post(f"/api/validation-tasks/{policy_id}/run")
|
|
return _api_result(resp)
|
|
|
|
ids = _dashboard_ids(dashboard_id, dashboard_ids)
|
|
sources = []
|
|
if dashboard_url:
|
|
sources.append({"type": "dashboard_url", "value": dashboard_url})
|
|
if not ids and not sources:
|
|
return "Error: provide dashboard_id, dashboard_ids, dashboard_url, or policy_id."
|
|
|
|
resolved_provider = await _resolve_active_provider_id(provider_id)
|
|
if not resolved_provider:
|
|
return "Error: no LLM provider configured. Configure or pass provider_id."
|
|
|
|
payload = {
|
|
"name": name or f"Agent LLM validation - {environment_id}",
|
|
"environment_id": environment_id,
|
|
"dashboard_ids": [str(i) for i in ids],
|
|
"provider_id": resolved_provider,
|
|
"is_active": True,
|
|
"screenshot_enabled": True,
|
|
"logs_enabled": True,
|
|
"execute_chart_data": False,
|
|
}
|
|
if sources:
|
|
payload["sources"] = sources
|
|
|
|
create_resp = await _post("/api/validation-tasks", payload)
|
|
if create_resp.status_code != 201:
|
|
return _api_result(create_resp, {201})
|
|
try:
|
|
created = create_resp.json()
|
|
created_id = created["id"]
|
|
except (json.JSONDecodeError, KeyError) as exc:
|
|
return f"Validation policy created but response could not be parsed: {exc}. Raw: {create_resp.text}"
|
|
run_resp = await _post(f"/api/validation-tasks/{created_id}/run")
|
|
return _api_result(run_resp)
|
|
|
|
|
|
@tool
|
|
async def list_maintenance_events() -> str:
|
|
"""List active and completed maintenance events."""
|
|
resp = await _get("/api/maintenance/events")
|
|
return _api_result(resp)
|
|
|
|
|
|
class StartMaintenanceInput(BaseModel):
|
|
tables: list[str] = Field(description="Affected table names")
|
|
environment_id: str = Field(description="Target environment ID")
|
|
start_time: str = Field(description="ISO datetime for maintenance start")
|
|
end_time: str | None = Field(default=None, description="Optional ISO datetime for maintenance end")
|
|
message: str | None = Field(default=None, description="Optional banner message")
|
|
|
|
|
|
@tool(args_schema=StartMaintenanceInput)
|
|
async def start_maintenance(
|
|
tables: list[str],
|
|
environment_id: str,
|
|
start_time: str,
|
|
end_time: str | None = None,
|
|
message: str | None = None,
|
|
) -> str:
|
|
"""Start a maintenance event and apply banners to affected dashboards."""
|
|
payload = {
|
|
"tables": tables,
|
|
"environment_id": environment_id,
|
|
"start_time": start_time,
|
|
"end_time": end_time,
|
|
"message": message,
|
|
}
|
|
resp = await _post("/api/maintenance/start", payload)
|
|
return _api_result(resp, {200, 202})
|
|
|
|
|
|
class EndMaintenanceInput(BaseModel):
|
|
maintenance_id: str | None = Field(default=None, description="Maintenance event ID; omit when end_all=True")
|
|
environment_id: str | None = Field(default=None, description="Optional environment scope for end_all")
|
|
end_all: bool = Field(default=False, description="End all active maintenance events")
|
|
|
|
|
|
@tool(args_schema=EndMaintenanceInput)
|
|
async def end_maintenance(
|
|
maintenance_id: str | None = None,
|
|
environment_id: str | None = None,
|
|
end_all: bool = False,
|
|
) -> str:
|
|
"""End one maintenance event, or end all active events when end_all is true."""
|
|
if end_all:
|
|
payload = {"environment_id": environment_id} if environment_id else {}
|
|
resp = await _post("/api/maintenance/end-all", payload)
|
|
return _api_result(resp, {202})
|
|
if not maintenance_id:
|
|
return "Error: maintenance_id is required unless end_all=true."
|
|
resp = await _post(f"/api/maintenance/{maintenance_id}/end")
|
|
return _api_result(resp, {202})
|
|
|
|
|
|
# ── All available tools for the agent ──
|
|
def get_all_tools() -> list:
|
|
return [
|
|
show_capabilities,
|
|
search_dashboards,
|
|
get_health_summary,
|
|
list_environments,
|
|
get_task_status,
|
|
list_llm_providers,
|
|
get_llm_status,
|
|
create_branch,
|
|
commit_changes,
|
|
deploy_dashboard,
|
|
execute_migration,
|
|
run_backup,
|
|
run_llm_validation,
|
|
run_llm_documentation,
|
|
list_maintenance_events,
|
|
start_maintenance,
|
|
end_maintenance,
|
|
]
|
|
|
|
|
|
def get_tools_for_query(query: str, *, prefetch_available: bool = False) -> list:
|
|
"""Return a compact, intent-scoped tool set to keep small-context models usable."""
|
|
text = (query or "").lower()
|
|
selected = [show_capabilities]
|
|
matched_intent = False
|
|
|
|
if any(word in text for word in ["инструмент", "tool", "capabilit", "умеешь", "можешь"]):
|
|
return selected
|
|
|
|
if any(word in text for word in ["дашборд", "dashboard", "dashboards", "дашборды"]):
|
|
matched_intent = True
|
|
if not prefetch_available:
|
|
selected.append(search_dashboards)
|
|
if any(word in text for word in ["здоров", "health", "статус системы", "system status"]):
|
|
matched_intent = True
|
|
selected.append(get_health_summary)
|
|
if any(word in text for word in ["окруж", "environment", "env"]):
|
|
matched_intent = True
|
|
selected.append(list_environments)
|
|
if any(word in text for word in ["задач", "task", "таск"]):
|
|
matched_intent = True
|
|
selected.append(get_task_status)
|
|
if any(word in text for word in ["llm", "provider", "провайдер", "модель"]):
|
|
matched_intent = True
|
|
selected.extend([list_llm_providers, get_llm_status])
|
|
if any(word in text for word in ["branch", "ветк"]):
|
|
matched_intent = True
|
|
selected.append(create_branch)
|
|
if any(word in text for word in ["commit", "коммит"]):
|
|
matched_intent = True
|
|
selected.append(commit_changes)
|
|
if any(word in text for word in ["deploy", "депло", "разверн"]):
|
|
matched_intent = True
|
|
selected.append(deploy_dashboard)
|
|
if any(word in text for word in ["миграц", "migration", "migrate"]):
|
|
matched_intent = True
|
|
selected.append(execute_migration)
|
|
if any(word in text for word in ["backup", "бэкап", "резерв"]):
|
|
matched_intent = True
|
|
selected.append(run_backup)
|
|
if any(word in text for word in ["валидац", "validation", "validate"]):
|
|
matched_intent = True
|
|
selected.append(run_llm_validation)
|
|
if any(word in text for word in ["документ", "documentation", "docs"]):
|
|
matched_intent = True
|
|
selected.append(run_llm_documentation)
|
|
if any(word in text for word in ["maintenance", "обслуж", "баннер"]):
|
|
matched_intent = True
|
|
selected.extend([list_maintenance_events, start_maintenance, end_maintenance])
|
|
|
|
if len(selected) == 1 and not matched_intent:
|
|
selected.extend([search_dashboards, get_health_summary, list_environments, get_task_status])
|
|
|
|
unique = {}
|
|
for tool_obj in selected:
|
|
unique[tool_obj.name] = tool_obj
|
|
return list(unique.values())
|
|
# #endregion AgentChat.Tools
|