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:
2026-06-30 15:48:46 +03:00
parent 131c7cdfa4
commit a0619ca049
10 changed files with 359 additions and 148 deletions

View File

@@ -9,7 +9,7 @@
from typing import Any
from src.agent.tools import get_all_tools
from src.core.cot_logger import log
from src.core.logger import logger
# #region AgentChat.ToolResolver.Sets [C:1] [TYPE Constants] [SEMANTICS agent-chat,tools,sets]
# @ingroup AgentChat
@@ -56,7 +56,11 @@ def known_agent_tool_names() -> set[str]:
try:
return {str(tool_obj.name) for tool_obj in get_all_tools() if getattr(tool_obj, "name", None)}
except Exception as exc:
log("AgentChat.ToolResolver", "EXPLORE", "tool catalog lookup failed", {"error": str(exc)})
logger.explore(
"tool catalog lookup failed",
payload={"error": str(exc)},
extra={"src": "AgentChat.ToolResolver"},
)
return _SAFE_AGENT_TOOLS | _GUARDED_AGENT_TOOLS | _DANGEROUS_AGENT_TOOLS
# #endregion AgentChat.ToolResolver.KnownNames
@@ -105,39 +109,46 @@ def coerce_tool_call(tool_call: Any) -> tuple[str | None, dict[str, Any]]:
# uses keyword matching to guess the user's intent and auto-trigger HITL.
def infer_tool_from_text(text: str) -> str | None:
lowered = (text or "").lower()
inferred: str | None = None
if any(word in lowered for word in ["окруж", "environment", "env"]):
return "list_environments"
if any(word in lowered for word in ["maintenance", "обслуж", "баннер"]):
inferred = "list_environments"
elif any(word in lowered for word in ["maintenance", "обслуж", "баннер"]):
if any(word in lowered for word in ["start", "созда", "запусти", "начни"]):
return "start_maintenance"
if any(word in lowered for word in ["end", "закрой", "заверши", "останов"]):
return "end_maintenance"
return "list_maintenance_events"
if any(word in lowered for word in ["дашборд", "dashboard", "dashboards", "дашборды"]):
return "search_dashboards"
if any(word in lowered for word in ["здоров", "health", "статус системы", "system status"]):
return "get_health_summary"
if any(word in lowered for word in ["задач", "task", "таск"]):
return "get_task_status"
if any(word in lowered for word in ["llm", "provider", "провайдер", "модель"]):
return "list_llm_providers"
if any(word in lowered for word in ["branch", "ветк"]):
return "create_branch"
if any(word in lowered for word in ["commit", "коммит"]):
return "commit_changes"
if any(word in lowered for word in ["deploy", "депло", "разверн"]):
return "deploy_dashboard"
if any(word in lowered for word in ["миграц", "migration", "migrate"]):
return "execute_migration"
if any(word in lowered for word in ["backup", "бэкап", "резерв"]):
return "run_backup"
if any(word in lowered for word in ["валидац", "validation", "validate"]):
return "run_llm_validation"
if any(word in lowered for word in ["документ", "documentation", "docs"]):
return "run_llm_documentation"
if any(word in lowered for word in ["инструмент", "tool", "capabilit", "умеешь", "можешь"]):
return "show_capabilities"
return None
inferred = "start_maintenance"
elif any(word in lowered for word in ["end", "закрой", "заверши", "останов"]):
inferred = "end_maintenance"
else:
inferred = "list_maintenance_events"
elif any(word in lowered for word in ["дашборд", "dashboard", "dashboards", "дашборды"]):
inferred = "search_dashboards"
elif any(word in lowered for word in ["здоров", "health", "статус системы", "system status"]):
inferred = "get_health_summary"
elif any(word in lowered for word in ["задач", "task", "таск"]):
inferred = "get_task_status"
elif any(word in lowered for word in ["llm", "provider", "провайдер", "модель"]):
inferred = "list_llm_providers"
elif any(word in lowered for word in ["branch", "ветк"]):
inferred = "create_branch"
elif any(word in lowered for word in ["commit", "коммит"]):
inferred = "commit_changes"
elif any(word in lowered for word in ["deploy", "депло", "разверн"]):
inferred = "deploy_dashboard"
elif any(word in lowered for word in ["миграц", "migration", "migrate"]):
inferred = "execute_migration"
elif any(word in lowered for word in ["backup", "бэкап", "резерв"]):
inferred = "run_backup"
elif any(word in lowered for word in ["валидац", "validation", "validate"]):
inferred = "run_llm_validation"
elif any(word in lowered for word in ["документ", "documentation", "docs"]):
inferred = "run_llm_documentation"
elif any(word in lowered for word in ["инструмент", "tool", "capabilit", "умеешь", "можешь"]):
inferred = "show_capabilities"
if inferred:
logger.reason("Tool inferred from user text",
payload={"tool": inferred, "text_preview": (text or "")[:80]},
extra={"src": "AgentChat.ToolResolver.InferFromText"})
return inferred
# #endregion AgentChat.ToolResolver.InferFromText
@@ -155,7 +166,11 @@ def extract_tool_call_from_state(state, user_text: str = "") -> tuple[str | None
if tool_name:
return (str(tool_name), tool_args)
except Exception as exc:
log("AgentChat.ToolResolver", "EXPLORE", "tool_call extraction failed", {"error": str(exc)})
logger.explore(
"tool_call extraction failed",
payload={"error": str(exc)},
extra={"src": "AgentChat.ToolResolver"},
)
if getattr(state, "next", None):
node_or_tool = str(state.next[0])
@@ -164,7 +179,11 @@ def extract_tool_call_from_state(state, user_text: str = "") -> tuple[str | None
inferred_tool = infer_tool_from_text(user_text)
if inferred_tool:
log("AgentChat.ToolResolver", "EXPLORE", "tool_call inferred from user text", {"tool": inferred_tool})
logger.explore(
"tool_call inferred from user text",
payload={"tool": inferred_tool},
extra={"src": "AgentChat.ToolResolver"},
)
return (inferred_tool, {})
return (None, {})
# #endregion AgentChat.ToolResolver.ExtractFromState