diff --git a/headroom/proxy/helpers.py b/headroom/proxy/helpers.py index 29364ac0d..f62fcb527 100644 --- a/headroom/proxy/helpers.py +++ b/headroom/proxy/helpers.py @@ -60,6 +60,12 @@ from headroom.proxy.tool_injection_config import ( from headroom.proxy.tool_injection_config import ( get_tool_tracker_max_sessions as _get_tool_tracker_max_sessions, ) +from headroom.proxy.tool_injection_logging import ( + ToolInjectionDecision, +) +from headroom.proxy.tool_injection_logging import ( + log_tool_injection_decision as _log_tool_injection_decision, +) from headroom.proxy.tool_injection_tracker import SessionToolTracker as _SessionToolTracker from headroom.proxy.tool_name_policy import extract_tool_name @@ -1930,12 +1936,7 @@ def log_tool_injection_decision( *, provider: str, session_id: str | None, - decision: Literal[ - "inject_first_time", - "inject_sticky_replay", - "skip", - "skip_disabled_via_env", - ], + decision: ToolInjectionDecision, tool_definition_bytes_count: int, request_id: str | None, ) -> None: @@ -1947,14 +1948,13 @@ def log_tool_injection_decision( tool definition contents (might contain user-specific schemas) per constraint #11. """ - logger.info( - "event=tool_injection_decision provider=%s session_id=%s " - "decision=%s tool_definition_bytes_count=%d request_id=%s", - provider, - session_id or "", - decision, - tool_definition_bytes_count, - request_id or "", + _log_tool_injection_decision( + logger=logger, + provider=provider, + session_id=session_id, + decision=decision, + tool_definition_bytes_count=tool_definition_bytes_count, + request_id=request_id, ) diff --git a/headroom/proxy/tool_injection_logging.py b/headroom/proxy/tool_injection_logging.py new file mode 100644 index 000000000..b6e9baa1d --- /dev/null +++ b/headroom/proxy/tool_injection_logging.py @@ -0,0 +1,35 @@ +"""Logging policy for proxy tool-injection decisions.""" + +from __future__ import annotations + +import logging +from typing import Literal + +ToolInjectionDecision = Literal[ + "inject_first_time", + "inject_sticky_replay", + "skip", + "skip_disabled_via_env", +] + + +def log_tool_injection_decision( + *, + logger: logging.Logger, + provider: str, + session_id: str | None, + decision: ToolInjectionDecision, + tool_definition_bytes_count: int, + request_id: str | None, +) -> None: + """Emit a cache-affecting tool-injection decision without tool contents.""" + + logger.info( + "event=tool_injection_decision provider=%s session_id=%s " + "decision=%s tool_definition_bytes_count=%d request_id=%s", + provider, + session_id or "", + decision, + tool_definition_bytes_count, + request_id or "", + ) diff --git a/tests/test_tool_injection_logging.py b/tests/test_tool_injection_logging.py new file mode 100644 index 000000000..568d05c73 --- /dev/null +++ b/tests/test_tool_injection_logging.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import logging + +import pytest + +from headroom.proxy.helpers import log_tool_injection_decision as helper_log_tool_injection_decision +from headroom.proxy.tool_injection_logging import log_tool_injection_decision + + +def test_logs_tool_injection_decision_without_contents(caplog: pytest.LogCaptureFixture) -> None: + logger = logging.getLogger("headroom.proxy.test_tool_injection_logging") + + with caplog.at_level(logging.INFO, logger=logger.name): + log_tool_injection_decision( + logger=logger, + provider="anthropic", + session_id="session-1", + decision="inject_sticky_replay", + tool_definition_bytes_count=123, + request_id="req-1", + ) + + assert len(caplog.records) == 1 + message = caplog.records[0].getMessage() + assert "event=tool_injection_decision" in message + assert "provider=anthropic" in message + assert "session_id=session-1" in message + assert "decision=inject_sticky_replay" in message + assert "tool_definition_bytes_count=123" in message + assert "request_id=req-1" in message + assert "memory_save" not in message + assert "headroom_retrieve" not in message + + +def test_helper_wrapper_uses_proxy_logger(caplog: pytest.LogCaptureFixture) -> None: + with caplog.at_level(logging.INFO, logger="headroom.proxy"): + helper_log_tool_injection_decision( + provider="openai", + session_id=None, + decision="skip", + tool_definition_bytes_count=0, + request_id=None, + ) + + assert len(caplog.records) == 1 + assert caplog.records[0].name == "headroom.proxy" + assert "session_id= decision=skip" in caplog.records[0].getMessage()