60 lines
2.5 KiB
Python
60 lines
2.5 KiB
Python
# backend/src/agent/middleware.py
|
|
# #region AgentChat.Middleware [C:3] [TYPE Module] [SEMANTICS agent-chat,middleware,logging,audit]
|
|
# @defgroup AgentChat Audit logging and confirmation risk middleware for LangGraph agent.
|
|
# @BRIEF LoggingMiddleware writes tool-call events to assistant_audit table.
|
|
# @RELATION DEPENDS_ON -> [AssistantAuditRecord]
|
|
# @RATIONALE FR-024: All agent interactions must be logged for auditability.
|
|
# @REJECTED ConfirmationRiskMiddleware rejected — LangGraph interrupt_before=DANGEROUS_TOOLS handles HITL natively.
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
from src.agent.context import get_user_jwt
|
|
from src.agent.tools import _redact_sensitive_fields
|
|
from src.core.logger import logger
|
|
|
|
# #region AgentChat.Middleware.LoggingMiddleware [C:3] [TYPE Function] [SEMANTICS audit,tool,logging]
|
|
# @ingroup AgentChat
|
|
# @BRIEF Log every tool-call event to assistant_audit table with user context.
|
|
# @PRE agent event has 'event' key with type on_tool_start/on_tool_end/on_tool_error.
|
|
# @POST Audit record written to assistant_audit table (async, non-blocking).
|
|
# @SIDE_EFFECT Writes to assistant_audit table via FastAPI REST call.
|
|
# @RELATION DISPATCHES -> [get_assistant_audit]
|
|
|
|
async def log_tool_event(event: dict, conversation_id: str) -> None:
|
|
"""Log a tool-call event to the audit trail.
|
|
|
|
Args:
|
|
event: LangGraph event dict with 'event', 'name', and 'data' keys.
|
|
conversation_id: Current conversation thread ID.
|
|
"""
|
|
kind = event.get("event", "")
|
|
tool_name = event.get("name", "unknown")
|
|
user_jwt = get_user_jwt()
|
|
|
|
audit_payload = {
|
|
"event_type": kind,
|
|
"tool": tool_name,
|
|
"conversation_id": conversation_id,
|
|
"user_jwt_present": bool(user_jwt),
|
|
"timestamp": datetime.now(UTC).isoformat(),
|
|
}
|
|
|
|
if "data" in event:
|
|
data = event["data"]
|
|
if kind == "on_tool_start":
|
|
raw_input = data.get("input", "")
|
|
audit_payload["input"] = str(_redact_sensitive_fields(raw_input))[:500]
|
|
elif kind == "on_tool_error":
|
|
audit_payload["error"] = str(data.get("error", ""))[:500]
|
|
|
|
logger.reason(
|
|
"Tool audit event",
|
|
payload=audit_payload,
|
|
extra={"src": "AgentChat.Middleware.LoggingMiddleware"},
|
|
)
|
|
|
|
# TODO: Async write to assistant_audit table via REST call to FastAPI
|
|
# This is intentionally fire-and-forget — audit failures must not block tool execution
|
|
# #endregion AgentChat.Middleware.LoggingMiddleware
|
|
# #endregion AgentChat.Middleware
|