@pipedog/core 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/NOTICE +9 -0
- package/dist/adapters/base_llm_adapter.d.ts +266 -0
- package/dist/adapters/base_llm_adapter.js +489 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +8 -0
- package/dist/adapters/schemas/direct_function.d.ts +202 -0
- package/dist/adapters/schemas/direct_function.js +302 -0
- package/dist/adapters/schemas/function_schema.d.ts +67 -0
- package/dist/adapters/schemas/function_schema.js +72 -0
- package/dist/adapters/schemas/index.d.ts +2 -0
- package/dist/adapters/schemas/index.js +8 -0
- package/dist/adapters/schemas/tools_schema.d.ts +57 -0
- package/dist/adapters/schemas/tools_schema.js +79 -0
- package/dist/adapters/services/index.d.ts +2 -0
- package/dist/adapters/services/index.js +8 -0
- package/dist/adapters/services/open_ai_adapter.d.ts +147 -0
- package/dist/adapters/services/open_ai_adapter.js +250 -0
- package/dist/audio/dtmf/dtmf_data.d.ts +3 -0
- package/dist/audio/dtmf/dtmf_data.js +22 -0
- package/dist/audio/dtmf/index.d.ts +2 -0
- package/dist/audio/dtmf/index.js +8 -0
- package/dist/audio/dtmf/types.d.ts +31 -0
- package/dist/audio/dtmf/types.js +35 -0
- package/dist/audio/dtmf/utils.d.ts +13 -0
- package/dist/audio/dtmf/utils.js +62 -0
- package/dist/audio/filters/base_audio_filter.d.ts +51 -0
- package/dist/audio/filters/base_audio_filter.js +17 -0
- package/dist/audio/filters/index.d.ts +2 -0
- package/dist/audio/filters/index.js +8 -0
- package/dist/audio/index.d.ts +2 -0
- package/dist/audio/index.js +8 -0
- package/dist/audio/krisp_instance.d.ts +95 -0
- package/dist/audio/krisp_instance.js +229 -0
- package/dist/audio/mixers/base_audio_mixer.d.ts +56 -0
- package/dist/audio/mixers/base_audio_mixer.js +16 -0
- package/dist/audio/mixers/index.d.ts +2 -0
- package/dist/audio/mixers/index.js +8 -0
- package/dist/audio/resamplers/base_audio_resampler.d.ts +46 -0
- package/dist/audio/resamplers/base_audio_resampler.js +35 -0
- package/dist/audio/resamplers/engine.d.ts +60 -0
- package/dist/audio/resamplers/engine.js +89 -0
- package/dist/audio/resamplers/index.d.ts +2 -0
- package/dist/audio/resamplers/index.js +8 -0
- package/dist/audio/resamplers/resampy_resampler.d.ts +10 -0
- package/dist/audio/resamplers/resampy_resampler.js +15 -0
- package/dist/audio/resamplers/sinc_engine.d.ts +10 -0
- package/dist/audio/resamplers/sinc_engine.js +235 -0
- package/dist/audio/resamplers/soxr_resampler.d.ts +58 -0
- package/dist/audio/resamplers/soxr_resampler.js +79 -0
- package/dist/audio/resamplers/soxr_stream_resampler.d.ts +78 -0
- package/dist/audio/resamplers/soxr_stream_resampler.js +159 -0
- package/dist/audio/turn/base_turn_analyzer.d.ts +84 -0
- package/dist/audio/turn/base_turn_analyzer.js +61 -0
- package/dist/audio/turn/index.d.ts +2 -0
- package/dist/audio/turn/index.js +8 -0
- package/dist/audio/turn/smart_turn/_whisper_features.d.ts +30 -0
- package/dist/audio/turn/smart_turn/_whisper_features.js +225 -0
- package/dist/audio/turn/smart_turn/base_smart_turn.d.ts +94 -0
- package/dist/audio/turn/smart_turn/base_smart_turn.js +239 -0
- package/dist/audio/turn/smart_turn/local_smart_turn_v3.d.ts +76 -0
- package/dist/audio/turn/smart_turn/local_smart_turn_v3.js +151 -0
- package/dist/audio/utils.d.ts +224 -0
- package/dist/audio/utils.js +634 -0
- package/dist/audio/vad/data/index.d.ts +2 -0
- package/dist/audio/vad/data/index.js +8 -0
- package/dist/audio/vad/index.d.ts +2 -0
- package/dist/audio/vad/index.js +8 -0
- package/dist/audio/vad/vad_analyzer.d.ts +134 -0
- package/dist/audio/vad/vad_analyzer.js +249 -0
- package/dist/audio/vad/vad_controller.d.ts +143 -0
- package/dist/audio/vad/vad_controller.js +228 -0
- package/dist/audio/volume.d.ts +38 -0
- package/dist/audio/volume.js +75 -0
- package/dist/bus/adapters/base.d.ts +35 -0
- package/dist/bus/adapters/base.js +19 -0
- package/dist/bus/adapters/index.d.ts +10 -0
- package/dist/bus/adapters/index.js +15 -0
- package/dist/bus/adapters/llm_context_adapter.d.ts +36 -0
- package/dist/bus/adapters/llm_context_adapter.js +82 -0
- package/dist/bus/adapters/tools_schema_adapter.d.ts +25 -0
- package/dist/bus/adapters/tools_schema_adapter.js +70 -0
- package/dist/bus/bridge_processor.d.ts +111 -0
- package/dist/bus/bridge_processor.js +166 -0
- package/dist/bus/bus.d.ts +110 -0
- package/dist/bus/bus.js +200 -0
- package/dist/bus/index.d.ts +22 -0
- package/dist/bus/index.js +27 -0
- package/dist/bus/local/async_queue.d.ts +12 -0
- package/dist/bus/local/async_queue.js +21 -0
- package/dist/bus/local/index.d.ts +3 -0
- package/dist/bus/local/index.js +8 -0
- package/dist/bus/messages.d.ts +378 -0
- package/dist/bus/messages.js +357 -0
- package/dist/bus/network/index.d.ts +16 -0
- package/dist/bus/network/index.js +21 -0
- package/dist/bus/queue.d.ts +27 -0
- package/dist/bus/queue.js +51 -0
- package/dist/bus/serializers/base.d.ts +25 -0
- package/dist/bus/serializers/base.js +14 -0
- package/dist/bus/serializers/index.d.ts +9 -0
- package/dist/bus/serializers/index.js +14 -0
- package/dist/bus/serializers/json.d.ts +77 -0
- package/dist/bus/serializers/json.js +367 -0
- package/dist/bus/subscriber.d.ts +46 -0
- package/dist/bus/subscriber.js +27 -0
- package/dist/bus/ui/index.d.ts +3 -0
- package/dist/bus/ui/index.js +8 -0
- package/dist/bus/ui/messages.d.ts +186 -0
- package/dist/bus/ui/messages.js +171 -0
- package/dist/classifiers/base_classifier.d.ts +263 -0
- package/dist/classifiers/base_classifier.js +346 -0
- package/dist/classifiers/cloudflare/clef/classifier.d.ts +70 -0
- package/dist/classifiers/cloudflare/clef/classifier.js +204 -0
- package/dist/classifiers/cloudflare/clef/client.d.ts +104 -0
- package/dist/classifiers/cloudflare/clef/client.js +234 -0
- package/dist/classifiers/cloudflare/clef/index.d.ts +2 -0
- package/dist/classifiers/cloudflare/clef/index.js +8 -0
- package/dist/classifiers/cloudflare/index.d.ts +2 -0
- package/dist/classifiers/cloudflare/index.js +8 -0
- package/dist/classifiers/http_client.d.ts +70 -0
- package/dist/classifiers/http_client.js +161 -0
- package/dist/classifiers/index.d.ts +2 -0
- package/dist/classifiers/index.js +8 -0
- package/dist/classifiers/jev/classifier.d.ts +8 -0
- package/dist/classifiers/jev/classifier.js +13 -0
- package/dist/classifiers/jev/client.d.ts +8 -0
- package/dist/classifiers/jev/client.js +13 -0
- package/dist/classifiers/jev/index.d.ts +2 -0
- package/dist/classifiers/jev/index.js +8 -0
- package/dist/classifiers/llm/classifier.d.ts +72 -0
- package/dist/classifiers/llm/classifier.js +304 -0
- package/dist/classifiers/llm/index.d.ts +2 -0
- package/dist/classifiers/llm/index.js +8 -0
- package/dist/classifiers/openai/decisions/classifier.d.ts +77 -0
- package/dist/classifiers/openai/decisions/classifier.js +241 -0
- package/dist/classifiers/openai/decisions/client.d.ts +108 -0
- package/dist/classifiers/openai/decisions/client.js +234 -0
- package/dist/classifiers/openai/decisions/index.d.ts +2 -0
- package/dist/classifiers/openai/decisions/index.js +8 -0
- package/dist/classifiers/openai/index.d.ts +2 -0
- package/dist/classifiers/openai/index.js +8 -0
- package/dist/classifiers/typesafe/index.d.ts +2 -0
- package/dist/classifiers/typesafe/index.js +8 -0
- package/dist/classifiers/typesafe/jev/classifier.d.ts +70 -0
- package/dist/classifiers/typesafe/jev/classifier.js +220 -0
- package/dist/classifiers/typesafe/jev/client.d.ts +105 -0
- package/dist/classifiers/typesafe/jev/client.js +233 -0
- package/dist/classifiers/typesafe/jev/index.d.ts +2 -0
- package/dist/classifiers/typesafe/jev/index.js +8 -0
- package/dist/clocks/base_clock.d.ts +13 -0
- package/dist/clocks/base_clock.js +14 -0
- package/dist/clocks/index.d.ts +2 -0
- package/dist/clocks/index.js +8 -0
- package/dist/clocks/system_clock.d.ts +15 -0
- package/dist/clocks/system_clock.js +25 -0
- package/dist/frames/frames.d.ts +2533 -0
- package/dist/frames/frames.js +2485 -0
- package/dist/frames/index.d.ts +2 -0
- package/dist/frames/index.js +8 -0
- package/dist/frames/protobufs/frames_pb2.d.ts +229 -0
- package/dist/frames/protobufs/frames_pb2.js +466 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +56 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.js +8 -0
- package/dist/metrics/metrics.d.ts +276 -0
- package/dist/metrics/metrics.js +254 -0
- package/dist/observers/base_observer.d.ts +173 -0
- package/dist/observers/base_observer.js +151 -0
- package/dist/observers/error_observer.d.ts +75 -0
- package/dist/observers/error_observer.js +87 -0
- package/dist/observers/function_call_observer.d.ts +157 -0
- package/dist/observers/function_call_observer.js +222 -0
- package/dist/observers/index.d.ts +2 -0
- package/dist/observers/index.js +8 -0
- package/dist/observers/loggers/debug_log_observer.d.ts +91 -0
- package/dist/observers/loggers/debug_log_observer.js +228 -0
- package/dist/observers/loggers/index.d.ts +2 -0
- package/dist/observers/loggers/index.js +8 -0
- package/dist/observers/loggers/llm_log_observer.d.ts +24 -0
- package/dist/observers/loggers/llm_log_observer.js +110 -0
- package/dist/observers/loggers/metrics_log_observer.d.ts +85 -0
- package/dist/observers/loggers/metrics_log_observer.js +186 -0
- package/dist/observers/loggers/transcription_log_observer.d.ts +23 -0
- package/dist/observers/loggers/transcription_log_observer.js +74 -0
- package/dist/observers/service_metrics_observer.d.ts +168 -0
- package/dist/observers/service_metrics_observer.js +236 -0
- package/dist/observers/speaking_observer.d.ts +104 -0
- package/dist/observers/speaking_observer.js +150 -0
- package/dist/observers/startup_timing_observer.d.ts +221 -0
- package/dist/observers/startup_timing_observer.js +365 -0
- package/dist/observers/turn_tracking_observer.d.ts +78 -0
- package/dist/observers/turn_tracking_observer.js +189 -0
- package/dist/observers/user_bot_latency_observer.d.ts +411 -0
- package/dist/observers/user_bot_latency_observer.js +974 -0
- package/dist/pipeline/base_pipeline.d.ts +49 -0
- package/dist/pipeline/base_pipeline.js +101 -0
- package/dist/pipeline/capabilities.d.ts +76 -0
- package/dist/pipeline/capabilities.js +97 -0
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +8 -0
- package/dist/pipeline/job_context.d.ts +317 -0
- package/dist/pipeline/job_context.js +450 -0
- package/dist/pipeline/job_decorator.d.ts +54 -0
- package/dist/pipeline/job_decorator.js +92 -0
- package/dist/pipeline/llm_switcher.d.ts +84 -0
- package/dist/pipeline/llm_switcher.js +107 -0
- package/dist/pipeline/llm_with_backend.d.ts +435 -0
- package/dist/pipeline/llm_with_backend.js +806 -0
- package/dist/pipeline/parallel_pipeline.d.ts +83 -0
- package/dist/pipeline/parallel_pipeline.js +200 -0
- package/dist/pipeline/pipeline.d.ts +126 -0
- package/dist/pipeline/pipeline.js +196 -0
- package/dist/pipeline/service_switcher.d.ts +287 -0
- package/dist/pipeline/service_switcher.js +509 -0
- package/dist/pipeline/sync_parallel_pipeline.d.ts +192 -0
- package/dist/pipeline/sync_parallel_pipeline.js +387 -0
- package/dist/pipeline/worker.d.ts +821 -0
- package/dist/pipeline/worker.js +1656 -0
- package/dist/pipeline/worker_observer.d.ts +131 -0
- package/dist/pipeline/worker_observer.js +274 -0
- package/dist/pipeline/worker_ready_decorator.d.ts +45 -0
- package/dist/pipeline/worker_ready_decorator.js +76 -0
- package/dist/processors/aggregators/async_tool_messages.d.ts +146 -0
- package/dist/processors/aggregators/async_tool_messages.js +291 -0
- package/dist/processors/aggregators/dtmf_aggregator.d.ts +61 -0
- package/dist/processors/aggregators/dtmf_aggregator.js +144 -0
- package/dist/processors/aggregators/gated.d.ts +43 -0
- package/dist/processors/aggregators/gated.js +86 -0
- package/dist/processors/aggregators/gated_llm_context.d.ts +42 -0
- package/dist/processors/aggregators/gated_llm_context.js +86 -0
- package/dist/processors/aggregators/index.d.ts +2 -0
- package/dist/processors/aggregators/index.js +8 -0
- package/dist/processors/aggregators/llm_context.d.ts +370 -0
- package/dist/processors/aggregators/llm_context.js +580 -0
- package/dist/processors/aggregators/llm_context_summarizer.d.ts +219 -0
- package/dist/processors/aggregators/llm_context_summarizer.js +447 -0
- package/dist/processors/aggregators/llm_response.d.ts +37 -0
- package/dist/processors/aggregators/llm_response.js +71 -0
- package/dist/processors/aggregators/llm_response_universal.d.ts +966 -0
- package/dist/processors/aggregators/llm_response_universal.js +2394 -0
- package/dist/processors/aggregators/llm_text_processor.d.ts +53 -0
- package/dist/processors/aggregators/llm_text_processor.js +102 -0
- package/dist/processors/aggregators/sentence.d.ts +39 -0
- package/dist/processors/aggregators/sentence.js +69 -0
- package/dist/processors/async_generator.d.ts +42 -0
- package/dist/processors/async_generator.js +76 -0
- package/dist/processors/audio/audio_buffer_processor.d.ts +241 -0
- package/dist/processors/audio/audio_buffer_processor.js +565 -0
- package/dist/processors/audio/index.d.ts +2 -0
- package/dist/processors/audio/index.js +8 -0
- package/dist/processors/audio/vad_processor.d.ts +67 -0
- package/dist/processors/audio/vad_processor.js +103 -0
- package/dist/processors/consumer_processor.d.ts +49 -0
- package/dist/processors/consumer_processor.js +85 -0
- package/dist/processors/filters/frame_filter.d.ts +39 -0
- package/dist/processors/filters/frame_filter.js +52 -0
- package/dist/processors/filters/function_filter.d.ts +59 -0
- package/dist/processors/filters/function_filter.js +78 -0
- package/dist/processors/filters/identity_filter.d.ts +33 -0
- package/dist/processors/filters/identity_filter.js +37 -0
- package/dist/processors/filters/index.d.ts +2 -0
- package/dist/processors/filters/index.js +8 -0
- package/dist/processors/filters/null_filter.d.ts +34 -0
- package/dist/processors/filters/null_filter.js +47 -0
- package/dist/processors/filters/wake_notifier_filter.d.ts +48 -0
- package/dist/processors/filters/wake_notifier_filter.js +49 -0
- package/dist/processors/frame_processor.d.ts +360 -0
- package/dist/processors/frame_processor.js +932 -0
- package/dist/processors/frameworks/index.d.ts +2 -0
- package/dist/processors/frameworks/index.js +8 -0
- package/dist/processors/frameworks/rtvi/frames.d.ts +241 -0
- package/dist/processors/frameworks/rtvi/frames.js +259 -0
- package/dist/processors/frameworks/rtvi/index.d.ts +6 -0
- package/dist/processors/frameworks/rtvi/index.js +11 -0
- package/dist/processors/frameworks/rtvi/models.d.ts +1741 -0
- package/dist/processors/frameworks/rtvi/models.js +1807 -0
- package/dist/processors/frameworks/rtvi/observer.d.ts +340 -0
- package/dist/processors/frameworks/rtvi/observer.js +1113 -0
- package/dist/processors/frameworks/rtvi/processor.d.ts +182 -0
- package/dist/processors/frameworks/rtvi/processor.js +676 -0
- package/dist/processors/idle_frame_processor.d.ts +44 -0
- package/dist/processors/idle_frame_processor.js +86 -0
- package/dist/processors/index.d.ts +2 -0
- package/dist/processors/index.js +8 -0
- package/dist/processors/logger.d.ts +42 -0
- package/dist/processors/logger.js +87 -0
- package/dist/processors/metrics/frame_processor_metrics.d.ts +172 -0
- package/dist/processors/metrics/frame_processor_metrics.js +372 -0
- package/dist/processors/metrics/index.d.ts +2 -0
- package/dist/processors/metrics/index.js +8 -0
- package/dist/processors/producer_processor.d.ts +68 -0
- package/dist/processors/producer_processor.js +78 -0
- package/dist/processors/text_transformer.d.ts +30 -0
- package/dist/processors/text_transformer.js +48 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +8 -0
- package/dist/registry/registry.d.ts +69 -0
- package/dist/registry/registry.js +149 -0
- package/dist/registry/types.d.ts +47 -0
- package/dist/registry/types.js +39 -0
- package/dist/serializers/base_serializer.d.ts +94 -0
- package/dist/serializers/base_serializer.js +82 -0
- package/dist/serializers/index.d.ts +2 -0
- package/dist/serializers/index.js +8 -0
- package/dist/services/ai_service.d.ts +145 -0
- package/dist/services/ai_service.js +262 -0
- package/dist/services/anthropic/index.d.ts +2 -0
- package/dist/services/anthropic/index.js +7 -0
- package/dist/services/assemblyai/index.d.ts +2 -0
- package/dist/services/assemblyai/index.js +7 -0
- package/dist/services/asyncai/index.d.ts +2 -0
- package/dist/services/asyncai/index.js +7 -0
- package/dist/services/aws/index.d.ts +2 -0
- package/dist/services/aws/index.js +7 -0
- package/dist/services/azure/index.d.ts +2 -0
- package/dist/services/azure/index.js +7 -0
- package/dist/services/baseten/index.d.ts +2 -0
- package/dist/services/baseten/index.js +7 -0
- package/dist/services/bland/index.d.ts +2 -0
- package/dist/services/bland/index.js +7 -0
- package/dist/services/camb/index.d.ts +2 -0
- package/dist/services/camb/index.js +7 -0
- package/dist/services/cartesia/index.d.ts +2 -0
- package/dist/services/cartesia/index.js +7 -0
- package/dist/services/cerebras/index.d.ts +2 -0
- package/dist/services/cerebras/index.js +7 -0
- package/dist/services/crusoe/index.d.ts +2 -0
- package/dist/services/crusoe/index.js +7 -0
- package/dist/services/deepgram/index.d.ts +2 -0
- package/dist/services/deepgram/index.js +7 -0
- package/dist/services/deepseek/index.d.ts +2 -0
- package/dist/services/deepseek/index.js +7 -0
- package/dist/services/elevenlabs/index.d.ts +2 -0
- package/dist/services/elevenlabs/index.js +7 -0
- package/dist/services/fal/index.d.ts +2 -0
- package/dist/services/fal/index.js +7 -0
- package/dist/services/fireworks/index.d.ts +2 -0
- package/dist/services/fireworks/index.js +7 -0
- package/dist/services/fish/index.d.ts +2 -0
- package/dist/services/fish/index.js +7 -0
- package/dist/services/funasr/index.d.ts +3 -0
- package/dist/services/funasr/index.js +8 -0
- package/dist/services/gladia/index.d.ts +2 -0
- package/dist/services/gladia/index.js +7 -0
- package/dist/services/google/index.d.ts +2 -0
- package/dist/services/google/index.js +7 -0
- package/dist/services/gradium/index.d.ts +2 -0
- package/dist/services/gradium/index.js +7 -0
- package/dist/services/grok/index.d.ts +2 -0
- package/dist/services/grok/index.js +7 -0
- package/dist/services/groq/index.d.ts +2 -0
- package/dist/services/groq/index.js +7 -0
- package/dist/services/heygen/index.d.ts +2 -0
- package/dist/services/heygen/index.js +7 -0
- package/dist/services/hume/index.d.ts +2 -0
- package/dist/services/hume/index.js +7 -0
- package/dist/services/image_service.d.ts +52 -0
- package/dist/services/image_service.js +60 -0
- package/dist/services/inception/index.d.ts +2 -0
- package/dist/services/inception/index.js +7 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +8 -0
- package/dist/services/inworld/index.d.ts +2 -0
- package/dist/services/inworld/index.js +7 -0
- package/dist/services/keenable/index.d.ts +2 -0
- package/dist/services/keenable/index.js +7 -0
- package/dist/services/kokoro/index.d.ts +2 -0
- package/dist/services/kokoro/index.js +7 -0
- package/dist/services/llm_service.d.ts +1106 -0
- package/dist/services/llm_service.js +2231 -0
- package/dist/services/mem0/index.d.ts +2 -0
- package/dist/services/mem0/index.js +7 -0
- package/dist/services/meta/index.d.ts +2 -0
- package/dist/services/meta/index.js +7 -0
- package/dist/services/minimax/index.d.ts +2 -0
- package/dist/services/minimax/index.js +7 -0
- package/dist/services/mistral/index.d.ts +2 -0
- package/dist/services/mistral/index.js +7 -0
- package/dist/services/moondream/index.d.ts +2 -0
- package/dist/services/moondream/index.js +7 -0
- package/dist/services/moonshine/index.d.ts +2 -0
- package/dist/services/moonshine/index.js +7 -0
- package/dist/services/nebius/index.d.ts +2 -0
- package/dist/services/nebius/index.js +7 -0
- package/dist/services/neuphonic/index.d.ts +2 -0
- package/dist/services/neuphonic/index.js +7 -0
- package/dist/services/novita/index.d.ts +2 -0
- package/dist/services/novita/index.js +7 -0
- package/dist/services/nvidia/index.d.ts +2 -0
- package/dist/services/nvidia/index.js +11 -0
- package/dist/services/ollama/index.d.ts +2 -0
- package/dist/services/ollama/index.js +7 -0
- package/dist/services/openai/index.d.ts +2 -0
- package/dist/services/openai/index.js +7 -0
- package/dist/services/openclaw/index.d.ts +14 -0
- package/dist/services/openclaw/index.js +19 -0
- package/dist/services/openrouter/index.d.ts +2 -0
- package/dist/services/openrouter/index.js +7 -0
- package/dist/services/perplexity/index.d.ts +2 -0
- package/dist/services/perplexity/index.js +7 -0
- package/dist/services/piper/index.d.ts +2 -0
- package/dist/services/piper/index.js +7 -0
- package/dist/services/pocket_tts/index.d.ts +2 -0
- package/dist/services/pocket_tts/index.js +7 -0
- package/dist/services/qwen/index.d.ts +2 -0
- package/dist/services/qwen/index.js +7 -0
- package/dist/services/resembleai/index.d.ts +2 -0
- package/dist/services/resembleai/index.js +7 -0
- package/dist/services/rime/index.d.ts +2 -0
- package/dist/services/rime/index.js +7 -0
- package/dist/services/sambanova/index.d.ts +2 -0
- package/dist/services/sambanova/index.js +7 -0
- package/dist/services/sarvam/index.d.ts +2 -0
- package/dist/services/sarvam/index.js +7 -0
- package/dist/services/settings.d.ts +356 -0
- package/dist/services/settings.js +437 -0
- package/dist/services/simli/index.d.ts +2 -0
- package/dist/services/simli/index.js +7 -0
- package/dist/services/smallest/index.d.ts +2 -0
- package/dist/services/smallest/index.js +7 -0
- package/dist/services/soniox/index.d.ts +2 -0
- package/dist/services/soniox/index.js +7 -0
- package/dist/services/speechify/index.d.ts +2 -0
- package/dist/services/speechify/index.js +7 -0
- package/dist/services/speechmatics/index.d.ts +2 -0
- package/dist/services/speechmatics/index.js +7 -0
- package/dist/services/stt_latency.d.ts +62 -0
- package/dist/services/stt_latency.js +71 -0
- package/dist/services/stt_service.d.ts +601 -0
- package/dist/services/stt_service.js +1044 -0
- package/dist/services/tavus/index.d.ts +2 -0
- package/dist/services/tavus/index.js +7 -0
- package/dist/services/together/index.d.ts +2 -0
- package/dist/services/together/index.js +7 -0
- package/dist/services/tts_service.d.ts +909 -0
- package/dist/services/tts_service.js +2018 -0
- package/dist/services/ultravox/index.d.ts +2 -0
- package/dist/services/ultravox/index.js +9 -0
- package/dist/services/vision_service.d.ts +56 -0
- package/dist/services/vision_service.js +62 -0
- package/dist/services/websocket_service.d.ts +213 -0
- package/dist/services/websocket_service.js +654 -0
- package/dist/services/whisper/index.d.ts +2 -0
- package/dist/services/whisper/index.js +9 -0
- package/dist/services/xai/index.d.ts +2 -0
- package/dist/services/xai/index.js +9 -0
- package/dist/services/xtts/index.d.ts +2 -0
- package/dist/services/xtts/index.js +9 -0
- package/dist/tests/index.d.ts +2 -0
- package/dist/tests/index.js +8 -0
- package/dist/tests/utils.d.ts +123 -0
- package/dist/tests/utils.js +212 -0
- package/dist/transcriptions/index.d.ts +2 -0
- package/dist/transcriptions/index.js +8 -0
- package/dist/transcriptions/language.d.ts +376 -0
- package/dist/transcriptions/language.js +543 -0
- package/dist/transports/base_input.d.ts +149 -0
- package/dist/transports/base_input.js +338 -0
- package/dist/transports/base_output.d.ts +432 -0
- package/dist/transports/base_output.js +1231 -0
- package/dist/transports/base_transport.d.ts +223 -0
- package/dist/transports/base_transport.js +167 -0
- package/dist/transports/daily/index.d.ts +2 -0
- package/dist/transports/daily/index.js +9 -0
- package/dist/transports/heygen/index.d.ts +2 -0
- package/dist/transports/heygen/index.js +9 -0
- package/dist/transports/index.d.ts +2 -0
- package/dist/transports/index.js +8 -0
- package/dist/transports/lemonslice/index.d.ts +2 -0
- package/dist/transports/lemonslice/index.js +9 -0
- package/dist/transports/livekit/index.d.ts +2 -0
- package/dist/transports/livekit/index.js +9 -0
- package/dist/transports/local/index.d.ts +2 -0
- package/dist/transports/local/index.js +9 -0
- package/dist/transports/moq/index.d.ts +2 -0
- package/dist/transports/moq/index.js +9 -0
- package/dist/transports/smallwebrtc/index.d.ts +2 -0
- package/dist/transports/smallwebrtc/index.js +9 -0
- package/dist/transports/tavus/index.d.ts +2 -0
- package/dist/transports/tavus/index.js +9 -0
- package/dist/transports/video_in_sampler.d.ts +157 -0
- package/dist/transports/video_in_sampler.js +258 -0
- package/dist/transports/vonage/index.d.ts +2 -0
- package/dist/transports/vonage/index.js +9 -0
- package/dist/transports/websocket/index.d.ts +2 -0
- package/dist/transports/websocket/index.js +9 -0
- package/dist/transports/whatsapp/index.d.ts +2 -0
- package/dist/transports/whatsapp/index.js +9 -0
- package/dist/turns/eager_end_of_turn_mixin.d.ts +70 -0
- package/dist/turns/eager_end_of_turn_mixin.js +122 -0
- package/dist/turns/empty_user_turn.d.ts +55 -0
- package/dist/turns/empty_user_turn.js +53 -0
- package/dist/turns/index.d.ts +2 -0
- package/dist/turns/index.js +8 -0
- package/dist/turns/speculation_gate.d.ts +169 -0
- package/dist/turns/speculation_gate.js +294 -0
- package/dist/turns/types.d.ts +33 -0
- package/dist/turns/types.js +35 -0
- package/dist/turns/user_idle_controller.d.ts +94 -0
- package/dist/turns/user_idle_controller.js +187 -0
- package/dist/turns/user_mute/always_user_mute_strategy.d.ts +15 -0
- package/dist/turns/user_mute/always_user_mute_strategy.js +29 -0
- package/dist/turns/user_mute/base_user_mute_strategy.d.ts +39 -0
- package/dist/turns/user_mute/base_user_mute_strategy.js +45 -0
- package/dist/turns/user_mute/first_speech_user_mute_strategy.d.ts +28 -0
- package/dist/turns/user_mute/first_speech_user_mute_strategy.js +52 -0
- package/dist/turns/user_mute/function_call_user_mute_strategy.d.ts +25 -0
- package/dist/turns/user_mute/function_call_user_mute_strategy.js +50 -0
- package/dist/turns/user_mute/index.d.ts +6 -0
- package/dist/turns/user_mute/index.js +11 -0
- package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.d.ts +43 -0
- package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.js +76 -0
- package/dist/turns/user_start/base_user_turn_start_strategy.d.ts +135 -0
- package/dist/turns/user_start/base_user_turn_start_strategy.js +134 -0
- package/dist/turns/user_start/external_user_turn_start_strategy.d.ts +45 -0
- package/dist/turns/user_start/external_user_turn_start_strategy.js +64 -0
- package/dist/turns/user_start/index.d.ts +8 -0
- package/dist/turns/user_start/index.js +16 -0
- package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.d.ts +133 -0
- package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.js +243 -0
- package/dist/turns/user_start/min_words_user_turn_start_strategy.d.ts +60 -0
- package/dist/turns/user_start/min_words_user_turn_start_strategy.js +102 -0
- package/dist/turns/user_start/transcription_user_turn_start_strategy.d.ts +29 -0
- package/dist/turns/user_start/transcription_user_turn_start_strategy.js +43 -0
- package/dist/turns/user_start/vad_user_turn_start_strategy.d.ts +20 -0
- package/dist/turns/user_start/vad_user_turn_start_strategy.js +31 -0
- package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.d.ts +141 -0
- package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.js +238 -0
- package/dist/turns/user_stop/base_user_turn_stop_strategy.d.ts +174 -0
- package/dist/turns/user_stop/base_user_turn_stop_strategy.js +170 -0
- package/dist/turns/user_stop/deferred_user_turn_stop_strategy.d.ts +81 -0
- package/dist/turns/user_stop/deferred_user_turn_stop_strategy.js +107 -0
- package/dist/turns/user_stop/eager_match_policy.d.ts +36 -0
- package/dist/turns/user_stop/eager_match_policy.js +50 -0
- package/dist/turns/user_stop/eager_user_turn_stop_strategy.d.ts +84 -0
- package/dist/turns/user_stop/eager_user_turn_stop_strategy.js +154 -0
- package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.d.ts +45 -0
- package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.js +55 -0
- package/dist/turns/user_stop/external_user_turn_stop_strategy.d.ts +115 -0
- package/dist/turns/user_stop/external_user_turn_stop_strategy.js +205 -0
- package/dist/turns/user_stop/index.d.ts +10 -0
- package/dist/turns/user_stop/index.js +15 -0
- package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.d.ts +44 -0
- package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.js +59 -0
- package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.d.ts +140 -0
- package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.js +303 -0
- package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.d.ts +135 -0
- package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.js +332 -0
- package/dist/turns/user_turn_completion_mixin.d.ts +147 -0
- package/dist/turns/user_turn_completion_mixin.js +591 -0
- package/dist/turns/user_turn_controller.d.ts +134 -0
- package/dist/turns/user_turn_controller.js +335 -0
- package/dist/turns/user_turn_processor.d.ts +87 -0
- package/dist/turns/user_turn_processor.js +180 -0
- package/dist/turns/user_turn_strategies.d.ts +187 -0
- package/dist/turns/user_turn_strategies.js +222 -0
- package/dist/utils/async_tool_cancellation.d.ts +35 -0
- package/dist/utils/async_tool_cancellation.js +78 -0
- package/dist/utils/asyncio/index.d.ts +2 -0
- package/dist/utils/asyncio/index.js +7 -0
- package/dist/utils/asyncio/task_manager.d.ts +49 -0
- package/dist/utils/asyncio/task_manager.js +105 -0
- package/dist/utils/base_object.d.ts +76 -0
- package/dist/utils/base_object.js +154 -0
- package/dist/utils/context/aggregated_frame_sequencer.d.ts +491 -0
- package/dist/utils/context/aggregated_frame_sequencer.js +803 -0
- package/dist/utils/context/audio_context_manager.d.ts +131 -0
- package/dist/utils/context/audio_context_manager.js +233 -0
- package/dist/utils/context/index.d.ts +2 -0
- package/dist/utils/context/index.js +7 -0
- package/dist/utils/context/llm_context_summarization.d.ts +258 -0
- package/dist/utils/context/llm_context_summarization.js +511 -0
- package/dist/utils/context/text_segment_map.d.ts +611 -0
- package/dist/utils/context/text_segment_map.js +1132 -0
- package/dist/utils/context/word_completion_tracker.d.ts +273 -0
- package/dist/utils/context/word_completion_tracker.js +441 -0
- package/dist/utils/context/word_timestamp_tracker.d.ts +72 -0
- package/dist/utils/context/word_timestamp_tracker.js +98 -0
- package/dist/utils/deprecation.d.ts +28 -0
- package/dist/utils/deprecation.js +75 -0
- package/dist/utils/env.d.ts +38 -0
- package/dist/utils/env.js +62 -0
- package/dist/utils/errors.d.ts +75 -0
- package/dist/utils/errors.js +202 -0
- package/dist/utils/file_resolver.d.ts +126 -0
- package/dist/utils/file_resolver.js +314 -0
- package/dist/utils/file_storage.d.ts +115 -0
- package/dist/utils/file_storage.js +178 -0
- package/dist/utils/frame_queue.d.ts +36 -0
- package/dist/utils/frame_queue.js +66 -0
- package/dist/utils/http.d.ts +39 -0
- package/dist/utils/http.js +53 -0
- package/dist/utils/image.d.ts +14 -0
- package/dist/utils/image.js +63 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/jpeg_encoder.d.ts +14 -0
- package/dist/utils/jpeg_encoder.js +315 -0
- package/dist/utils/log_config.d.ts +34 -0
- package/dist/utils/log_config.js +76 -0
- package/dist/utils/logger.d.ts +40 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/mixin.d.ts +14 -0
- package/dist/utils/mixin.js +63 -0
- package/dist/utils/network.d.ts +73 -0
- package/dist/utils/network.js +90 -0
- package/dist/utils/security/allowed_origins.d.ts +16 -0
- package/dist/utils/security/allowed_origins.js +34 -0
- package/dist/utils/security/index.d.ts +2 -0
- package/dist/utils/security/index.js +7 -0
- package/dist/utils/security/redaction.d.ts +27 -0
- package/dist/utils/security/redaction.js +123 -0
- package/dist/utils/security/ssrf.d.ts +79 -0
- package/dist/utils/security/ssrf.js +327 -0
- package/dist/utils/shared.d.ts +35 -0
- package/dist/utils/shared.js +136 -0
- package/dist/utils/startup.d.ts +17 -0
- package/dist/utils/startup.js +130 -0
- package/dist/utils/string.d.ts +116 -0
- package/dist/utils/string.js +460 -0
- package/dist/utils/sync/base_notifier.d.ts +25 -0
- package/dist/utils/sync/base_notifier.js +16 -0
- package/dist/utils/sync/event_notifier.d.ts +38 -0
- package/dist/utils/sync/event_notifier.js +51 -0
- package/dist/utils/sync/index.d.ts +2 -0
- package/dist/utils/sync/index.js +7 -0
- package/dist/utils/text/alnum_utils.d.ts +109 -0
- package/dist/utils/text/alnum_utils.js +262 -0
- package/dist/utils/text/base_text_aggregator.d.ts +146 -0
- package/dist/utils/text/base_text_aggregator.js +102 -0
- package/dist/utils/text/base_text_filter.d.ts +54 -0
- package/dist/utils/text/base_text_filter.js +50 -0
- package/dist/utils/text/index.d.ts +2 -0
- package/dist/utils/text/index.js +7 -0
- package/dist/utils/text/markdown_text_filter.d.ts +147 -0
- package/dist/utils/text/markdown_text_filter.js +522 -0
- package/dist/utils/text/markup_utils.d.ts +54 -0
- package/dist/utils/text/markup_utils.js +156 -0
- package/dist/utils/text/pattern_pair_aggregator.d.ts +251 -0
- package/dist/utils/text/pattern_pair_aggregator.js +478 -0
- package/dist/utils/text/phonemes.d.ts +60 -0
- package/dist/utils/text/phonemes.js +172 -0
- package/dist/utils/text/simple_text_aggregator.d.ts +135 -0
- package/dist/utils/text/simple_text_aggregator.js +254 -0
- package/dist/utils/text/skip_tags_aggregator.d.ts +85 -0
- package/dist/utils/text/skip_tags_aggregator.js +147 -0
- package/dist/utils/text/transforms/acronyms.d.ts +21 -0
- package/dist/utils/text/transforms/acronyms.js +31 -0
- package/dist/utils/text/transforms/currency.d.ts +16 -0
- package/dist/utils/text/transforms/currency.js +56 -0
- package/dist/utils/text/transforms/dates.d.ts +18 -0
- package/dist/utils/text/transforms/dates.js +102 -0
- package/dist/utils/text/transforms/email.d.ts +17 -0
- package/dist/utils/text/transforms/email.js +35 -0
- package/dist/utils/text/transforms/index.d.ts +37 -0
- package/dist/utils/text/transforms/index.js +42 -0
- package/dist/utils/text/transforms/numbers.d.ts +25 -0
- package/dist/utils/text/transforms/numbers.js +59 -0
- package/dist/utils/text/transforms/percentages.d.ts +16 -0
- package/dist/utils/text/transforms/percentages.js +32 -0
- package/dist/utils/text/transforms/phone.d.ts +21 -0
- package/dist/utils/text/transforms/phone.js +39 -0
- package/dist/utils/text/transforms/pronunciations.d.ts +65 -0
- package/dist/utils/text/transforms/pronunciations.js +112 -0
- package/dist/utils/text/transforms/replacements.d.ts +39 -0
- package/dist/utils/text/transforms/replacements.js +46 -0
- package/dist/utils/text/transforms/strip_markdown.d.ts +25 -0
- package/dist/utils/text/transforms/strip_markdown.js +60 -0
- package/dist/utils/text/transforms/units.d.ts +22 -0
- package/dist/utils/text/transforms/units.js +81 -0
- package/dist/utils/text/transforms/voice_formatter.d.ts +86 -0
- package/dist/utils/text/transforms/voice_formatter.js +98 -0
- package/dist/utils/text/unicode_digits.d.ts +3 -0
- package/dist/utils/text/unicode_digits.js +34 -0
- package/dist/utils/text/word_timestamp_utils.d.ts +26 -0
- package/dist/utils/text/word_timestamp_utils.js +52 -0
- package/dist/utils/time.d.ts +13 -0
- package/dist/utils/time.js +31 -0
- package/dist/utils/tracing/index.d.ts +3 -0
- package/dist/utils/tracing/index.js +8 -0
- package/dist/utils/tracing/service_attributes.d.ts +198 -0
- package/dist/utils/tracing/service_attributes.js +434 -0
- package/dist/utils/tracing/service_decorators.d.ts +214 -0
- package/dist/utils/tracing/service_decorators.js +1662 -0
- package/dist/utils/tracing/setup.d.ts +107 -0
- package/dist/utils/tracing/setup.js +160 -0
- package/dist/utils/tracing/tracing_context.d.ts +55 -0
- package/dist/utils/tracing/tracing_context.js +97 -0
- package/dist/utils/tracing/turn_trace_observer.d.ts +95 -0
- package/dist/utils/tracing/turn_trace_observer.js +233 -0
- package/dist/utils/types.d.ts +80 -0
- package/dist/utils/types.js +108 -0
- package/dist/utils/utils.d.ts +5 -0
- package/dist/utils/utils.js +22 -0
- package/dist/utils/yaml.d.ts +179 -0
- package/dist/utils/yaml.js +556 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +7 -0
- package/dist/workers/base_worker.d.ts +772 -0
- package/dist/workers/base_worker.js +1298 -0
- package/dist/workers/index.d.ts +2 -0
- package/dist/workers/index.js +8 -0
- package/dist/workers/llm/backend_llm_worker.d.ts +552 -0
- package/dist/workers/llm/backend_llm_worker.js +1055 -0
- package/dist/workers/llm/index.d.ts +6 -0
- package/dist/workers/llm/index.js +11 -0
- package/dist/workers/llm/llm_context_worker.d.ts +66 -0
- package/dist/workers/llm/llm_context_worker.js +72 -0
- package/dist/workers/llm/llm_worker.d.ts +180 -0
- package/dist/workers/llm/llm_worker.js +323 -0
- package/dist/workers/llm/tool_decorator.d.ts +68 -0
- package/dist/workers/llm/tool_decorator.js +60 -0
- package/dist/workers/proxy/index.d.ts +3 -0
- package/dist/workers/proxy/index.js +9 -0
- package/dist/workers/proxy/websocket/client.d.ts +110 -0
- package/dist/workers/proxy/websocket/client.js +196 -0
- package/dist/workers/proxy/websocket/index.d.ts +3 -0
- package/dist/workers/proxy/websocket/index.js +10 -0
- package/dist/workers/runner.d.ts +268 -0
- package/dist/workers/runner.js +489 -0
- package/dist/workers/ui/index.d.ts +22 -0
- package/dist/workers/ui/index.js +27 -0
- package/dist/workers/ui/ui_event_decorator.d.ts +43 -0
- package/dist/workers/ui/ui_event_decorator.js +75 -0
- package/dist/workers/ui/ui_job_context.d.ts +12 -0
- package/dist/workers/ui/ui_job_context.js +17 -0
- package/dist/workers/ui/ui_prompts.d.ts +21 -0
- package/dist/workers/ui/ui_prompts.js +41 -0
- package/dist/workers/ui/ui_tools.d.ts +32 -0
- package/dist/workers/ui/ui_tools.js +112 -0
- package/dist/workers/ui/ui_worker.d.ts +571 -0
- package/dist/workers/ui/ui_worker.js +1156 -0
- package/package.json +61 -3
- package/README.md +0 -5
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/observers/user_bot_latency_observer.py
|
|
6
|
+
/**
|
|
7
|
+
* Observer for tracking user-to-bot response latency.
|
|
8
|
+
*
|
|
9
|
+
* Provides an observer that monitors the time between when a user stops
|
|
10
|
+
* speaking and when the bot starts speaking, emitting events when latency is
|
|
11
|
+
* measured. When `enableMetrics` is true it also collects a breakdown of the
|
|
12
|
+
* interval: per-service metrics (TTFB, text aggregation), and contributions
|
|
13
|
+
* that name each part of the timeline, including the parts no service
|
|
14
|
+
* measures.
|
|
15
|
+
*/
|
|
16
|
+
import { time } from "@pipedog/asyncio";
|
|
17
|
+
import { BotStartedSpeakingFrame, ClientConnectedFrame, FunctionCallInProgressFrame, FunctionCallResultFrame, InterruptionFrame, LLMFullResponseStartFrame, LLMMarkerFrame, LLMTextFrame, MetricsFrame, TranscriptionFrame, TTSAudioRawFrame, UserStoppedSpeakingFrame, VADUserStartedSpeakingFrame, VADUserStoppedSpeakingFrame, } from "../frames/frames.js";
|
|
18
|
+
import { TextAggregationMetricsData, TTFBMetricsData } from "../metrics/metrics.js";
|
|
19
|
+
import { FrameDirection } from "../processors/frame_processor.js";
|
|
20
|
+
import { BaseObserver } from "./base_observer.js";
|
|
21
|
+
/**
|
|
22
|
+
* TTFB measurement with timestamp for timeline placement.
|
|
23
|
+
*
|
|
24
|
+
* - `processor`: name of the processor that reported the TTFB.
|
|
25
|
+
* - `model`: optional model name associated with the metric.
|
|
26
|
+
* - `startTime`: Unix timestamp (seconds) when the TTFB measurement started.
|
|
27
|
+
* - `durationSecs`: TTFB duration in seconds.
|
|
28
|
+
*/
|
|
29
|
+
export class TTFBBreakdownMetrics {
|
|
30
|
+
processor;
|
|
31
|
+
model;
|
|
32
|
+
startTime;
|
|
33
|
+
durationSecs;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.processor = options.processor;
|
|
36
|
+
this.model = options.model ?? null;
|
|
37
|
+
this.startTime = options.startTime;
|
|
38
|
+
this.durationSecs = options.durationSecs;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Text aggregation measurement with timestamp for timeline placement.
|
|
43
|
+
*
|
|
44
|
+
* - `processor`: name of the processor that reported the metric.
|
|
45
|
+
* - `startTime`: Unix timestamp (seconds) when text aggregation started.
|
|
46
|
+
* - `durationSecs`: aggregation duration in seconds.
|
|
47
|
+
*/
|
|
48
|
+
export class TextAggregationBreakdownMetrics {
|
|
49
|
+
processor;
|
|
50
|
+
startTime;
|
|
51
|
+
durationSecs;
|
|
52
|
+
constructor(options) {
|
|
53
|
+
this.processor = options.processor;
|
|
54
|
+
this.startTime = options.startTime;
|
|
55
|
+
this.durationSecs = options.durationSecs;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Latency for a single function call execution.
|
|
60
|
+
*
|
|
61
|
+
* - `functionName`: name of the function that was called.
|
|
62
|
+
* - `startTime`: Unix timestamp (seconds) when execution started.
|
|
63
|
+
* - `durationSecs`: time in seconds from execution start to result.
|
|
64
|
+
*/
|
|
65
|
+
export class FunctionCallMetrics {
|
|
66
|
+
functionName;
|
|
67
|
+
startTime;
|
|
68
|
+
durationSecs;
|
|
69
|
+
constructor(options) {
|
|
70
|
+
this.functionName = options.functionName;
|
|
71
|
+
this.startTime = options.startTime;
|
|
72
|
+
this.durationSecs = options.durationSecs;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Anything shorter than this is a frame hop rather than work worth naming, so
|
|
77
|
+
* it is rolled into the single pipeline contribution.
|
|
78
|
+
*/
|
|
79
|
+
export const MIN_CONTRIBUTION_SECS = 0.005;
|
|
80
|
+
/**
|
|
81
|
+
* Half of the last digit `turnContributionLines()` prints, below which a span
|
|
82
|
+
* would show as zero and so is not part of the timeline at all.
|
|
83
|
+
*/
|
|
84
|
+
export const PRINTS_AS_ZERO_SECS = 0.0005;
|
|
85
|
+
/**
|
|
86
|
+
* A point in a user-to-bot cycle that a contribution can start or end at.
|
|
87
|
+
*
|
|
88
|
+
* Most are observed as frames. `LLM_CHUNK` and `SENTENCE` are worked back from
|
|
89
|
+
* a service's own metric, because no frame marks them.
|
|
90
|
+
*/
|
|
91
|
+
const _MomentKind = {
|
|
92
|
+
SILENCE: "silence",
|
|
93
|
+
READY: "ready",
|
|
94
|
+
VAD_STOP: "vad stop",
|
|
95
|
+
TRANSCRIPT: "transcript",
|
|
96
|
+
LLM_REQUEST: "llm request",
|
|
97
|
+
LLM_CHUNK: "llm chunk",
|
|
98
|
+
MARKER_COMPLETE: "marker complete",
|
|
99
|
+
MARKER_INCOMPLETE: "marker incomplete",
|
|
100
|
+
HANDLERS_START: "handlers start",
|
|
101
|
+
HANDLERS_END: "handlers end",
|
|
102
|
+
FIRST_TEXT: "first text",
|
|
103
|
+
SENTENCE: "sentence",
|
|
104
|
+
FIRST_AUDIO: "first audio",
|
|
105
|
+
BOT_SPEAKING: "bot speaking",
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* One moment in a cycle.
|
|
109
|
+
*
|
|
110
|
+
* - `at`: Unix timestamp (seconds) of the moment.
|
|
111
|
+
* - `kind`: what happened.
|
|
112
|
+
* - `source`: name of whatever produced it, used when a span takes its owner
|
|
113
|
+
* from one end.
|
|
114
|
+
* - `derived`: whether it was worked back from a metric rather than observed
|
|
115
|
+
* as a frame. A derived moment sits at or before the frame that reported
|
|
116
|
+
* it, which decides the order when the two share a time.
|
|
117
|
+
*/
|
|
118
|
+
class _Moment {
|
|
119
|
+
at;
|
|
120
|
+
kind;
|
|
121
|
+
source;
|
|
122
|
+
derived;
|
|
123
|
+
constructor(options) {
|
|
124
|
+
this.at = options.at;
|
|
125
|
+
this.kind = options.kind;
|
|
126
|
+
this.source = options.source ?? "";
|
|
127
|
+
this.derived = options.derived ?? false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const OPENER = Symbol("OPENER");
|
|
131
|
+
const CLOSER = Symbol("CLOSER");
|
|
132
|
+
/** Which end of a span supplies its owner, when a setting does not. */
|
|
133
|
+
const _Owner = { OPENER, CLOSER };
|
|
134
|
+
const _TURN_COMPLETION = "config: filter_incomplete_user_turns";
|
|
135
|
+
/**
|
|
136
|
+
* One stretch of a cycle, named by the moments at its ends.
|
|
137
|
+
*
|
|
138
|
+
* - `start`: the moment the stretch runs from.
|
|
139
|
+
* - `end`: the moment it runs to.
|
|
140
|
+
* - `key`: stable identifier, which survives a label being reworded.
|
|
141
|
+
* - `label`: how the stretch reads in a timeline.
|
|
142
|
+
* - `owner`: the setting that governs the time, the bot whose code spends it,
|
|
143
|
+
* or which end of the span supplies the processor that does.
|
|
144
|
+
* - `core`: whether this is a core stage, which stays listed however brief so
|
|
145
|
+
* the timeline keeps its shape from one turn to the next.
|
|
146
|
+
*/
|
|
147
|
+
class _Span {
|
|
148
|
+
start;
|
|
149
|
+
end;
|
|
150
|
+
key;
|
|
151
|
+
label;
|
|
152
|
+
owner;
|
|
153
|
+
core;
|
|
154
|
+
constructor(options) {
|
|
155
|
+
this.start = options.start;
|
|
156
|
+
this.end = options.end;
|
|
157
|
+
this.key = options.key;
|
|
158
|
+
this.label = options.label;
|
|
159
|
+
this.owner = options.owner;
|
|
160
|
+
this.core = options.core;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Each pair of adjacent moments names the span between them, and a pair that
|
|
164
|
+
// is absent leaves that stretch unnamed, to be reported as pipeline time.
|
|
165
|
+
const _SPANS = [
|
|
166
|
+
// Silence the VAD had to hear before it would call the turn over. It is a
|
|
167
|
+
// setting rather than a service, and shortening it trades latency for
|
|
168
|
+
// transcription accuracy.
|
|
169
|
+
new _Span({
|
|
170
|
+
start: _MomentKind.SILENCE,
|
|
171
|
+
end: _MomentKind.VAD_STOP,
|
|
172
|
+
key: "endpointing_wait",
|
|
173
|
+
label: "endpointing wait",
|
|
174
|
+
owner: "config: VAD stop_secs",
|
|
175
|
+
core: true,
|
|
176
|
+
}),
|
|
177
|
+
// From a pipeline that is ready with a client on it, to the bot asking for
|
|
178
|
+
// something to say: the application's connected handler, and the frame it
|
|
179
|
+
// queues reaching the LLM.
|
|
180
|
+
new _Span({
|
|
181
|
+
start: _MomentKind.READY,
|
|
182
|
+
end: _MomentKind.LLM_REQUEST,
|
|
183
|
+
key: "first_request",
|
|
184
|
+
label: "first request",
|
|
185
|
+
owner: "bot",
|
|
186
|
+
core: true,
|
|
187
|
+
}),
|
|
188
|
+
new _Span({
|
|
189
|
+
start: _MomentKind.VAD_STOP,
|
|
190
|
+
end: _MomentKind.TRANSCRIPT,
|
|
191
|
+
key: "transcription",
|
|
192
|
+
label: "transcription",
|
|
193
|
+
owner: _Owner.CLOSER,
|
|
194
|
+
core: true,
|
|
195
|
+
}),
|
|
196
|
+
// Whatever decides the user is done: a fixed speech timeout, a smart-turn
|
|
197
|
+
// model's inference, or an external signal.
|
|
198
|
+
new _Span({
|
|
199
|
+
start: _MomentKind.TRANSCRIPT,
|
|
200
|
+
end: _MomentKind.LLM_REQUEST,
|
|
201
|
+
key: "turn_detection",
|
|
202
|
+
label: "turn detection",
|
|
203
|
+
owner: "config: user turn strategies",
|
|
204
|
+
core: true,
|
|
205
|
+
}),
|
|
206
|
+
new _Span({
|
|
207
|
+
start: _MomentKind.LLM_REQUEST,
|
|
208
|
+
end: _MomentKind.LLM_CHUNK,
|
|
209
|
+
key: "llm_inference",
|
|
210
|
+
label: "LLM inference",
|
|
211
|
+
owner: _Owner.OPENER,
|
|
212
|
+
core: true,
|
|
213
|
+
}),
|
|
214
|
+
// The gate reading the verdict, whichever way it went.
|
|
215
|
+
new _Span({
|
|
216
|
+
start: _MomentKind.LLM_CHUNK,
|
|
217
|
+
end: _MomentKind.MARKER_COMPLETE,
|
|
218
|
+
key: "turn_completion",
|
|
219
|
+
label: "turn completion",
|
|
220
|
+
owner: _TURN_COMPLETION,
|
|
221
|
+
core: false,
|
|
222
|
+
}),
|
|
223
|
+
new _Span({
|
|
224
|
+
start: _MomentKind.LLM_CHUNK,
|
|
225
|
+
end: _MomentKind.MARKER_INCOMPLETE,
|
|
226
|
+
key: "turn_completion",
|
|
227
|
+
label: "turn completion",
|
|
228
|
+
owner: _TURN_COMPLETION,
|
|
229
|
+
core: false,
|
|
230
|
+
}),
|
|
231
|
+
// The token the marker occupies before anything can be spoken.
|
|
232
|
+
new _Span({
|
|
233
|
+
start: _MomentKind.MARKER_COMPLETE,
|
|
234
|
+
end: _MomentKind.FIRST_TEXT,
|
|
235
|
+
key: "turn_completion",
|
|
236
|
+
label: "turn completion",
|
|
237
|
+
owner: _TURN_COMPLETION,
|
|
238
|
+
core: false,
|
|
239
|
+
}),
|
|
240
|
+
new _Span({
|
|
241
|
+
start: _MomentKind.MARKER_INCOMPLETE,
|
|
242
|
+
end: _MomentKind.LLM_REQUEST,
|
|
243
|
+
key: "waiting_for_user",
|
|
244
|
+
label: "waiting for user",
|
|
245
|
+
owner: _TURN_COMPLETION,
|
|
246
|
+
core: false,
|
|
247
|
+
}),
|
|
248
|
+
// Between the LLM's first chunk and the call being dispatched, the LLM is
|
|
249
|
+
// still writing the call.
|
|
250
|
+
new _Span({
|
|
251
|
+
start: _MomentKind.LLM_CHUNK,
|
|
252
|
+
end: _MomentKind.HANDLERS_START,
|
|
253
|
+
key: "llm_tool_call",
|
|
254
|
+
label: "LLM tool call",
|
|
255
|
+
owner: _Owner.OPENER,
|
|
256
|
+
core: false,
|
|
257
|
+
}),
|
|
258
|
+
new _Span({
|
|
259
|
+
start: _MomentKind.HANDLERS_START,
|
|
260
|
+
end: _MomentKind.HANDLERS_END,
|
|
261
|
+
key: "function_handler",
|
|
262
|
+
label: "function handler",
|
|
263
|
+
owner: _Owner.CLOSER,
|
|
264
|
+
core: false,
|
|
265
|
+
}),
|
|
266
|
+
// Waiting for a full sentence before speaking any of it.
|
|
267
|
+
new _Span({
|
|
268
|
+
start: _MomentKind.FIRST_TEXT,
|
|
269
|
+
end: _MomentKind.SENTENCE,
|
|
270
|
+
key: "sentence_aggregation",
|
|
271
|
+
label: "sentence aggregation",
|
|
272
|
+
owner: "config: text_aggregation_mode",
|
|
273
|
+
core: false,
|
|
274
|
+
}),
|
|
275
|
+
new _Span({
|
|
276
|
+
start: _MomentKind.SENTENCE,
|
|
277
|
+
end: _MomentKind.FIRST_AUDIO,
|
|
278
|
+
key: "speech_synthesis",
|
|
279
|
+
label: "speech synthesis",
|
|
280
|
+
owner: _Owner.CLOSER,
|
|
281
|
+
core: true,
|
|
282
|
+
}),
|
|
283
|
+
new _Span({
|
|
284
|
+
start: _MomentKind.FIRST_TEXT,
|
|
285
|
+
end: _MomentKind.FIRST_AUDIO,
|
|
286
|
+
key: "speech_synthesis",
|
|
287
|
+
label: "speech synthesis",
|
|
288
|
+
owner: _Owner.CLOSER,
|
|
289
|
+
core: true,
|
|
290
|
+
}),
|
|
291
|
+
new _Span({
|
|
292
|
+
start: _MomentKind.FIRST_AUDIO,
|
|
293
|
+
end: _MomentKind.BOT_SPEAKING,
|
|
294
|
+
key: "output_transport",
|
|
295
|
+
label: "output transport",
|
|
296
|
+
owner: _Owner.CLOSER,
|
|
297
|
+
core: false,
|
|
298
|
+
}),
|
|
299
|
+
];
|
|
300
|
+
function spanKey(start, end) {
|
|
301
|
+
return `${start}\u0000${end}`;
|
|
302
|
+
}
|
|
303
|
+
const _SPANS_BY_MOMENTS = new Map(_SPANS.map((span) => [spanKey(span.start, span.end), span]));
|
|
304
|
+
// One stretch is two spans rather than one, so it is chosen rather than looked
|
|
305
|
+
// up: where turn completion is in use, a response that carried no marker is
|
|
306
|
+
// buffered whole before anything can be spoken; where it is not, the same wait
|
|
307
|
+
// is the LLM still streaming, so its inference covers it.
|
|
308
|
+
const _AWAITING_SPEAKABLE_TEXT = new _Span({
|
|
309
|
+
start: _MomentKind.LLM_CHUNK,
|
|
310
|
+
end: _MomentKind.FIRST_TEXT,
|
|
311
|
+
key: "awaiting_speakable_text",
|
|
312
|
+
label: "awaiting speakable text",
|
|
313
|
+
owner: _Owner.CLOSER,
|
|
314
|
+
core: false,
|
|
315
|
+
});
|
|
316
|
+
const _LLM_INFERENCE_UNTIL_TEXT = new _Span({
|
|
317
|
+
start: _MomentKind.LLM_CHUNK,
|
|
318
|
+
end: _MomentKind.FIRST_TEXT,
|
|
319
|
+
key: "llm_inference",
|
|
320
|
+
label: "LLM inference",
|
|
321
|
+
owner: _Owner.OPENER,
|
|
322
|
+
core: true,
|
|
323
|
+
});
|
|
324
|
+
/**
|
|
325
|
+
* Where a measured interval was anchored.
|
|
326
|
+
*
|
|
327
|
+
* A greeting is timed from the client connecting, a turn from the user
|
|
328
|
+
* falling silent, so a partial interval is never mistaken for a whole one.
|
|
329
|
+
*/
|
|
330
|
+
export const MeasuredFrom = {
|
|
331
|
+
USER_SILENCE: "user_silence",
|
|
332
|
+
CLIENT_CONNECTED: "client_connected",
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* What kind of thing a contribution's owner is.
|
|
336
|
+
*
|
|
337
|
+
* Grouping on this answers where a turn's time went without matching on the
|
|
338
|
+
* owner's name: a service that could be swapped, a setting the bot chose, the
|
|
339
|
+
* bot's own code, or the pipeline between them.
|
|
340
|
+
*/
|
|
341
|
+
export const LatencyOwnerKind = {
|
|
342
|
+
SERVICE: "service",
|
|
343
|
+
SETTING: "setting",
|
|
344
|
+
BOT: "bot",
|
|
345
|
+
PIPELINE: "pipeline",
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* One named part of the user-to-bot interval.
|
|
349
|
+
*
|
|
350
|
+
* Contributions account for the whole interval, so their durations sum to the
|
|
351
|
+
* measured latency. Unlike a TTFB, which reports what a service spent once it
|
|
352
|
+
* was asked, a contribution can also name time no service is measuring: the
|
|
353
|
+
* silence a VAD waits out, the tokens a turn-completion marker occupies before
|
|
354
|
+
* any speakable text, or a hold while the pipeline waits for the user to
|
|
355
|
+
* finish a sentence.
|
|
356
|
+
*
|
|
357
|
+
* - `key`: stable identifier for this part of a turn. Unlike `label`, it is
|
|
358
|
+
* safe to group on: it survives the label being reworded.
|
|
359
|
+
* - `label`: what the time was spent on.
|
|
360
|
+
* - `owner`: what spent it — a processor name, or a `config:` tag naming the
|
|
361
|
+
* setting that governs it.
|
|
362
|
+
* - `ownerKind`: what kind of thing the owner is.
|
|
363
|
+
* - `startTime`: Unix timestamp (seconds) when it started.
|
|
364
|
+
* - `durationSecs`: how long it took, in seconds.
|
|
365
|
+
*/
|
|
366
|
+
export class LatencyContribution {
|
|
367
|
+
key;
|
|
368
|
+
label;
|
|
369
|
+
owner;
|
|
370
|
+
ownerKind;
|
|
371
|
+
startTime;
|
|
372
|
+
durationSecs;
|
|
373
|
+
constructor(options) {
|
|
374
|
+
this.key = options.key;
|
|
375
|
+
this.label = options.label;
|
|
376
|
+
this.owner = options.owner;
|
|
377
|
+
this.ownerKind = options.ownerKind;
|
|
378
|
+
this.startTime = options.startTime;
|
|
379
|
+
this.durationSecs = options.durationSecs;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Per-service latency breakdown for a single user-to-bot cycle.
|
|
384
|
+
*
|
|
385
|
+
* Collected between `VADUserStoppedSpeakingFrame` and
|
|
386
|
+
* `BotStartedSpeakingFrame` when `enableMetrics` is true in `PipelineParams`.
|
|
387
|
+
*
|
|
388
|
+
* - `ttfb`: time-to-first-byte metrics from each service in the pipeline.
|
|
389
|
+
* - `textAggregation`: first text aggregation measurement, representing the
|
|
390
|
+
* latency cost of sentence aggregation in the TTS pipeline.
|
|
391
|
+
* - `userTurnStartTime`: Unix timestamp when the user turn started (actual
|
|
392
|
+
* user silence, adjusted for VAD stop_secs). null if no
|
|
393
|
+
* `VADUserStoppedSpeakingFrame` was observed.
|
|
394
|
+
* - `userTurnSecs`: duration in seconds of the user's turn, measured from
|
|
395
|
+
* when the user actually stopped speaking to when the turn was released
|
|
396
|
+
* (`UserStoppedSpeakingFrame`). This includes VAD silence detection, STT
|
|
397
|
+
* finalization, and any turn analyzer wait. null if no
|
|
398
|
+
* `UserStoppedSpeakingFrame` was observed (e.g. no turn analyzer
|
|
399
|
+
* configured).
|
|
400
|
+
* - `functionCalls`: latency for each function call executed during this
|
|
401
|
+
* cycle. Empty if no function calls occurred.
|
|
402
|
+
* - `contributions`: named parts of the interval, in chronological order,
|
|
403
|
+
* summing to the measured latency. A part is listed only if it happened, so
|
|
404
|
+
* a bot without turn completion has no marker or wait entries.
|
|
405
|
+
* - `measuredFrom`: where the interval was anchored, so a greeting is not
|
|
406
|
+
* compared with a turn.
|
|
407
|
+
* - `totalSecs`: the measured interval, which the contributions sum to.
|
|
408
|
+
*/
|
|
409
|
+
export class LatencyBreakdown {
|
|
410
|
+
ttfb;
|
|
411
|
+
contributions;
|
|
412
|
+
measuredFrom;
|
|
413
|
+
totalSecs;
|
|
414
|
+
textAggregation;
|
|
415
|
+
userTurnStartTime;
|
|
416
|
+
userTurnSecs;
|
|
417
|
+
functionCalls;
|
|
418
|
+
constructor(options = {}) {
|
|
419
|
+
this.ttfb = options.ttfb ?? [];
|
|
420
|
+
this.contributions = options.contributions ?? [];
|
|
421
|
+
this.measuredFrom = options.measuredFrom ?? null;
|
|
422
|
+
this.totalSecs = options.totalSecs ?? 0.0;
|
|
423
|
+
this.textAggregation = options.textAggregation ?? null;
|
|
424
|
+
this.userTurnStartTime = options.userTurnStartTime ?? null;
|
|
425
|
+
this.userTurnSecs = options.userTurnSecs ?? null;
|
|
426
|
+
this.functionCalls = options.functionCalls ?? [];
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Format the contributions for logging, one per line plus a total.
|
|
430
|
+
*
|
|
431
|
+
* One breakdown covers one user-to-bot cycle, so these lines describe a
|
|
432
|
+
* single turn — or, for the first thing the bot says, the wait before it
|
|
433
|
+
* from the pipeline being ready with a client on it, which the total line
|
|
434
|
+
* names.
|
|
435
|
+
*
|
|
436
|
+
* @param options.byCost Order by duration, largest first, rather than in
|
|
437
|
+
* the order things happened. Useful when the question is what to optimize
|
|
438
|
+
* rather than what the turn did.
|
|
439
|
+
* @returns One formatted line per contribution, then a total line. Empty if
|
|
440
|
+
* no contributions were collected.
|
|
441
|
+
*/
|
|
442
|
+
turnContributionLines(options = {}) {
|
|
443
|
+
if (this.contributions.length === 0) {
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
const ordered = options.byCost
|
|
447
|
+
? [...this.contributions].sort((a, b) => b.durationSecs - a.durationSecs)
|
|
448
|
+
: this.contributions;
|
|
449
|
+
const lines = ordered.map((c) => `${formatSecs(c.durationSecs)}s ${c.label.padEnd(20)} [${c.owner}]`);
|
|
450
|
+
const total = this.contributions.reduce((sum, c) => sum + c.durationSecs, 0);
|
|
451
|
+
const anchor = this.measuredFrom === MeasuredFrom.CLIENT_CONNECTED ? " (from client connected)" : "";
|
|
452
|
+
lines.push(`${formatSecs(total)}s TOTAL${anchor}`);
|
|
453
|
+
return lines;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/** Python's `f"{secs:6.3f}"`. */
|
|
457
|
+
function formatSecs(secs) {
|
|
458
|
+
return secs.toFixed(3).padStart(6);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Observer that tracks user-to-bot response latency.
|
|
462
|
+
*
|
|
463
|
+
* Measures the time between when a user stops speaking
|
|
464
|
+
* (VADUserStoppedSpeakingFrame) and when the bot starts speaking
|
|
465
|
+
* (BotStartedSpeakingFrame). Emits events when latency is measured, allowing
|
|
466
|
+
* consumers to log, trace, or otherwise process the latency data.
|
|
467
|
+
*
|
|
468
|
+
* When `enableMetrics` is true in pipeline params, also collects a latency
|
|
469
|
+
* breakdown and emits an `on_latency_breakdown` event alongside the existing
|
|
470
|
+
* latency measurement. The breakdown carries per-service metrics (TTFB, text
|
|
471
|
+
* aggregation) and, in `contributions`, the whole interval named part by part:
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* for (const line of breakdown.turnContributionLines()) {
|
|
475
|
+
* logger.info(line);
|
|
476
|
+
* }
|
|
477
|
+
* // 0.200s endpointing wait [config: VAD stop_secs]
|
|
478
|
+
* // 0.125s transcription [DeepgramSTTService#0]
|
|
479
|
+
* // 0.336s LLM inference [OpenAILLMService#0]
|
|
480
|
+
* // 0.359s speech synthesis [CartesiaTTSService#0]
|
|
481
|
+
* // 1.020s TOTAL
|
|
482
|
+
*
|
|
483
|
+
* This observer follows the composition pattern used by
|
|
484
|
+
* TurnTrackingObserver, acting as a reusable component for latency
|
|
485
|
+
* measurement.
|
|
486
|
+
*
|
|
487
|
+
* Events:
|
|
488
|
+
*
|
|
489
|
+
* - on_latency_measured(observer, latencySeconds): emitted when
|
|
490
|
+
* time-to-first-bot-speech is calculated. Measures the time from when the
|
|
491
|
+
* user stopped speaking to when the bot starts speaking.
|
|
492
|
+
* - on_latency_breakdown(observer, breakdown): emitted at each
|
|
493
|
+
* `BotStartedSpeakingFrame` with a `LatencyBreakdown` containing
|
|
494
|
+
* per-service metrics collected during the user→bot cycle.
|
|
495
|
+
* - on_first_bot_speech_latency(observer, latencySeconds): emitted once, the
|
|
496
|
+
* first time `BotStartedSpeakingFrame` arrives after `ClientConnectedFrame`.
|
|
497
|
+
* Measures the time from client connection to the first bot speech.
|
|
498
|
+
*/
|
|
499
|
+
export class UserBotLatencyObserver extends BaseObserver {
|
|
500
|
+
_minContributionSecs;
|
|
501
|
+
_now;
|
|
502
|
+
_userStoppedTime = null;
|
|
503
|
+
_userTurnStartTime = null;
|
|
504
|
+
_userTurn = null;
|
|
505
|
+
// First bot speech tracking
|
|
506
|
+
_clientConnectedTime = null;
|
|
507
|
+
_pipelineStartedTime = null;
|
|
508
|
+
_firstBotSpeechMeasured = false;
|
|
509
|
+
// The moments of the cycle, in the order they were observed.
|
|
510
|
+
_moments = [];
|
|
511
|
+
_llmRequest = null;
|
|
512
|
+
// Whether this bot uses turn completion at all, which a marker frame
|
|
513
|
+
// proves. Not per-cycle: a bot that emits markers keeps using them.
|
|
514
|
+
_markersSeen = false;
|
|
515
|
+
// Per-cycle metric accumulators
|
|
516
|
+
_ttfb = [];
|
|
517
|
+
_textAggregation = null;
|
|
518
|
+
_functionCallStarts = new Map();
|
|
519
|
+
_functionCallMetrics = [];
|
|
520
|
+
/**
|
|
521
|
+
* Initialize the user-bot latency observer.
|
|
522
|
+
*
|
|
523
|
+
* @param options.minContributionSecs Contributions shorter than this are
|
|
524
|
+
* rolled into the single pipeline entry rather than listed.
|
|
525
|
+
* @param options.timeSource Reads the current time in seconds.
|
|
526
|
+
*/
|
|
527
|
+
constructor(options = {}) {
|
|
528
|
+
super({ ...options, observeEveryPush: false });
|
|
529
|
+
this._minContributionSecs = options.minContributionSecs ?? MIN_CONTRIBUTION_SECS;
|
|
530
|
+
this._now = options.timeSource ?? time;
|
|
531
|
+
this._registerEventHandler("on_latency_measured");
|
|
532
|
+
this._registerEventHandler("on_latency_breakdown");
|
|
533
|
+
this._registerEventHandler("on_first_bot_speech_latency");
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Record that the pipeline finished starting.
|
|
537
|
+
*
|
|
538
|
+
* The `StartFrame` has reached every processor by now, which is where
|
|
539
|
+
* `StartupTimingObserver` stops measuring. A greeting is timed from here so
|
|
540
|
+
* the two reports meet rather than overlap.
|
|
541
|
+
*/
|
|
542
|
+
async onPipelineStarted() {
|
|
543
|
+
if (this._pipelineStartedTime === null) {
|
|
544
|
+
this._pipelineStartedTime = this._now();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Process frames to track speech timing and calculate latency.
|
|
549
|
+
*
|
|
550
|
+
* Tracks VAD events and bot speaking events to measure the time between
|
|
551
|
+
* user stopping speech and bot starting speech. Also accumulates metrics
|
|
552
|
+
* from MetricsFrame for the latency breakdown.
|
|
553
|
+
*
|
|
554
|
+
* @param data Frame push event containing the frame and direction information.
|
|
555
|
+
*/
|
|
556
|
+
async onPushFrame(data) {
|
|
557
|
+
// Only process downstream frames
|
|
558
|
+
if (data.direction !== FrameDirection.DOWNSTREAM) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const frame = data.frame;
|
|
562
|
+
// Track client connection (first occurrence only)
|
|
563
|
+
if (frame instanceof ClientConnectedFrame) {
|
|
564
|
+
if (this._clientConnectedTime === null) {
|
|
565
|
+
this._clientConnectedTime = this._now();
|
|
566
|
+
}
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// Track speech and pipeline events for latency
|
|
570
|
+
if (frame instanceof VADUserStartedSpeakingFrame) {
|
|
571
|
+
// Reset when user starts speaking
|
|
572
|
+
this._userStoppedTime = null;
|
|
573
|
+
this._userTurnStartTime = null;
|
|
574
|
+
this._userTurn = null;
|
|
575
|
+
this._resetAccumulators();
|
|
576
|
+
// If user speaks before the bot's first speech, abandon the
|
|
577
|
+
// first-bot-speech measurement — it's only meaningful for greetings.
|
|
578
|
+
this._firstBotSpeechMeasured = true;
|
|
579
|
+
}
|
|
580
|
+
else if (frame instanceof VADUserStoppedSpeakingFrame) {
|
|
581
|
+
// Record the actual time the user stopped speaking, which is the VAD
|
|
582
|
+
// determination time minus the stop_secs silence duration that had to
|
|
583
|
+
// elapse before the VAD confirmed speech ended.
|
|
584
|
+
this._userStoppedTime = frame.timestamp - frame.stopSecs;
|
|
585
|
+
this._userTurnStartTime = this._userStoppedTime;
|
|
586
|
+
this._mark(_MomentKind.VAD_STOP, { at: frame.timestamp });
|
|
587
|
+
}
|
|
588
|
+
else if (frame instanceof UserStoppedSpeakingFrame) {
|
|
589
|
+
// Measure the user turn duration: from actual user silence to turn
|
|
590
|
+
// release. Includes VAD silence detection, STT finalization, and any
|
|
591
|
+
// turn analyzer wait.
|
|
592
|
+
if (this._userStoppedTime !== null) {
|
|
593
|
+
this._userTurn = this._now() - this._userStoppedTime;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else if (frame instanceof TranscriptionFrame) {
|
|
597
|
+
// A service can finalize an utterance in several pieces. The turn is
|
|
598
|
+
// not detectable until the last of them, which is also where the
|
|
599
|
+
// service stops its own TTFB clock, so a later one replaces the earlier
|
|
600
|
+
// until the LLM is asked.
|
|
601
|
+
if (!this._seen(_MomentKind.LLM_REQUEST)) {
|
|
602
|
+
this._moments = this._moments.filter((m) => m.kind !== _MomentKind.TRANSCRIPT);
|
|
603
|
+
this._mark(_MomentKind.TRANSCRIPT, { source: data.source.name });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
else if (frame instanceof LLMFullResponseStartFrame) {
|
|
607
|
+
this._llmRequest = this._mark(_MomentKind.LLM_REQUEST, { source: data.source.name });
|
|
608
|
+
}
|
|
609
|
+
else if (frame instanceof LLMMarkerFrame) {
|
|
610
|
+
// A stand-alone marker holds the turn open; one that prefixes a
|
|
611
|
+
// response is the completion the pipeline was waiting for.
|
|
612
|
+
this._markersSeen = true;
|
|
613
|
+
// A turn can be held more than once before it completes, so every
|
|
614
|
+
// incomplete verdict is recorded; only the completion is kept once.
|
|
615
|
+
this._mark(frame.appendToContextImmediately
|
|
616
|
+
? _MomentKind.MARKER_INCOMPLETE
|
|
617
|
+
: _MomentKind.MARKER_COMPLETE, { once: !frame.appendToContextImmediately });
|
|
618
|
+
}
|
|
619
|
+
else if (frame instanceof LLMTextFrame) {
|
|
620
|
+
this._mark(_MomentKind.FIRST_TEXT, { source: data.source.name, once: true });
|
|
621
|
+
}
|
|
622
|
+
else if (frame instanceof TTSAudioRawFrame) {
|
|
623
|
+
this._mark(_MomentKind.FIRST_AUDIO, { source: data.source.name, once: true });
|
|
624
|
+
}
|
|
625
|
+
else if (frame instanceof InterruptionFrame) {
|
|
626
|
+
// Discard stale metrics from cancelled LLM/TTS cycles
|
|
627
|
+
this._resetAccumulators();
|
|
628
|
+
}
|
|
629
|
+
else if (frame instanceof FunctionCallInProgressFrame) {
|
|
630
|
+
this._functionCallStarts.set(frame.toolCallId, [frame.functionName, this._now()]);
|
|
631
|
+
}
|
|
632
|
+
else if (frame instanceof FunctionCallResultFrame) {
|
|
633
|
+
const start = this._functionCallStarts.get(frame.toolCallId);
|
|
634
|
+
this._functionCallStarts.delete(frame.toolCallId);
|
|
635
|
+
if (start !== undefined) {
|
|
636
|
+
const [functionName, startTime] = start;
|
|
637
|
+
this._functionCallMetrics.push(new FunctionCallMetrics({
|
|
638
|
+
functionName,
|
|
639
|
+
startTime,
|
|
640
|
+
durationSecs: this._now() - startTime,
|
|
641
|
+
}));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else if (frame instanceof MetricsFrame) {
|
|
645
|
+
this._handleMetricsFrame(frame);
|
|
646
|
+
}
|
|
647
|
+
else if (frame instanceof BotStartedSpeakingFrame) {
|
|
648
|
+
this._mark(_MomentKind.BOT_SPEAKING, { source: data.source.name, once: true });
|
|
649
|
+
await this._handleBotStartedSpeaking();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Record a moment, if a cycle is being measured.
|
|
654
|
+
*
|
|
655
|
+
* @param kind What happened.
|
|
656
|
+
* @param options.at When, defaulting to now. Derived moments pass their own time.
|
|
657
|
+
* @param options.source Name of whatever produced it.
|
|
658
|
+
* @param options.once Keep only the first of this kind, for frames that
|
|
659
|
+
* repeat within a cycle such as text and audio.
|
|
660
|
+
* @param options.derived Whether it comes from a metric rather than from a frame.
|
|
661
|
+
* @returns The recorded moment, or null if it was not recorded.
|
|
662
|
+
*/
|
|
663
|
+
_mark(kind, options = {}) {
|
|
664
|
+
if (!this._measuring) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
if (options.once && this._seen(kind)) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
const moment = new _Moment({
|
|
671
|
+
at: options.at ?? this._now(),
|
|
672
|
+
kind,
|
|
673
|
+
source: options.source ?? "",
|
|
674
|
+
derived: options.derived ?? false,
|
|
675
|
+
});
|
|
676
|
+
this._moments.push(moment);
|
|
677
|
+
return moment;
|
|
678
|
+
}
|
|
679
|
+
/** Whether a moment of this kind has been recorded this cycle. */
|
|
680
|
+
_seen(kind) {
|
|
681
|
+
return this._moments.some((m) => m.kind === kind);
|
|
682
|
+
}
|
|
683
|
+
/** Whether a cycle is open: a user turn, or the wait for the greeting. */
|
|
684
|
+
get _measuring() {
|
|
685
|
+
if (this._userStoppedTime !== null) {
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
return this._clientConnectedTime !== null && !this._firstBotSpeechMeasured;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Moments that come from metrics rather than from frames.
|
|
692
|
+
*
|
|
693
|
+
* Handlers dispatched together run concurrently, so they become one pair of
|
|
694
|
+
* moments named for the one the wait actually depends on: listing each
|
|
695
|
+
* would count the same stretch of wall clock more than once.
|
|
696
|
+
*
|
|
697
|
+
* @returns The sentence-aggregation and function-handler moments, if any.
|
|
698
|
+
*/
|
|
699
|
+
_derivedMoments() {
|
|
700
|
+
const moments = [];
|
|
701
|
+
const firstText = this._moments.find((m) => m.kind === _MomentKind.FIRST_TEXT);
|
|
702
|
+
if (firstText && this._textAggregation && this._textAggregation.durationSecs > 0) {
|
|
703
|
+
moments.push(new _Moment({
|
|
704
|
+
at: firstText.at + this._textAggregation.durationSecs,
|
|
705
|
+
kind: _MomentKind.SENTENCE,
|
|
706
|
+
derived: true,
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
for (const group of this._concurrentHandlers()) {
|
|
710
|
+
// max() keeps the first of equal values.
|
|
711
|
+
let slowest = group[0];
|
|
712
|
+
for (const c of group) {
|
|
713
|
+
if (c.durationSecs > slowest.durationSecs)
|
|
714
|
+
slowest = c;
|
|
715
|
+
}
|
|
716
|
+
let owner = slowest.functionName;
|
|
717
|
+
if (group.length > 1) {
|
|
718
|
+
owner += ` +${group.length - 1}`;
|
|
719
|
+
}
|
|
720
|
+
moments.push(new _Moment({
|
|
721
|
+
at: Math.min(...group.map((c) => c.startTime)),
|
|
722
|
+
kind: _MomentKind.HANDLERS_START,
|
|
723
|
+
derived: true,
|
|
724
|
+
}));
|
|
725
|
+
moments.push(new _Moment({
|
|
726
|
+
at: Math.max(...group.map((c) => c.startTime + c.durationSecs)),
|
|
727
|
+
kind: _MomentKind.HANDLERS_END,
|
|
728
|
+
source: owner,
|
|
729
|
+
derived: true,
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
return moments;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Group function handlers whose executions overlap in time.
|
|
736
|
+
*
|
|
737
|
+
* @returns Groups of overlapping calls, in the order they started.
|
|
738
|
+
*/
|
|
739
|
+
_concurrentHandlers() {
|
|
740
|
+
const groups = [];
|
|
741
|
+
const sorted = [...this._functionCallMetrics].sort((a, b) => a.startTime - b.startTime);
|
|
742
|
+
for (const call of sorted) {
|
|
743
|
+
const last = groups[groups.length - 1];
|
|
744
|
+
if (last && call.startTime <= Math.max(...last.map((c) => c.startTime + c.durationSecs))) {
|
|
745
|
+
last.push(call);
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
groups.push([call]);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return groups;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Name each stretch between one moment and the next.
|
|
755
|
+
*
|
|
756
|
+
* Spans run from one moment to the following one, so they tile the cycle
|
|
757
|
+
* and cannot overlap. A stretch whose pair of moments has no entry in the
|
|
758
|
+
* table is left unnamed and reported as pipeline time, which is what makes
|
|
759
|
+
* a gap in the accounting visible rather than silently absorbed.
|
|
760
|
+
*
|
|
761
|
+
* @returns Contributions in chronological order, or empty if the cycle was
|
|
762
|
+
* never measured.
|
|
763
|
+
*/
|
|
764
|
+
_buildContributions() {
|
|
765
|
+
let start = this._userTurnStartTime;
|
|
766
|
+
if (start === null) {
|
|
767
|
+
// A greeting begins once the pipeline is ready and someone is there to
|
|
768
|
+
// hear it, whichever came second: before the pipeline started, the
|
|
769
|
+
// startup report has the time; before a client connected, the bot was
|
|
770
|
+
// waiting on a person rather than working.
|
|
771
|
+
const ready = [this._clientConnectedTime, this._pipelineStartedTime].filter((moment) => moment !== null);
|
|
772
|
+
start = ready.length > 0 ? Math.max(...ready) : null;
|
|
773
|
+
}
|
|
774
|
+
if (start === null || !this._seen(_MomentKind.BOT_SPEAKING)) {
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
const cycleStart = start;
|
|
778
|
+
const moments = [
|
|
779
|
+
new _Moment({
|
|
780
|
+
at: cycleStart,
|
|
781
|
+
kind: this._userTurnStartTime !== null ? _MomentKind.SILENCE : _MomentKind.READY,
|
|
782
|
+
}),
|
|
783
|
+
...this._moments.filter((m) => m.at >= cycleStart),
|
|
784
|
+
...this._derivedMoments(),
|
|
785
|
+
].sort((a, b) => a.at - b.at || Number(!a.derived) - Number(!b.derived));
|
|
786
|
+
let spans = [];
|
|
787
|
+
const core = new Set();
|
|
788
|
+
for (let i = 0; i + 1 < moments.length; i++) {
|
|
789
|
+
const opener = moments[i];
|
|
790
|
+
const closer = moments[i + 1];
|
|
791
|
+
if (closer.at <= opener.at) {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
let span;
|
|
795
|
+
if (opener.kind === _MomentKind.LLM_CHUNK && closer.kind === _MomentKind.FIRST_TEXT) {
|
|
796
|
+
span = this._markersSeen ? _AWAITING_SPEAKABLE_TEXT : _LLM_INFERENCE_UNTIL_TEXT;
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
span = _SPANS_BY_MOMENTS.get(spanKey(opener.kind, closer.kind));
|
|
800
|
+
}
|
|
801
|
+
if (span === undefined) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
let owner;
|
|
805
|
+
let ownerKind = LatencyOwnerKind.SERVICE;
|
|
806
|
+
if (span.owner === _Owner.OPENER) {
|
|
807
|
+
owner = opener.source;
|
|
808
|
+
}
|
|
809
|
+
else if (span.owner === _Owner.CLOSER) {
|
|
810
|
+
owner = closer.source;
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
owner = span.owner;
|
|
814
|
+
// A literal owner is not a processor: either the setting that governs
|
|
815
|
+
// the time, or the bot's own code.
|
|
816
|
+
ownerKind = owner.startsWith("config: ") ? LatencyOwnerKind.SETTING : LatencyOwnerKind.BOT;
|
|
817
|
+
}
|
|
818
|
+
if (span.core) {
|
|
819
|
+
core.add(span.label);
|
|
820
|
+
}
|
|
821
|
+
spans.push(new LatencyContribution({
|
|
822
|
+
key: span.key,
|
|
823
|
+
label: span.label,
|
|
824
|
+
owner,
|
|
825
|
+
ownerKind,
|
|
826
|
+
startTime: opener.at,
|
|
827
|
+
durationSecs: closer.at - opener.at,
|
|
828
|
+
}));
|
|
829
|
+
}
|
|
830
|
+
spans = UserBotLatencyObserver._mergeAdjacent(spans);
|
|
831
|
+
// A core stage stays listed however brief, but nothing is listed that
|
|
832
|
+
// would print as zero: a stage that took no measurable time is not part of
|
|
833
|
+
// the timeline.
|
|
834
|
+
const named = spans.filter((c) => c.durationSecs >= this._minContributionSecs ||
|
|
835
|
+
(core.has(c.label) && c.durationSecs >= PRINTS_AS_ZERO_SECS));
|
|
836
|
+
// Time the named stages did not cover, which includes the spans the
|
|
837
|
+
// threshold filtered out. It is spread across the cycle rather than
|
|
838
|
+
// sitting anywhere in it, so it is listed last, and it is listed whenever
|
|
839
|
+
// there is any of it, so the contributions always sum to the interval
|
|
840
|
+
// they describe.
|
|
841
|
+
const last = moments[moments.length - 1];
|
|
842
|
+
const pipelineSecs = last.at - cycleStart - named.reduce((sum, c) => sum + c.durationSecs, 0);
|
|
843
|
+
if (pipelineSecs >= PRINTS_AS_ZERO_SECS) {
|
|
844
|
+
named.push(new LatencyContribution({
|
|
845
|
+
key: "pipeline",
|
|
846
|
+
label: "pipeline",
|
|
847
|
+
owner: "pipecat",
|
|
848
|
+
ownerKind: LatencyOwnerKind.PIPELINE,
|
|
849
|
+
startTime: cycleStart,
|
|
850
|
+
durationSecs: pipelineSecs,
|
|
851
|
+
}));
|
|
852
|
+
}
|
|
853
|
+
return named;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Join neighbouring spans that say the same thing.
|
|
857
|
+
*
|
|
858
|
+
* A marker sits in the middle of what a reader thinks of as one stretch, so
|
|
859
|
+
* the table names both halves the same way and they are joined here. Only
|
|
860
|
+
* spans that meet are joined: two of the same name either side of an
|
|
861
|
+
* unnamed gap stay apart, so the gap is reported rather than absorbed.
|
|
862
|
+
*
|
|
863
|
+
* @param spans Contributions in chronological order.
|
|
864
|
+
* @returns The same coverage, with runs of one label and owner joined.
|
|
865
|
+
*/
|
|
866
|
+
static _mergeAdjacent(spans) {
|
|
867
|
+
const merged = [];
|
|
868
|
+
for (const span of spans) {
|
|
869
|
+
const previous = merged.length > 0 ? merged[merged.length - 1] : null;
|
|
870
|
+
const touching = previous !== null &&
|
|
871
|
+
span.startTime - (previous.startTime + previous.durationSecs) < PRINTS_AS_ZERO_SECS;
|
|
872
|
+
if (previous && touching && previous.label === span.label && previous.owner === span.owner) {
|
|
873
|
+
previous.durationSecs = span.startTime + span.durationSecs - previous.startTime;
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
merged.push(span);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return merged;
|
|
880
|
+
}
|
|
881
|
+
/** Handle BotStartedSpeakingFrame to emit latency and breakdown. */
|
|
882
|
+
async _handleBotStartedSpeaking() {
|
|
883
|
+
let emitBreakdown = false;
|
|
884
|
+
// One-time first bot speech measurement (client connect → first speech)
|
|
885
|
+
if (this._clientConnectedTime !== null && !this._firstBotSpeechMeasured) {
|
|
886
|
+
this._firstBotSpeechMeasured = true;
|
|
887
|
+
const latency = this._now() - this._clientConnectedTime;
|
|
888
|
+
await this._callEventHandler("on_first_bot_speech_latency", latency);
|
|
889
|
+
emitBreakdown = true;
|
|
890
|
+
}
|
|
891
|
+
if (this._userStoppedTime !== null) {
|
|
892
|
+
const latency = this._now() - this._userStoppedTime;
|
|
893
|
+
this._userStoppedTime = null;
|
|
894
|
+
await this._callEventHandler("on_latency_measured", latency);
|
|
895
|
+
emitBreakdown = true;
|
|
896
|
+
}
|
|
897
|
+
if (emitBreakdown) {
|
|
898
|
+
const contributions = this._buildContributions();
|
|
899
|
+
const breakdown = new LatencyBreakdown({
|
|
900
|
+
ttfb: [...this._ttfb],
|
|
901
|
+
contributions,
|
|
902
|
+
measuredFrom: contributions.length > 0
|
|
903
|
+
? this._userTurnStartTime !== null
|
|
904
|
+
? MeasuredFrom.USER_SILENCE
|
|
905
|
+
: MeasuredFrom.CLIENT_CONNECTED
|
|
906
|
+
: null,
|
|
907
|
+
totalSecs: contributions.reduce((sum, c) => sum + c.durationSecs, 0),
|
|
908
|
+
textAggregation: this._textAggregation,
|
|
909
|
+
userTurnStartTime: this._userTurnStartTime,
|
|
910
|
+
userTurnSecs: this._userTurn,
|
|
911
|
+
functionCalls: [...this._functionCallMetrics],
|
|
912
|
+
});
|
|
913
|
+
await this._callEventHandler("on_latency_breakdown", breakdown);
|
|
914
|
+
this._resetAccumulators();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Extract latency metrics from a MetricsFrame.
|
|
919
|
+
*
|
|
920
|
+
* Accumulates metrics when a measurement is in progress: either a user→bot
|
|
921
|
+
* cycle (after `VADUserStoppedSpeakingFrame`) or the first-bot-speech
|
|
922
|
+
* window (after `ClientConnectedFrame`).
|
|
923
|
+
*/
|
|
924
|
+
_handleMetricsFrame(frame) {
|
|
925
|
+
const waitingForFirstSpeech = this._clientConnectedTime !== null && !this._firstBotSpeechMeasured;
|
|
926
|
+
if (this._userStoppedTime === null && !waitingForFirstSpeech) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const now = this._now();
|
|
930
|
+
for (const metricsData of frame.data) {
|
|
931
|
+
if (metricsData instanceof TTFBMetricsData && metricsData.value > 0) {
|
|
932
|
+
this._ttfb.push(new TTFBBreakdownMetrics({
|
|
933
|
+
processor: metricsData.processor,
|
|
934
|
+
model: metricsData.model,
|
|
935
|
+
startTime: now - metricsData.value,
|
|
936
|
+
durationSecs: metricsData.value,
|
|
937
|
+
}));
|
|
938
|
+
if (this._llmRequest && metricsData.processor === this._llmRequest.source) {
|
|
939
|
+
// The first chunk landed before this metric was pushed, so cap it
|
|
940
|
+
// there rather than letting a derived moment sort after the frames
|
|
941
|
+
// that followed it.
|
|
942
|
+
this._mark(_MomentKind.LLM_CHUNK, {
|
|
943
|
+
at: Math.min(this._llmRequest.at + metricsData.value, now),
|
|
944
|
+
source: this._llmRequest.source,
|
|
945
|
+
derived: true,
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
else if (metricsData instanceof TextAggregationMetricsData) {
|
|
950
|
+
// Only keep the first measurement — it's the one that impacts the
|
|
951
|
+
// initial speaking latency.
|
|
952
|
+
if (this._textAggregation === null) {
|
|
953
|
+
this._textAggregation = new TextAggregationBreakdownMetrics({
|
|
954
|
+
processor: metricsData.processor,
|
|
955
|
+
startTime: now - metricsData.value,
|
|
956
|
+
durationSecs: metricsData.value,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
/** Clear per-cycle metric accumulators. */
|
|
963
|
+
_resetAccumulators() {
|
|
964
|
+
this._moments = [];
|
|
965
|
+
this._llmRequest = null;
|
|
966
|
+
this._ttfb = [];
|
|
967
|
+
this._textAggregation = null;
|
|
968
|
+
this._userTurnStartTime = null;
|
|
969
|
+
this._userTurn = null;
|
|
970
|
+
this._functionCallStarts = new Map();
|
|
971
|
+
this._functionCallMetrics = [];
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
//# sourceMappingURL=user_bot_latency_observer.js.map
|