fix(agent): unify logging API + add molecular-cot coverage to agent module
Phase 1 — API unification: - Replace direct log() from cot_logger with canonical logger.reason/reflect/explore across _confirmation.py, _persistence.py, _tool_resolver.py, app.py Phase 2 — Gap filling: - tools.py: add REASON/REFLECT/EXPLORE to 17 C3 tool functions (was 0 logs) - app.py agent_handler (C4): add lifecycle REASON on entry, REFLECT on exit, EXPLORE on OutputParserException + general exception - langgraph_setup.py create_agent (C4): add REASON with model/config_source, EXPLORE on env-var/InMemorySaver fallback, REFLECT on graph compilation - _tool_resolver.py infer_tool_from_text (C3, 13 branches): REASON on inference - _persistence.py: REFLECT on save_conversation success, EXPLORE on prefetch Phase 3 — Plain log migration: - middleware.py: plain logger.info() -> logger.reason() - run.py: 7 plain logger.info/warning/error -> molecular REASON/EXPLORE Phase 4 — Cleanup: - cot_logger.py: deprecate MarkerLogger (@DEPRECATED + @REPLACED_BY) - molecular-cot-logging SKILL.md: remove cot_span Section IV (never implemented), renumber sections V-VII -> IV-VI, add cot_span rejection rationale Verification: pytest 27/27, axiom rebuild 5844/2993/0 warnings
This commit is contained in:
@@ -14,6 +14,7 @@ from langchain_core.tools import tool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.agent.context import get_service_jwt, get_user_jwt
|
||||
from src.core.logger import logger
|
||||
|
||||
FASTAPI_URL = os.getenv("FASTAPI_URL", "http://localhost:8000")
|
||||
TOOL_RESPONSE_LIMIT = 4000
|
||||
@@ -178,9 +179,15 @@ class SearchDashboardsInput(BaseModel):
|
||||
@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."""
|
||||
logger.reason("Search dashboards", payload={"query": query, "env_id": env_id},
|
||||
extra={"src": "AgentChat.Tools.SearchDashboards"})
|
||||
params = {"q": query, "env_id": env_id or ""}
|
||||
resp = await _get("/api/dashboards", params=params)
|
||||
if resp.status_code != 200:
|
||||
logger.explore("Dashboard search failed",
|
||||
payload={"status": resp.status_code, "query": query},
|
||||
error=resp.text[:200],
|
||||
extra={"src": "AgentChat.Tools.SearchDashboards"})
|
||||
return f"Error {resp.status_code}: Unable to fetch dashboards. {resp.text}"
|
||||
|
||||
try:
|
||||
@@ -189,6 +196,9 @@ async def search_dashboards(query: str, env_id: str | None = None) -> str:
|
||||
total = data.get("total", 0)
|
||||
|
||||
if total == 0:
|
||||
logger.reflect("No dashboards found",
|
||||
payload={"query": query, "total": 0},
|
||||
extra={"src": "AgentChat.Tools.SearchDashboards"})
|
||||
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'}':"]
|
||||
@@ -199,8 +209,14 @@ async def search_dashboards(query: str, env_id: str | None = None) -> str:
|
||||
lines.append(f" - {title}")
|
||||
lines.append(f" Owners: {owners}")
|
||||
lines.append(f" Last modified: {modified}")
|
||||
logger.reflect("Dashboards listed",
|
||||
payload={"query": query, "total": total},
|
||||
extra={"src": "AgentChat.Tools.SearchDashboards"})
|
||||
return "\n".join(lines)
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
logger.explore("Failed to parse dashboards response",
|
||||
payload={"query": query}, error=str(e),
|
||||
extra={"src": "AgentChat.Tools.SearchDashboards"})
|
||||
return f"Could not parse dashboards response: {e}"
|
||||
# #endregion AgentChat.Tools.SearchDashboards
|
||||
|
||||
@@ -220,9 +236,18 @@ class HealthSummaryInput(BaseModel):
|
||||
@tool(args_schema=HealthSummaryInput)
|
||||
async def get_health_summary(env_id: str | None = None) -> str:
|
||||
"""Get system health summary — dashboard validation status, recent failures."""
|
||||
logger.reason("Get health summary", payload={"env_id": env_id},
|
||||
extra={"src": "AgentChat.Tools.HealthSummary"})
|
||||
resp = await _get("/api/health/summary", params={"environment_id": env_id or ""})
|
||||
if resp.status_code != 200:
|
||||
logger.explore("Health check failed",
|
||||
payload={"status": resp.status_code},
|
||||
error=resp.text[:200],
|
||||
extra={"src": "AgentChat.Tools.HealthSummary"})
|
||||
return f"Health check failed: HTTP {resp.status_code}"
|
||||
logger.reflect("Health summary retrieved",
|
||||
payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.HealthSummary"})
|
||||
return _trim_response(resp.text, 2000)
|
||||
# #endregion AgentChat.Tools.HealthSummary
|
||||
|
||||
@@ -236,8 +261,11 @@ async def get_health_summary(env_id: str | None = None) -> str:
|
||||
@tool
|
||||
async def list_environments() -> str:
|
||||
"""List configured deployment environments."""
|
||||
logger.reason("List environments", extra={"src": "AgentChat.Tools.ListEnvironments"})
|
||||
resp = await _get("/api/settings/environments")
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Environments listed", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.ListEnvironments"})
|
||||
return _trim_response(_safe_json_text(result))
|
||||
# #endregion AgentChat.Tools.ListEnvironments
|
||||
|
||||
@@ -251,8 +279,13 @@ async def list_environments() -> str:
|
||||
@tool
|
||||
async def get_task_status(task_id: str) -> str:
|
||||
"""Check the status of a background task by its task_id."""
|
||||
logger.reason("Get task status", payload={"task_id": task_id},
|
||||
extra={"src": "AgentChat.Tools.TaskStatus"})
|
||||
resp = await _get(f"/api/tasks/{task_id}")
|
||||
return _api_result(resp)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Task status retrieved", payload={"task_id": task_id, "status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.TaskStatus"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.TaskStatus
|
||||
|
||||
|
||||
@@ -278,8 +311,12 @@ async def show_capabilities() -> str:
|
||||
@tool
|
||||
async def list_llm_providers() -> str:
|
||||
"""List configured LLM providers."""
|
||||
logger.reason("List LLM providers", extra={"src": "AgentChat.Tools.ListLlmProviders"})
|
||||
resp = await _get("/api/llm/providers")
|
||||
return _api_result(resp)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("LLM providers listed", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.ListLlmProviders"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.ListLlmProviders
|
||||
|
||||
|
||||
@@ -292,8 +329,12 @@ async def list_llm_providers() -> str:
|
||||
@tool
|
||||
async def get_llm_status() -> str:
|
||||
"""Check whether the LLM runtime is configured and usable."""
|
||||
logger.reason("Get LLM status", extra={"src": "AgentChat.Tools.LlmStatus"})
|
||||
resp = await _get("/api/llm/status")
|
||||
return _api_result(resp)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("LLM status retrieved", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.LlmStatus"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.LlmStatus
|
||||
|
||||
|
||||
@@ -318,12 +359,17 @@ async def run_backup(
|
||||
dashboard_ids: list[int] | None = None,
|
||||
) -> str:
|
||||
"""Run a Superset backup for an environment, optionally scoped to dashboard IDs."""
|
||||
vars = {"env_id": environment_id, "dashboard_id": dashboard_id, "dashboard_ids": dashboard_ids}
|
||||
logger.reason("Run backup", payload=vars, extra={"src": "AgentChat.Tools.RunBackup"})
|
||||
params: dict[str, Any] = {"environment_id": environment_id}
|
||||
ids = _dashboard_ids(dashboard_id, dashboard_ids)
|
||||
if ids:
|
||||
params["dashboard_ids"] = ids
|
||||
ids_l = _dashboard_ids(dashboard_id, dashboard_ids)
|
||||
if ids_l:
|
||||
params["dashboard_ids"] = ids_l
|
||||
resp = await _post("/api/tasks", {"plugin_id": "superset-backup", "params": params})
|
||||
return _api_result(resp, {201})
|
||||
result = _api_result(resp, {201})
|
||||
logger.reflect("Backup task created", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.RunBackup"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.RunBackup
|
||||
|
||||
|
||||
@@ -352,6 +398,9 @@ async def execute_migration(
|
||||
fix_cross_filters: bool = True,
|
||||
) -> str:
|
||||
"""Execute dashboard migration between two environments."""
|
||||
logger.reason("Execute migration",
|
||||
payload={"source": source_env_id, "target": target_env_id, "ids_count": len(selected_ids)},
|
||||
extra={"src": "AgentChat.Tools.ExecuteMigration"})
|
||||
payload = {
|
||||
"source_env_id": source_env_id,
|
||||
"target_env_id": target_env_id,
|
||||
@@ -360,7 +409,10 @@ async def execute_migration(
|
||||
"fix_cross_filters": fix_cross_filters,
|
||||
}
|
||||
resp = await _post("/api/migration/execute", payload)
|
||||
return _api_result(resp)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Migration executed", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.ExecuteMigration"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.ExecuteMigration
|
||||
|
||||
|
||||
@@ -394,12 +446,18 @@ async def create_branch(
|
||||
env_id: str | None = None,
|
||||
) -> str:
|
||||
"""Create a branch in a dashboard Git repository."""
|
||||
logger.reason("Create branch",
|
||||
payload={"dashboard_ref": dashboard_ref, "branch_name": branch_name, "from_branch": from_branch},
|
||||
extra={"src": "AgentChat.Tools.CreateBranch"})
|
||||
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)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Branch created", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.CreateBranch"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.CreateBranch
|
||||
|
||||
|
||||
@@ -424,12 +482,18 @@ async def commit_changes(
|
||||
env_id: str | None = None,
|
||||
) -> str:
|
||||
"""Stage and commit changes in a dashboard Git repository."""
|
||||
logger.reason("Commit changes",
|
||||
payload={"dashboard_ref": dashboard_ref, "message": message[:80]},
|
||||
extra={"src": "AgentChat.Tools.CommitChanges"})
|
||||
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)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Changes committed", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.CommitChanges"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.CommitChanges
|
||||
|
||||
|
||||
@@ -452,12 +516,18 @@ async def deploy_dashboard(
|
||||
env_id: str | None = None,
|
||||
) -> str:
|
||||
"""Deploy a dashboard from Git to a target environment."""
|
||||
logger.reason("Deploy dashboard",
|
||||
payload={"dashboard_ref": dashboard_ref, "target_env": environment_id},
|
||||
extra={"src": "AgentChat.Tools.DeployDashboard"})
|
||||
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)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Dashboard deployed", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.DeployDashboard"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.DeployDashboard
|
||||
|
||||
|
||||
@@ -482,6 +552,9 @@ async def run_llm_documentation(
|
||||
provider_id: str | None = None,
|
||||
) -> str:
|
||||
"""Generate dataset documentation via the LLM documentation task."""
|
||||
logger.reason("Run LLM documentation",
|
||||
payload={"dataset_id": dataset_id, "environment_id": environment_id},
|
||||
extra={"src": "AgentChat.Tools.LlmDocumentation"})
|
||||
resolved_provider = await _resolve_active_provider_id(provider_id)
|
||||
params = {
|
||||
"dataset_id": str(dataset_id),
|
||||
@@ -490,7 +563,11 @@ async def run_llm_documentation(
|
||||
if resolved_provider:
|
||||
params["provider_id"] = resolved_provider
|
||||
resp = await _post("/api/tasks", {"plugin_id": "llm_documentation", "params": params})
|
||||
return _api_result(resp, {201})
|
||||
result = _api_result(resp, {201})
|
||||
logger.reflect("LLM documentation task created",
|
||||
payload={"status": resp.status_code, "provider_id": resolved_provider},
|
||||
extra={"src": "AgentChat.Tools.LlmDocumentation"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.LlmDocumentation
|
||||
|
||||
|
||||
@@ -524,10 +601,19 @@ async def run_llm_validation(
|
||||
) -> str:
|
||||
"""Create and immediately run an LLM dashboard validation policy, or run an existing policy."""
|
||||
if policy_id:
|
||||
logger.reason("Run existing validation policy",
|
||||
payload={"policy_id": policy_id},
|
||||
extra={"src": "AgentChat.Tools.LlmValidation"})
|
||||
resp = await _post(f"/api/validation-tasks/{policy_id}/run")
|
||||
return _api_result(resp)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Validation policy run", payload={"policy_id": policy_id, "status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.LlmValidation"})
|
||||
return result
|
||||
|
||||
ids = _dashboard_ids(dashboard_id, dashboard_ids)
|
||||
logger.reason("Create LLM validation policy",
|
||||
payload={"environment_id": environment_id, "dashboard_count": len(ids)},
|
||||
extra={"src": "AgentChat.Tools.LlmValidation"})
|
||||
sources = []
|
||||
if dashboard_url:
|
||||
sources.append({"type": "dashboard_url", "value": dashboard_url})
|
||||
@@ -560,7 +646,11 @@ async def run_llm_validation(
|
||||
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)
|
||||
result = _api_result(run_resp)
|
||||
logger.reflect("Validation policy created and run",
|
||||
payload={"policy_id": created_id, "status": run_resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.LlmValidation"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.LlmValidation
|
||||
|
||||
|
||||
@@ -573,8 +663,12 @@ async def run_llm_validation(
|
||||
@tool
|
||||
async def list_maintenance_events() -> str:
|
||||
"""List active and completed maintenance events."""
|
||||
logger.reason("List maintenance events", extra={"src": "AgentChat.Tools.ListMaintenance"})
|
||||
resp = await _get("/api/maintenance/events")
|
||||
return _api_result(resp)
|
||||
result = _api_result(resp)
|
||||
logger.reflect("Maintenance events listed", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.ListMaintenance"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.ListMaintenance
|
||||
|
||||
|
||||
@@ -603,6 +697,9 @@ async def start_maintenance(
|
||||
message: str | None = None,
|
||||
) -> str:
|
||||
"""Start a maintenance event and apply banners to affected dashboards."""
|
||||
logger.reason("Start maintenance",
|
||||
payload={"environment_id": environment_id, "tables": tables},
|
||||
extra={"src": "AgentChat.Tools.StartMaintenance"})
|
||||
payload = {
|
||||
"tables": tables,
|
||||
"environment_id": environment_id,
|
||||
@@ -611,7 +708,10 @@ async def start_maintenance(
|
||||
"message": message,
|
||||
}
|
||||
resp = await _post("/api/maintenance/start", payload)
|
||||
return _api_result(resp, {200, 202})
|
||||
result = _api_result(resp, {200, 202})
|
||||
logger.reflect("Maintenance started", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.StartMaintenance"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.StartMaintenance
|
||||
|
||||
|
||||
@@ -637,13 +737,25 @@ async def end_maintenance(
|
||||
) -> str:
|
||||
"""End one maintenance event, or end all active events when end_all is true."""
|
||||
if end_all:
|
||||
logger.reason("End all maintenance events",
|
||||
payload={"environment_id": environment_id},
|
||||
extra={"src": "AgentChat.Tools.EndMaintenance"})
|
||||
payload = {"environment_id": environment_id} if environment_id else {}
|
||||
resp = await _post("/api/maintenance/end-all", payload)
|
||||
return _api_result(resp, {202})
|
||||
result = _api_result(resp, {202})
|
||||
logger.reflect("All maintenance ended", payload={"status": resp.status_code},
|
||||
extra={"src": "AgentChat.Tools.EndMaintenance"})
|
||||
return result
|
||||
if not maintenance_id:
|
||||
return "Error: maintenance_id is required unless end_all=true."
|
||||
logger.reason("End maintenance event",
|
||||
payload={"maintenance_id": maintenance_id},
|
||||
extra={"src": "AgentChat.Tools.EndMaintenance"})
|
||||
resp = await _post(f"/api/maintenance/{maintenance_id}/end")
|
||||
return _api_result(resp, {202})
|
||||
result = _api_result(resp, {202})
|
||||
logger.reflect("Maintenance ended", payload={"status": resp.status_code, "maintenance_id": maintenance_id},
|
||||
extra={"src": "AgentChat.Tools.EndMaintenance"})
|
||||
return result
|
||||
# #endregion AgentChat.Tools.EndMaintenance
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user