Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions studio/backend/core/inference/api_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import annotations

import os
import threading
import time
import uuid
Expand All @@ -18,6 +19,17 @@
_MAX_REPLY_CHARS = 12000
_PREVIEW_CHARS = 360

# Opt-in kill switch for the in-memory API monitor. Users who run Studio purely
# as an inference API and handle logging/telemetry elsewhere can set this to turn
# the monitor into a no-op (nothing is recorded and the Monitor view stays empty).
# Off by default, so existing behaviour is unchanged.
_DISABLE_ENV = "UNSLOTH_STUDIO_DISABLE_API_MONITOR"
_TRUE_VALUES = frozenset({"1", "true", "yes", "on"})


def _api_monitor_disabled() -> bool:
return os.environ.get(_DISABLE_ENV, "").strip().lower() in _TRUE_VALUES


def _trim(text: Optional[str], limit: int) -> str:
if not text:
Expand Down Expand Up @@ -93,10 +105,16 @@ def snapshot(self, *, include_details: bool = True) -> dict[str, Any]:


class ApiMonitor:
def __init__(self, max_entries: int = _MAX_ENTRIES):
def __init__(
self,
max_entries: int = _MAX_ENTRIES,
*,
enabled: bool = True,
):
self._entries: deque[ApiMonitorEntry] = deque()
self._max_entries = max(0, max_entries)
self._lock = threading.Lock()
self._enabled = enabled

def start(
self,
Expand All @@ -108,6 +126,11 @@ def start(
context_length: Optional[int] = None,
subject: Optional[str] = None,
) -> str:
# Disabled monitor is a no-op: return a falsy id so every downstream
# mutator (append_reply/set_reply/set_usage/finish/fail) short-circuits
# on its `if not entry_id` guard and no entry is ever recorded.
if not self._enabled:
return ""
now = time.time()
entry = ApiMonitorEntry(
id = f"apireq_{uuid.uuid4().hex[:12]}",
Expand Down Expand Up @@ -292,4 +315,4 @@ def _trim_terminal_locked(self) -> None:
self._entries = kept


api_monitor = ApiMonitor()
api_monitor = ApiMonitor(enabled = not _api_monitor_disabled())
58 changes: 58 additions & 0 deletions studio/backend/tests/test_api_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,61 @@ def test_api_monitor_append_reply_exact_cap_then_more_marks_truncated():
monitor.append_reply(entry_id, "y")
reply = monitor.snapshot()[0]["reply"]
assert len(reply) == m._MAX_REPLY_CHARS and reply.endswith("...")


def test_api_monitor_enabled_by_default_records():
# Guard against accidentally flipping the default to disabled.
monitor = ApiMonitor(max_entries = 3)
entry_id = monitor.start(
endpoint = "/v1/chat/completions",
method = "POST",
model = "local-model",
prompt = "user: hello",
)
assert entry_id
assert len(monitor.snapshot()) == 1


def test_api_monitor_disabled_is_noop():
monitor = ApiMonitor(max_entries = 3, enabled = False)

entry_id = monitor.start(
endpoint = "/v1/chat/completions",
method = "POST",
model = "local-model",
prompt = "user: hello",
context_length = 100,
)
# A disabled monitor returns a falsy id and records nothing.
assert entry_id == ""

# Every mutator must be a safe no-op on the falsy id.
monitor.append_reply(entry_id, "hi")
monitor.set_reply(entry_id, "hi")
monitor.set_usage(entry_id, prompt_tokens = 4, completion_tokens = 6)
monitor.finish(entry_id)
monitor.fail(entry_id, "boom")

assert monitor.snapshot() == []
assert monitor.active_count() == 0
assert monitor.get(entry_id) is None


def test_api_monitor_disable_env_var_truthy(monkeypatch):
import core.inference.api_monitor as m
for value in ("1", "true", "yes", "on", "TRUE", "On", " yes "):
monkeypatch.setenv(m._DISABLE_ENV, value)
assert m._api_monitor_disabled() is True, value


def test_api_monitor_disable_env_var_falsy(monkeypatch):
import core.inference.api_monitor as m
for value in ("", "0", "false", "no", "off", "disabled"):
monkeypatch.setenv(m._DISABLE_ENV, value)
assert m._api_monitor_disabled() is False, value


def test_api_monitor_disable_env_var_unset(monkeypatch):
import core.inference.api_monitor as m
monkeypatch.delenv(m._DISABLE_ENV, raising = False)
assert m._api_monitor_disabled() is False
Loading