[eric] agents: gated TTFT phase probes (OSW_TTFT_PROBE) for latency profiling

This commit is contained in:
ciregenz
2026-07-03 02:23:32 -07:00
parent f3c53ea3cf
commit ab0caa1054
3 changed files with 33 additions and 1 deletions
+5 -1
View File
@@ -28,6 +28,7 @@ from backend.apps.agents.manager.RunSupport import RunSupport
from backend.apps.agents.manager.run.handle_run_error import handle_run_error
from backend.apps.agents.manager.run.TurnRunner import TurnRunner
from backend.apps.agents.manager.run.RunOptions import RunOptions
from backend.apps.agents.manager.ttft_probe import ttft_probe
logger = logging.getLogger(__name__)
@@ -51,13 +52,15 @@ class AgentManager(SessionLifecycle, SessionPersistence, Messaging, SessionContr
session = self.sessions.get(session_id)
if not session:
return
ttft_probe(session_id, "loop_start", fork=fork_session, model=session.model, msgs=len(session.messages))
from backend.apps.agents.providers.registry import get_api_type as p_get_api_type
p_api = p_get_api_type(session.model)
prompt_content = self.build_prompt_content(
prompt, images, context_paths, forced_tools, attached_skills,
api_type=p_api, model=session.model,
)
ttft_probe(session_id, "prompt_built")
try:
# SDK presence check: fall to mock mode here, before the options build, so a missing SDK is a clean mock run, not an error card. The real use is in run_options / turn_runner (lazy-imported there).
@@ -88,6 +91,7 @@ class AgentManager(SessionLifecycle, SessionPersistence, Messaging, SessionContr
session, session_id, prompt, prompt_content, builtin_perms,
selected_browser_ids, selected_app_output_ids, selected_setting_ids,
fork_session, p_router_model_id, p_api_type_for_session)
ttft_probe(session_id, "options_built")
resolved_model = p_router_model_id
api_type = p_api_type_for_session
@@ -16,6 +16,7 @@ from backend.apps.agents.manager.streaming.state import ThinkingState, TurnState
from backend.apps.agents.manager.streaming.handle_stream_event import handle_stream_event
from backend.apps.agents.manager.streaming.handle_assistant_message import handle_assistant_message
from backend.apps.agents.manager.streaming.handle_result_message import handle_result_message
from backend.apps.agents.manager.ttft_probe import ttft_probe
from backend.apps.agents.manager.streaming import thinking as thinking_mod
from backend.apps.settings.models import AppSettings
@@ -35,6 +36,7 @@ class TurnRunner(AgentManagerProtocol):
global_settings: AppSettings) -> None:
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage
from claude_agent_sdk.types import StreamEvent, SystemMessage
ttft_probe(session_id, "query_enter")
async def prompt_stream():
yield {
@@ -88,6 +90,7 @@ class TurnRunner(AgentManagerProtocol):
logger.exception("pre-emit thinking pill failed; continuing")
if turn.first_event:
ttft_probe(session_id, "first_event", type=type(message).__name__)
logger.info(f"[MCP-DEBUG] First event received: {type(message).__name__}")
turn.first_event = False
+25
View File
@@ -0,0 +1,25 @@
"""Temporary time-to-first-token phase probe for the send->first-token A/B sweep. A no-op unless
OSW_TTFT_PROBE=1, so it never spams a normal run. Strip once the persistent-client work lands."""
import logging
import os
import time
from typeguard import typechecked
logger = logging.getLogger(__name__)
P_TTFT_ENABLED = os.environ.get("OSW_TTFT_PROBE") == "1"
@typechecked
def ttft_probe(session_id: str, phase: str, **extra: object) -> None:
"""One monotonic phase stamp for the TTFT breakdown; the A/B parser reads `phase=<name> mono=<t>`.
A no-op unless OSW_TTFT_PROBE=1, and it swallows any error so instrumentation can NEVER break a turn."""
if not P_TTFT_ENABLED:
return
try:
tail = " ".join(f"{k}={v}" for k, v in extra.items())
logger.warning(f"[TTFT] sid={session_id} phase={phase} mono={time.monotonic():.4f} {tail}".rstrip())
except Exception:
pass