Module livekit.plugins.posthog.observer
Functions
def observe(session: Any,
api_key: str | None = None,
host: str | None = None,
session_id: str | None = None,
project_id: str | None = None) ‑> PostHogAgentObserver-
Expand source code
def observe( session: Any, api_key: Optional[str] = None, host: Optional[str] = None, session_id: Optional[str] = None, project_id: Optional[str] = None, ) -> PostHogAgentObserver: """ Convenience helper to initialize a PostHogAgentObserver and immediately attach it to an active LiveKit AgentSession or VoicePipelineAgent. """ observer = PostHogAgentObserver( api_key=api_key, host=host, session_id=session_id, project_id=project_id, ) observer.attach_to_session(session) return observerConvenience helper to initialize a PostHogAgentObserver and immediately attach it to an active LiveKit AgentSession or VoicePipelineAgent.
Classes
class PostHogAgentObserver (api_key: str | None = None,
host: str | None = None,
session_id: str | None = None,
project_id: str | None = None)-
Expand source code
class PostHogAgentObserver: """ Subscribes to LiveKit Agent metrics and emits standardized PostHog events for AI observability ($ai_generation), conversational frustration (interruptions), voice latency waterfalls (TTS, STT, EOU), Session Replay cross-linking, and session costs. """ def __init__( self, api_key: Optional[str] = None, host: Optional[str] = None, session_id: Optional[str] = None, project_id: Optional[str] = None, ): self.api_key = api_key or os.getenv("POSTHOG_API_KEY", "phc_mock_key_prism") self.host = host or os.getenv("POSTHOG_HOST", "https://us.i.posthog.com") self.session_id = session_id or f"RM_prism_{int(time.time())}" self.project_id = project_id or os.getenv("POSTHOG_PROJECT_ID", "default_proj") # Initialize PostHog client (flush_at=1 for real-time streaming telemetry) try: self.client = posthog.Posthog( project_api_key=self.api_key, host=self.host, flush_at=1, flush_interval=0.5, ) logger.info("Initialized PostHogAgentObserver with session %s", self.session_id) except Exception as e: logger.warning("Failed to initialize PostHog client, logging locally: %s", e) self.client = None # Feature flags helper self.flags = PostHogFeatureFlags(client=self.client, default_distinct_id=self.session_id) def attach_to_session(self, session: Any): """Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.""" if hasattr(session, "on"): session.on("metrics_collected", self.on_metrics_collected) logger.info("Attached PostHogAgentObserver to session.on('metrics_collected')") def link_session_replay( self, livekit_room: str, participant_identity: str, replay_id: Optional[str] = None, ) -> str: """ Cross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays. """ rep_id = replay_id or self.session_id replay_url = f"{self.host.rstrip('/')}/project/{self.project_id}/replay/{rep_id}" properties = { "$session_id": self.session_id, "livekit_room": livekit_room, "participant_identity": participant_identity, "replay_url": replay_url, "webrtc_stream_linked": True, "timestamp": time.time(), } logger.info("[PostHog SessionReplay] Linked LiveKit room '%s' to replay: %s", livekit_room, replay_url) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$session_replay_link", properties=properties, ) except Exception as e: logger.error("Failed to emit $session_replay_link event: %s", e) return replay_url def on_metrics_collected(self, metrics: Any): """Dispatches collected metrics into PostHog telemetry.""" metrics_type = getattr(metrics, "type", metrics.__class__.__name__) if "LLM" in metrics_type or hasattr(metrics, "input_tokens"): self.record_llm_metrics(metrics) elif "Interruption" in metrics_type or hasattr(metrics, "interrupted"): self.record_interruption_metrics(metrics) elif "TTS" in metrics_type or hasattr(metrics, "ttfb"): self.record_tts_metrics(metrics) elif "STT" in metrics_type: self.record_stt_metrics(metrics) elif "EOU" in metrics_type or hasattr(metrics, "end_of_utterance_delay"): self.record_eou_metrics(metrics) elif "Usage" in metrics_type or hasattr(metrics, "total_tokens"): self.record_session_usage(metrics) else: logger.debug("Captured general metric: %s", metrics_type) def record_llm_metrics(self, m: Any): """ Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation). """ input_tokens = getattr(m, "input_tokens", getattr(m, "prompt_tokens", 0)) output_tokens = getattr(m, "output_tokens", getattr(m, "completion_tokens", 0)) duration = getattr(m, "duration", getattr(m, "latency", 0.0)) ttft = getattr(m, "time_to_first_token", getattr(m, "ttft", 0.0)) model = getattr(m, "model", "gpt-4o-mini") cancelled = getattr(m, "cancelled", False) tps = (output_tokens / duration) if (duration > 0 and output_tokens > 0) else 0.0 properties = { "$session_id": self.session_id, "$ai_trace_id": getattr(m, "request_id", f"req_{int(time.time()*1000)}"), "$ai_model": model, "$ai_provider": "openai", "$ai_input_tokens": input_tokens, "$ai_output_tokens": output_tokens, "$ai_latency": round(duration, 4), "$ai_time_to_first_token": round(ttft, 4), "tokens_per_second": round(tps, 2), "cancelled": cancelled, } logger.info("[PostHog Telemetry] Emitting $ai_generation: tokens=(in:%d, out:%d) latency=%.2fs", input_tokens, output_tokens, duration) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$ai_generation", properties=properties, ) except Exception as e: logger.error("Failed to capture $ai_generation event: %s", e) def record_interruption_metrics(self, m: Any): """Records conversational interruption / user frustration event.""" properties = { "$session_id": self.session_id, "timestamp": time.time(), "speech_duration_before_cut": getattr(m, "duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_agent_interrupted") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_interrupted", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_interrupted event: %s", e) def record_tts_metrics(self, m: Any): """Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).""" properties = { "$session_id": self.session_id, "tts_ttfb": round(getattr(m, "ttfb", 0.0), 4), "tts_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "characters_count": getattr(m, "characters_count", 0), "tts_model": getattr(m, "label", "tts"), "cancelled": getattr(m, "cancelled", False), } logger.info("[PostHog Telemetry] Emitting voice_agent_tts_latency: ttfb=%.3fs duration=%.3fs", properties["tts_ttfb"], properties["tts_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_tts_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_tts_latency event: %s", e) def record_stt_metrics(self, m: Any): """Records Speech-to-Text transcription latency metrics.""" properties = { "$session_id": self.session_id, "stt_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "stt_model": getattr(m, "label", "stt"), "streamed": getattr(m, "streamed", True), } logger.info("[PostHog Telemetry] Emitting voice_agent_stt_latency: duration=%.3fs", properties["stt_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_stt_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_stt_latency event: %s", e) def record_eou_metrics(self, m: Any): """Records End-of-Utterance (endpointing) conversational turn transition delay.""" properties = { "$session_id": self.session_id, "eou_delay": round(getattr(m, "end_of_utterance_delay", 0.0), 4), "transcription_delay": round(getattr(m, "transcription_delay", 0.0), 4), "turn_completed_delay": round(getattr(m, "on_user_turn_completed_delay", 0.0), 4), } logger.info("[PostHog Telemetry] Emitting voice_agent_endpointing_delay: eou_delay=%.3fs", properties["eou_delay"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_endpointing_delay", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_endpointing_delay event: %s", e) def record_session_usage(self, m: Any): """Records cumulative session resource and token usage.""" properties = { "$session_id": self.session_id, "total_tokens": getattr(m, "total_tokens", 0), "input_tokens": getattr(m, "input_tokens", 0), "output_tokens": getattr(m, "output_tokens", 0), "audio_duration": getattr(m, "audio_duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_session_completed") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_session_completed", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_session_completed event: %s", e) def shutdown(self): """Flushes remaining events to PostHog.""" if self.client: try: self.client.flush() except Exception: passSubscribes to LiveKit Agent metrics and emits standardized PostHog events for AI observability ($ai_generation), conversational frustration (interruptions), voice latency waterfalls (TTS, STT, EOU), Session Replay cross-linking, and session costs.
Methods
def attach_to_session(self, session: Any)-
Expand source code
def attach_to_session(self, session: Any): """Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.""" if hasattr(session, "on"): session.on("metrics_collected", self.on_metrics_collected) logger.info("Attached PostHogAgentObserver to session.on('metrics_collected')")Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.
def link_session_replay(self, livekit_room: str, participant_identity: str, replay_id: str | None = None) ‑> str-
Expand source code
def link_session_replay( self, livekit_room: str, participant_identity: str, replay_id: Optional[str] = None, ) -> str: """ Cross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays. """ rep_id = replay_id or self.session_id replay_url = f"{self.host.rstrip('/')}/project/{self.project_id}/replay/{rep_id}" properties = { "$session_id": self.session_id, "livekit_room": livekit_room, "participant_identity": participant_identity, "replay_url": replay_url, "webrtc_stream_linked": True, "timestamp": time.time(), } logger.info("[PostHog SessionReplay] Linked LiveKit room '%s' to replay: %s", livekit_room, replay_url) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$session_replay_link", properties=properties, ) except Exception as e: logger.error("Failed to emit $session_replay_link event: %s", e) return replay_urlCross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays.
def on_metrics_collected(self, metrics: Any)-
Expand source code
def on_metrics_collected(self, metrics: Any): """Dispatches collected metrics into PostHog telemetry.""" metrics_type = getattr(metrics, "type", metrics.__class__.__name__) if "LLM" in metrics_type or hasattr(metrics, "input_tokens"): self.record_llm_metrics(metrics) elif "Interruption" in metrics_type or hasattr(metrics, "interrupted"): self.record_interruption_metrics(metrics) elif "TTS" in metrics_type or hasattr(metrics, "ttfb"): self.record_tts_metrics(metrics) elif "STT" in metrics_type: self.record_stt_metrics(metrics) elif "EOU" in metrics_type or hasattr(metrics, "end_of_utterance_delay"): self.record_eou_metrics(metrics) elif "Usage" in metrics_type or hasattr(metrics, "total_tokens"): self.record_session_usage(metrics) else: logger.debug("Captured general metric: %s", metrics_type)Dispatches collected metrics into PostHog telemetry.
def record_eou_metrics(self, m: Any)-
Expand source code
def record_eou_metrics(self, m: Any): """Records End-of-Utterance (endpointing) conversational turn transition delay.""" properties = { "$session_id": self.session_id, "eou_delay": round(getattr(m, "end_of_utterance_delay", 0.0), 4), "transcription_delay": round(getattr(m, "transcription_delay", 0.0), 4), "turn_completed_delay": round(getattr(m, "on_user_turn_completed_delay", 0.0), 4), } logger.info("[PostHog Telemetry] Emitting voice_agent_endpointing_delay: eou_delay=%.3fs", properties["eou_delay"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_endpointing_delay", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_endpointing_delay event: %s", e)Records End-of-Utterance (endpointing) conversational turn transition delay.
def record_interruption_metrics(self, m: Any)-
Expand source code
def record_interruption_metrics(self, m: Any): """Records conversational interruption / user frustration event.""" properties = { "$session_id": self.session_id, "timestamp": time.time(), "speech_duration_before_cut": getattr(m, "duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_agent_interrupted") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_interrupted", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_interrupted event: %s", e)Records conversational interruption / user frustration event.
def record_llm_metrics(self, m: Any)-
Expand source code
def record_llm_metrics(self, m: Any): """ Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation). """ input_tokens = getattr(m, "input_tokens", getattr(m, "prompt_tokens", 0)) output_tokens = getattr(m, "output_tokens", getattr(m, "completion_tokens", 0)) duration = getattr(m, "duration", getattr(m, "latency", 0.0)) ttft = getattr(m, "time_to_first_token", getattr(m, "ttft", 0.0)) model = getattr(m, "model", "gpt-4o-mini") cancelled = getattr(m, "cancelled", False) tps = (output_tokens / duration) if (duration > 0 and output_tokens > 0) else 0.0 properties = { "$session_id": self.session_id, "$ai_trace_id": getattr(m, "request_id", f"req_{int(time.time()*1000)}"), "$ai_model": model, "$ai_provider": "openai", "$ai_input_tokens": input_tokens, "$ai_output_tokens": output_tokens, "$ai_latency": round(duration, 4), "$ai_time_to_first_token": round(ttft, 4), "tokens_per_second": round(tps, 2), "cancelled": cancelled, } logger.info("[PostHog Telemetry] Emitting $ai_generation: tokens=(in:%d, out:%d) latency=%.2fs", input_tokens, output_tokens, duration) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$ai_generation", properties=properties, ) except Exception as e: logger.error("Failed to capture $ai_generation event: %s", e)Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation).
def record_session_usage(self, m: Any)-
Expand source code
def record_session_usage(self, m: Any): """Records cumulative session resource and token usage.""" properties = { "$session_id": self.session_id, "total_tokens": getattr(m, "total_tokens", 0), "input_tokens": getattr(m, "input_tokens", 0), "output_tokens": getattr(m, "output_tokens", 0), "audio_duration": getattr(m, "audio_duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_session_completed") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_session_completed", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_session_completed event: %s", e)Records cumulative session resource and token usage.
def record_stt_metrics(self, m: Any)-
Expand source code
def record_stt_metrics(self, m: Any): """Records Speech-to-Text transcription latency metrics.""" properties = { "$session_id": self.session_id, "stt_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "stt_model": getattr(m, "label", "stt"), "streamed": getattr(m, "streamed", True), } logger.info("[PostHog Telemetry] Emitting voice_agent_stt_latency: duration=%.3fs", properties["stt_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_stt_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_stt_latency event: %s", e)Records Speech-to-Text transcription latency metrics.
def record_tts_metrics(self, m: Any)-
Expand source code
def record_tts_metrics(self, m: Any): """Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).""" properties = { "$session_id": self.session_id, "tts_ttfb": round(getattr(m, "ttfb", 0.0), 4), "tts_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "characters_count": getattr(m, "characters_count", 0), "tts_model": getattr(m, "label", "tts"), "cancelled": getattr(m, "cancelled", False), } logger.info("[PostHog Telemetry] Emitting voice_agent_tts_latency: ttfb=%.3fs duration=%.3fs", properties["tts_ttfb"], properties["tts_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_tts_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_tts_latency event: %s", e)Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).
def shutdown(self)-
Expand source code
def shutdown(self): """Flushes remaining events to PostHog.""" if self.client: try: self.client.flush() except Exception: passFlushes remaining events to PostHog.
class Observer (api_key: str | None = None,
host: str | None = None,
session_id: str | None = None,
project_id: str | None = None)-
Expand source code
class PostHogAgentObserver: """ Subscribes to LiveKit Agent metrics and emits standardized PostHog events for AI observability ($ai_generation), conversational frustration (interruptions), voice latency waterfalls (TTS, STT, EOU), Session Replay cross-linking, and session costs. """ def __init__( self, api_key: Optional[str] = None, host: Optional[str] = None, session_id: Optional[str] = None, project_id: Optional[str] = None, ): self.api_key = api_key or os.getenv("POSTHOG_API_KEY", "phc_mock_key_prism") self.host = host or os.getenv("POSTHOG_HOST", "https://us.i.posthog.com") self.session_id = session_id or f"RM_prism_{int(time.time())}" self.project_id = project_id or os.getenv("POSTHOG_PROJECT_ID", "default_proj") # Initialize PostHog client (flush_at=1 for real-time streaming telemetry) try: self.client = posthog.Posthog( project_api_key=self.api_key, host=self.host, flush_at=1, flush_interval=0.5, ) logger.info("Initialized PostHogAgentObserver with session %s", self.session_id) except Exception as e: logger.warning("Failed to initialize PostHog client, logging locally: %s", e) self.client = None # Feature flags helper self.flags = PostHogFeatureFlags(client=self.client, default_distinct_id=self.session_id) def attach_to_session(self, session: Any): """Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.""" if hasattr(session, "on"): session.on("metrics_collected", self.on_metrics_collected) logger.info("Attached PostHogAgentObserver to session.on('metrics_collected')") def link_session_replay( self, livekit_room: str, participant_identity: str, replay_id: Optional[str] = None, ) -> str: """ Cross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays. """ rep_id = replay_id or self.session_id replay_url = f"{self.host.rstrip('/')}/project/{self.project_id}/replay/{rep_id}" properties = { "$session_id": self.session_id, "livekit_room": livekit_room, "participant_identity": participant_identity, "replay_url": replay_url, "webrtc_stream_linked": True, "timestamp": time.time(), } logger.info("[PostHog SessionReplay] Linked LiveKit room '%s' to replay: %s", livekit_room, replay_url) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$session_replay_link", properties=properties, ) except Exception as e: logger.error("Failed to emit $session_replay_link event: %s", e) return replay_url def on_metrics_collected(self, metrics: Any): """Dispatches collected metrics into PostHog telemetry.""" metrics_type = getattr(metrics, "type", metrics.__class__.__name__) if "LLM" in metrics_type or hasattr(metrics, "input_tokens"): self.record_llm_metrics(metrics) elif "Interruption" in metrics_type or hasattr(metrics, "interrupted"): self.record_interruption_metrics(metrics) elif "TTS" in metrics_type or hasattr(metrics, "ttfb"): self.record_tts_metrics(metrics) elif "STT" in metrics_type: self.record_stt_metrics(metrics) elif "EOU" in metrics_type or hasattr(metrics, "end_of_utterance_delay"): self.record_eou_metrics(metrics) elif "Usage" in metrics_type or hasattr(metrics, "total_tokens"): self.record_session_usage(metrics) else: logger.debug("Captured general metric: %s", metrics_type) def record_llm_metrics(self, m: Any): """ Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation). """ input_tokens = getattr(m, "input_tokens", getattr(m, "prompt_tokens", 0)) output_tokens = getattr(m, "output_tokens", getattr(m, "completion_tokens", 0)) duration = getattr(m, "duration", getattr(m, "latency", 0.0)) ttft = getattr(m, "time_to_first_token", getattr(m, "ttft", 0.0)) model = getattr(m, "model", "gpt-4o-mini") cancelled = getattr(m, "cancelled", False) tps = (output_tokens / duration) if (duration > 0 and output_tokens > 0) else 0.0 properties = { "$session_id": self.session_id, "$ai_trace_id": getattr(m, "request_id", f"req_{int(time.time()*1000)}"), "$ai_model": model, "$ai_provider": "openai", "$ai_input_tokens": input_tokens, "$ai_output_tokens": output_tokens, "$ai_latency": round(duration, 4), "$ai_time_to_first_token": round(ttft, 4), "tokens_per_second": round(tps, 2), "cancelled": cancelled, } logger.info("[PostHog Telemetry] Emitting $ai_generation: tokens=(in:%d, out:%d) latency=%.2fs", input_tokens, output_tokens, duration) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$ai_generation", properties=properties, ) except Exception as e: logger.error("Failed to capture $ai_generation event: %s", e) def record_interruption_metrics(self, m: Any): """Records conversational interruption / user frustration event.""" properties = { "$session_id": self.session_id, "timestamp": time.time(), "speech_duration_before_cut": getattr(m, "duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_agent_interrupted") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_interrupted", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_interrupted event: %s", e) def record_tts_metrics(self, m: Any): """Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).""" properties = { "$session_id": self.session_id, "tts_ttfb": round(getattr(m, "ttfb", 0.0), 4), "tts_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "characters_count": getattr(m, "characters_count", 0), "tts_model": getattr(m, "label", "tts"), "cancelled": getattr(m, "cancelled", False), } logger.info("[PostHog Telemetry] Emitting voice_agent_tts_latency: ttfb=%.3fs duration=%.3fs", properties["tts_ttfb"], properties["tts_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_tts_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_tts_latency event: %s", e) def record_stt_metrics(self, m: Any): """Records Speech-to-Text transcription latency metrics.""" properties = { "$session_id": self.session_id, "stt_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "stt_model": getattr(m, "label", "stt"), "streamed": getattr(m, "streamed", True), } logger.info("[PostHog Telemetry] Emitting voice_agent_stt_latency: duration=%.3fs", properties["stt_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_stt_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_stt_latency event: %s", e) def record_eou_metrics(self, m: Any): """Records End-of-Utterance (endpointing) conversational turn transition delay.""" properties = { "$session_id": self.session_id, "eou_delay": round(getattr(m, "end_of_utterance_delay", 0.0), 4), "transcription_delay": round(getattr(m, "transcription_delay", 0.0), 4), "turn_completed_delay": round(getattr(m, "on_user_turn_completed_delay", 0.0), 4), } logger.info("[PostHog Telemetry] Emitting voice_agent_endpointing_delay: eou_delay=%.3fs", properties["eou_delay"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_endpointing_delay", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_endpointing_delay event: %s", e) def record_session_usage(self, m: Any): """Records cumulative session resource and token usage.""" properties = { "$session_id": self.session_id, "total_tokens": getattr(m, "total_tokens", 0), "input_tokens": getattr(m, "input_tokens", 0), "output_tokens": getattr(m, "output_tokens", 0), "audio_duration": getattr(m, "audio_duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_session_completed") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_session_completed", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_session_completed event: %s", e) def shutdown(self): """Flushes remaining events to PostHog.""" if self.client: try: self.client.flush() except Exception: passSubscribes to LiveKit Agent metrics and emits standardized PostHog events for AI observability ($ai_generation), conversational frustration (interruptions), voice latency waterfalls (TTS, STT, EOU), Session Replay cross-linking, and session costs.
Methods
def attach_to_session(self, session: Any)-
Expand source code
def attach_to_session(self, session: Any): """Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.""" if hasattr(session, "on"): session.on("metrics_collected", self.on_metrics_collected) logger.info("Attached PostHogAgentObserver to session.on('metrics_collected')")Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.
def link_session_replay(self, livekit_room: str, participant_identity: str, replay_id: str | None = None) ‑> str-
Expand source code
def link_session_replay( self, livekit_room: str, participant_identity: str, replay_id: Optional[str] = None, ) -> str: """ Cross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays. """ rep_id = replay_id or self.session_id replay_url = f"{self.host.rstrip('/')}/project/{self.project_id}/replay/{rep_id}" properties = { "$session_id": self.session_id, "livekit_room": livekit_room, "participant_identity": participant_identity, "replay_url": replay_url, "webrtc_stream_linked": True, "timestamp": time.time(), } logger.info("[PostHog SessionReplay] Linked LiveKit room '%s' to replay: %s", livekit_room, replay_url) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$session_replay_link", properties=properties, ) except Exception as e: logger.error("Failed to emit $session_replay_link event: %s", e) return replay_urlCross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays.
def on_metrics_collected(self, metrics: Any)-
Expand source code
def on_metrics_collected(self, metrics: Any): """Dispatches collected metrics into PostHog telemetry.""" metrics_type = getattr(metrics, "type", metrics.__class__.__name__) if "LLM" in metrics_type or hasattr(metrics, "input_tokens"): self.record_llm_metrics(metrics) elif "Interruption" in metrics_type or hasattr(metrics, "interrupted"): self.record_interruption_metrics(metrics) elif "TTS" in metrics_type or hasattr(metrics, "ttfb"): self.record_tts_metrics(metrics) elif "STT" in metrics_type: self.record_stt_metrics(metrics) elif "EOU" in metrics_type or hasattr(metrics, "end_of_utterance_delay"): self.record_eou_metrics(metrics) elif "Usage" in metrics_type or hasattr(metrics, "total_tokens"): self.record_session_usage(metrics) else: logger.debug("Captured general metric: %s", metrics_type)Dispatches collected metrics into PostHog telemetry.
def record_eou_metrics(self, m: Any)-
Expand source code
def record_eou_metrics(self, m: Any): """Records End-of-Utterance (endpointing) conversational turn transition delay.""" properties = { "$session_id": self.session_id, "eou_delay": round(getattr(m, "end_of_utterance_delay", 0.0), 4), "transcription_delay": round(getattr(m, "transcription_delay", 0.0), 4), "turn_completed_delay": round(getattr(m, "on_user_turn_completed_delay", 0.0), 4), } logger.info("[PostHog Telemetry] Emitting voice_agent_endpointing_delay: eou_delay=%.3fs", properties["eou_delay"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_endpointing_delay", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_endpointing_delay event: %s", e)Records End-of-Utterance (endpointing) conversational turn transition delay.
def record_interruption_metrics(self, m: Any)-
Expand source code
def record_interruption_metrics(self, m: Any): """Records conversational interruption / user frustration event.""" properties = { "$session_id": self.session_id, "timestamp": time.time(), "speech_duration_before_cut": getattr(m, "duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_agent_interrupted") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_interrupted", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_interrupted event: %s", e)Records conversational interruption / user frustration event.
def record_llm_metrics(self, m: Any)-
Expand source code
def record_llm_metrics(self, m: Any): """ Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation). """ input_tokens = getattr(m, "input_tokens", getattr(m, "prompt_tokens", 0)) output_tokens = getattr(m, "output_tokens", getattr(m, "completion_tokens", 0)) duration = getattr(m, "duration", getattr(m, "latency", 0.0)) ttft = getattr(m, "time_to_first_token", getattr(m, "ttft", 0.0)) model = getattr(m, "model", "gpt-4o-mini") cancelled = getattr(m, "cancelled", False) tps = (output_tokens / duration) if (duration > 0 and output_tokens > 0) else 0.0 properties = { "$session_id": self.session_id, "$ai_trace_id": getattr(m, "request_id", f"req_{int(time.time()*1000)}"), "$ai_model": model, "$ai_provider": "openai", "$ai_input_tokens": input_tokens, "$ai_output_tokens": output_tokens, "$ai_latency": round(duration, 4), "$ai_time_to_first_token": round(ttft, 4), "tokens_per_second": round(tps, 2), "cancelled": cancelled, } logger.info("[PostHog Telemetry] Emitting $ai_generation: tokens=(in:%d, out:%d) latency=%.2fs", input_tokens, output_tokens, duration) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$ai_generation", properties=properties, ) except Exception as e: logger.error("Failed to capture $ai_generation event: %s", e)Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation).
def record_session_usage(self, m: Any)-
Expand source code
def record_session_usage(self, m: Any): """Records cumulative session resource and token usage.""" properties = { "$session_id": self.session_id, "total_tokens": getattr(m, "total_tokens", 0), "input_tokens": getattr(m, "input_tokens", 0), "output_tokens": getattr(m, "output_tokens", 0), "audio_duration": getattr(m, "audio_duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_session_completed") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_session_completed", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_session_completed event: %s", e)Records cumulative session resource and token usage.
def record_stt_metrics(self, m: Any)-
Expand source code
def record_stt_metrics(self, m: Any): """Records Speech-to-Text transcription latency metrics.""" properties = { "$session_id": self.session_id, "stt_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "stt_model": getattr(m, "label", "stt"), "streamed": getattr(m, "streamed", True), } logger.info("[PostHog Telemetry] Emitting voice_agent_stt_latency: duration=%.3fs", properties["stt_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_stt_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_stt_latency event: %s", e)Records Speech-to-Text transcription latency metrics.
def record_tts_metrics(self, m: Any)-
Expand source code
def record_tts_metrics(self, m: Any): """Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).""" properties = { "$session_id": self.session_id, "tts_ttfb": round(getattr(m, "ttfb", 0.0), 4), "tts_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "characters_count": getattr(m, "characters_count", 0), "tts_model": getattr(m, "label", "tts"), "cancelled": getattr(m, "cancelled", False), } logger.info("[PostHog Telemetry] Emitting voice_agent_tts_latency: ttfb=%.3fs duration=%.3fs", properties["tts_ttfb"], properties["tts_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_tts_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_tts_latency event: %s", e)Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).
def shutdown(self)-
Expand source code
def shutdown(self): """Flushes remaining events to PostHog.""" if self.client: try: self.client.flush() except Exception: passFlushes remaining events to PostHog.
class AgentObserver (api_key: str | None = None,
host: str | None = None,
session_id: str | None = None,
project_id: str | None = None)-
Expand source code
class PostHogAgentObserver: """ Subscribes to LiveKit Agent metrics and emits standardized PostHog events for AI observability ($ai_generation), conversational frustration (interruptions), voice latency waterfalls (TTS, STT, EOU), Session Replay cross-linking, and session costs. """ def __init__( self, api_key: Optional[str] = None, host: Optional[str] = None, session_id: Optional[str] = None, project_id: Optional[str] = None, ): self.api_key = api_key or os.getenv("POSTHOG_API_KEY", "phc_mock_key_prism") self.host = host or os.getenv("POSTHOG_HOST", "https://us.i.posthog.com") self.session_id = session_id or f"RM_prism_{int(time.time())}" self.project_id = project_id or os.getenv("POSTHOG_PROJECT_ID", "default_proj") # Initialize PostHog client (flush_at=1 for real-time streaming telemetry) try: self.client = posthog.Posthog( project_api_key=self.api_key, host=self.host, flush_at=1, flush_interval=0.5, ) logger.info("Initialized PostHogAgentObserver with session %s", self.session_id) except Exception as e: logger.warning("Failed to initialize PostHog client, logging locally: %s", e) self.client = None # Feature flags helper self.flags = PostHogFeatureFlags(client=self.client, default_distinct_id=self.session_id) def attach_to_session(self, session: Any): """Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.""" if hasattr(session, "on"): session.on("metrics_collected", self.on_metrics_collected) logger.info("Attached PostHogAgentObserver to session.on('metrics_collected')") def link_session_replay( self, livekit_room: str, participant_identity: str, replay_id: Optional[str] = None, ) -> str: """ Cross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays. """ rep_id = replay_id or self.session_id replay_url = f"{self.host.rstrip('/')}/project/{self.project_id}/replay/{rep_id}" properties = { "$session_id": self.session_id, "livekit_room": livekit_room, "participant_identity": participant_identity, "replay_url": replay_url, "webrtc_stream_linked": True, "timestamp": time.time(), } logger.info("[PostHog SessionReplay] Linked LiveKit room '%s' to replay: %s", livekit_room, replay_url) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$session_replay_link", properties=properties, ) except Exception as e: logger.error("Failed to emit $session_replay_link event: %s", e) return replay_url def on_metrics_collected(self, metrics: Any): """Dispatches collected metrics into PostHog telemetry.""" metrics_type = getattr(metrics, "type", metrics.__class__.__name__) if "LLM" in metrics_type or hasattr(metrics, "input_tokens"): self.record_llm_metrics(metrics) elif "Interruption" in metrics_type or hasattr(metrics, "interrupted"): self.record_interruption_metrics(metrics) elif "TTS" in metrics_type or hasattr(metrics, "ttfb"): self.record_tts_metrics(metrics) elif "STT" in metrics_type: self.record_stt_metrics(metrics) elif "EOU" in metrics_type or hasattr(metrics, "end_of_utterance_delay"): self.record_eou_metrics(metrics) elif "Usage" in metrics_type or hasattr(metrics, "total_tokens"): self.record_session_usage(metrics) else: logger.debug("Captured general metric: %s", metrics_type) def record_llm_metrics(self, m: Any): """ Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation). """ input_tokens = getattr(m, "input_tokens", getattr(m, "prompt_tokens", 0)) output_tokens = getattr(m, "output_tokens", getattr(m, "completion_tokens", 0)) duration = getattr(m, "duration", getattr(m, "latency", 0.0)) ttft = getattr(m, "time_to_first_token", getattr(m, "ttft", 0.0)) model = getattr(m, "model", "gpt-4o-mini") cancelled = getattr(m, "cancelled", False) tps = (output_tokens / duration) if (duration > 0 and output_tokens > 0) else 0.0 properties = { "$session_id": self.session_id, "$ai_trace_id": getattr(m, "request_id", f"req_{int(time.time()*1000)}"), "$ai_model": model, "$ai_provider": "openai", "$ai_input_tokens": input_tokens, "$ai_output_tokens": output_tokens, "$ai_latency": round(duration, 4), "$ai_time_to_first_token": round(ttft, 4), "tokens_per_second": round(tps, 2), "cancelled": cancelled, } logger.info("[PostHog Telemetry] Emitting $ai_generation: tokens=(in:%d, out:%d) latency=%.2fs", input_tokens, output_tokens, duration) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$ai_generation", properties=properties, ) except Exception as e: logger.error("Failed to capture $ai_generation event: %s", e) def record_interruption_metrics(self, m: Any): """Records conversational interruption / user frustration event.""" properties = { "$session_id": self.session_id, "timestamp": time.time(), "speech_duration_before_cut": getattr(m, "duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_agent_interrupted") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_interrupted", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_interrupted event: %s", e) def record_tts_metrics(self, m: Any): """Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).""" properties = { "$session_id": self.session_id, "tts_ttfb": round(getattr(m, "ttfb", 0.0), 4), "tts_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "characters_count": getattr(m, "characters_count", 0), "tts_model": getattr(m, "label", "tts"), "cancelled": getattr(m, "cancelled", False), } logger.info("[PostHog Telemetry] Emitting voice_agent_tts_latency: ttfb=%.3fs duration=%.3fs", properties["tts_ttfb"], properties["tts_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_tts_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_tts_latency event: %s", e) def record_stt_metrics(self, m: Any): """Records Speech-to-Text transcription latency metrics.""" properties = { "$session_id": self.session_id, "stt_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "stt_model": getattr(m, "label", "stt"), "streamed": getattr(m, "streamed", True), } logger.info("[PostHog Telemetry] Emitting voice_agent_stt_latency: duration=%.3fs", properties["stt_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_stt_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_stt_latency event: %s", e) def record_eou_metrics(self, m: Any): """Records End-of-Utterance (endpointing) conversational turn transition delay.""" properties = { "$session_id": self.session_id, "eou_delay": round(getattr(m, "end_of_utterance_delay", 0.0), 4), "transcription_delay": round(getattr(m, "transcription_delay", 0.0), 4), "turn_completed_delay": round(getattr(m, "on_user_turn_completed_delay", 0.0), 4), } logger.info("[PostHog Telemetry] Emitting voice_agent_endpointing_delay: eou_delay=%.3fs", properties["eou_delay"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_endpointing_delay", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_endpointing_delay event: %s", e) def record_session_usage(self, m: Any): """Records cumulative session resource and token usage.""" properties = { "$session_id": self.session_id, "total_tokens": getattr(m, "total_tokens", 0), "input_tokens": getattr(m, "input_tokens", 0), "output_tokens": getattr(m, "output_tokens", 0), "audio_duration": getattr(m, "audio_duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_session_completed") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_session_completed", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_session_completed event: %s", e) def shutdown(self): """Flushes remaining events to PostHog.""" if self.client: try: self.client.flush() except Exception: passSubscribes to LiveKit Agent metrics and emits standardized PostHog events for AI observability ($ai_generation), conversational frustration (interruptions), voice latency waterfalls (TTS, STT, EOU), Session Replay cross-linking, and session costs.
Methods
def attach_to_session(self, session: Any)-
Expand source code
def attach_to_session(self, session: Any): """Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.""" if hasattr(session, "on"): session.on("metrics_collected", self.on_metrics_collected) logger.info("Attached PostHogAgentObserver to session.on('metrics_collected')")Attaches metrics listener to a LiveKit AgentSession or VoicePipelineAgent.
def link_session_replay(self, livekit_room: str, participant_identity: str, replay_id: str | None = None) ‑> str-
Expand source code
def link_session_replay( self, livekit_room: str, participant_identity: str, replay_id: Optional[str] = None, ) -> str: """ Cross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays. """ rep_id = replay_id or self.session_id replay_url = f"{self.host.rstrip('/')}/project/{self.project_id}/replay/{rep_id}" properties = { "$session_id": self.session_id, "livekit_room": livekit_room, "participant_identity": participant_identity, "replay_url": replay_url, "webrtc_stream_linked": True, "timestamp": time.time(), } logger.info("[PostHog SessionReplay] Linked LiveKit room '%s' to replay: %s", livekit_room, replay_url) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$session_replay_link", properties=properties, ) except Exception as e: logger.error("Failed to emit $session_replay_link event: %s", e) return replay_urlCross-links the LiveKit WebRTC voice session with PostHog Session Replay. Emits a $session_replay_link event so the voice recording is indexed alongside DOM replays.
def on_metrics_collected(self, metrics: Any)-
Expand source code
def on_metrics_collected(self, metrics: Any): """Dispatches collected metrics into PostHog telemetry.""" metrics_type = getattr(metrics, "type", metrics.__class__.__name__) if "LLM" in metrics_type or hasattr(metrics, "input_tokens"): self.record_llm_metrics(metrics) elif "Interruption" in metrics_type or hasattr(metrics, "interrupted"): self.record_interruption_metrics(metrics) elif "TTS" in metrics_type or hasattr(metrics, "ttfb"): self.record_tts_metrics(metrics) elif "STT" in metrics_type: self.record_stt_metrics(metrics) elif "EOU" in metrics_type or hasattr(metrics, "end_of_utterance_delay"): self.record_eou_metrics(metrics) elif "Usage" in metrics_type or hasattr(metrics, "total_tokens"): self.record_session_usage(metrics) else: logger.debug("Captured general metric: %s", metrics_type)Dispatches collected metrics into PostHog telemetry.
def record_eou_metrics(self, m: Any)-
Expand source code
def record_eou_metrics(self, m: Any): """Records End-of-Utterance (endpointing) conversational turn transition delay.""" properties = { "$session_id": self.session_id, "eou_delay": round(getattr(m, "end_of_utterance_delay", 0.0), 4), "transcription_delay": round(getattr(m, "transcription_delay", 0.0), 4), "turn_completed_delay": round(getattr(m, "on_user_turn_completed_delay", 0.0), 4), } logger.info("[PostHog Telemetry] Emitting voice_agent_endpointing_delay: eou_delay=%.3fs", properties["eou_delay"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_endpointing_delay", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_endpointing_delay event: %s", e)Records End-of-Utterance (endpointing) conversational turn transition delay.
def record_interruption_metrics(self, m: Any)-
Expand source code
def record_interruption_metrics(self, m: Any): """Records conversational interruption / user frustration event.""" properties = { "$session_id": self.session_id, "timestamp": time.time(), "speech_duration_before_cut": getattr(m, "duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_agent_interrupted") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_interrupted", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_interrupted event: %s", e)Records conversational interruption / user frustration event.
def record_llm_metrics(self, m: Any)-
Expand source code
def record_llm_metrics(self, m: Any): """ Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation). """ input_tokens = getattr(m, "input_tokens", getattr(m, "prompt_tokens", 0)) output_tokens = getattr(m, "output_tokens", getattr(m, "completion_tokens", 0)) duration = getattr(m, "duration", getattr(m, "latency", 0.0)) ttft = getattr(m, "time_to_first_token", getattr(m, "ttft", 0.0)) model = getattr(m, "model", "gpt-4o-mini") cancelled = getattr(m, "cancelled", False) tps = (output_tokens / duration) if (duration > 0 and output_tokens > 0) else 0.0 properties = { "$session_id": self.session_id, "$ai_trace_id": getattr(m, "request_id", f"req_{int(time.time()*1000)}"), "$ai_model": model, "$ai_provider": "openai", "$ai_input_tokens": input_tokens, "$ai_output_tokens": output_tokens, "$ai_latency": round(duration, 4), "$ai_time_to_first_token": round(ttft, 4), "tokens_per_second": round(tps, 2), "cancelled": cancelled, } logger.info("[PostHog Telemetry] Emitting $ai_generation: tokens=(in:%d, out:%d) latency=%.2fs", input_tokens, output_tokens, duration) if self.client: try: self.client.capture( distinct_id=self.session_id, event="$ai_generation", properties=properties, ) except Exception as e: logger.error("Failed to capture $ai_generation event: %s", e)Conforms strictly to Contract C: PostHog AI Observability Schema ($ai_generation).
def record_session_usage(self, m: Any)-
Expand source code
def record_session_usage(self, m: Any): """Records cumulative session resource and token usage.""" properties = { "$session_id": self.session_id, "total_tokens": getattr(m, "total_tokens", 0), "input_tokens": getattr(m, "input_tokens", 0), "output_tokens": getattr(m, "output_tokens", 0), "audio_duration": getattr(m, "audio_duration", 0.0), } logger.info("[PostHog Telemetry] Emitting voice_session_completed") if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_session_completed", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_session_completed event: %s", e)Records cumulative session resource and token usage.
def record_stt_metrics(self, m: Any)-
Expand source code
def record_stt_metrics(self, m: Any): """Records Speech-to-Text transcription latency metrics.""" properties = { "$session_id": self.session_id, "stt_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "stt_model": getattr(m, "label", "stt"), "streamed": getattr(m, "streamed", True), } logger.info("[PostHog Telemetry] Emitting voice_agent_stt_latency: duration=%.3fs", properties["stt_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_stt_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_stt_latency event: %s", e)Records Speech-to-Text transcription latency metrics.
def record_tts_metrics(self, m: Any)-
Expand source code
def record_tts_metrics(self, m: Any): """Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).""" properties = { "$session_id": self.session_id, "tts_ttfb": round(getattr(m, "ttfb", 0.0), 4), "tts_duration": round(getattr(m, "duration", 0.0), 4), "audio_duration": round(getattr(m, "audio_duration", 0.0), 4), "characters_count": getattr(m, "characters_count", 0), "tts_model": getattr(m, "label", "tts"), "cancelled": getattr(m, "cancelled", False), } logger.info("[PostHog Telemetry] Emitting voice_agent_tts_latency: ttfb=%.3fs duration=%.3fs", properties["tts_ttfb"], properties["tts_duration"]) if self.client: try: self.client.capture( distinct_id=self.session_id, event="voice_agent_tts_latency", properties=properties, ) except Exception as e: logger.error("Failed to capture voice_agent_tts_latency event: %s", e)Records Text-to-Speech synthesis latency waterfall metrics (TTFB, duration).
def shutdown(self)-
Expand source code
def shutdown(self): """Flushes remaining events to PostHog.""" if self.client: try: self.client.flush() except Exception: passFlushes remaining events to PostHog.