codexLB-up 1.25.0b10__py3-none-any.whl
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.
- app/__init__.py +10 -0
- app/admin_cli.py +494 -0
- app/cli.py +339 -0
- app/codex_sessions_retag.py +487 -0
- app/core/__init__.py +0 -0
- app/core/audit/__init__.py +0 -0
- app/core/audit/service.py +216 -0
- app/core/audit/types.py +94 -0
- app/core/auth/__init__.py +236 -0
- app/core/auth/api_key_cache.py +65 -0
- app/core/auth/dashboard_access.py +429 -0
- app/core/auth/dashboard_mode.py +171 -0
- app/core/auth/dashboard_session_ttl.py +74 -0
- app/core/auth/dashboard_users_cache.py +120 -0
- app/core/auth/dependencies.py +710 -0
- app/core/auth/external_identity.py +78 -0
- app/core/auth/guardian.py +374 -0
- app/core/auth/models.py +49 -0
- app/core/auth/providers/__init__.py +129 -0
- app/core/auth/providers/oidc.py +838 -0
- app/core/auth/providers/registry.py +118 -0
- app/core/auth/refresh.py +394 -0
- app/core/auth/step_up.py +117 -0
- app/core/auth/totp.py +81 -0
- app/core/balancer/__init__.py +71 -0
- app/core/balancer/logic.py +1612 -0
- app/core/balancer/rendezvous_hash.py +21 -0
- app/core/balancer/types.py +21 -0
- app/core/bootstrap.py +172 -0
- app/core/cache/invalidation.py +426 -0
- app/core/clients/__init__.py +0 -0
- app/core/clients/codex.py +996 -0
- app/core/clients/codex_version.py +187 -0
- app/core/clients/codex_version_snapshot.py +2 -0
- app/core/clients/files.py +534 -0
- app/core/clients/headers.py +29 -0
- app/core/clients/http.py +511 -0
- app/core/clients/model_fetcher.py +276 -0
- app/core/clients/native_egress.py +1304 -0
- app/core/clients/oauth.py +477 -0
- app/core/clients/proxy.py +6089 -0
- app/core/clients/proxy_websocket.py +1411 -0
- app/core/clients/rate_limit_reset_credits.py +412 -0
- app/core/clients/stream_errors.py +12 -0
- app/core/clients/thread_cache_identity.py +347 -0
- app/core/clients/usage.py +467 -0
- app/core/clock.py +170 -0
- app/core/config/__init__.py +0 -0
- app/core/config/background_jobs.py +73 -0
- app/core/config/context_window_overrides.py +166 -0
- app/core/config/dashboard_overrides.py +145 -0
- app/core/config/inheritable.py +47 -0
- app/core/config/key_fingerprint.py +115 -0
- app/core/config/settings.py +872 -0
- app/core/config/settings_cache.py +100 -0
- app/core/config/spool_retention.py +217 -0
- app/core/config/tiers.py +174 -0
- app/core/conversation_archive.py +568 -0
- app/core/crypto.py +37 -0
- app/core/draining.py +11 -0
- app/core/errors.py +365 -0
- app/core/exceptions.py +155 -0
- app/core/handlers/__init__.py +3 -0
- app/core/handlers/exceptions.py +478 -0
- app/core/http_protocol.py +176 -0
- app/core/http_protocol_httptools.py +204 -0
- app/core/ingress_limits.py +17 -0
- app/core/metadata_store.py +18 -0
- app/core/metrics/__init__.py +0 -0
- app/core/metrics/middleware.py +74 -0
- app/core/metrics/prometheus.py +604 -0
- app/core/middleware/__init__.py +25 -0
- app/core/middleware/api_firewall.py +111 -0
- app/core/middleware/app_version.py +37 -0
- app/core/middleware/dashboard_auth_proxy.py +55 -0
- app/core/middleware/dashboard_csrf.py +177 -0
- app/core/middleware/dashboard_gzip.py +41 -0
- app/core/middleware/dashboard_overrides.py +59 -0
- app/core/middleware/firewall_cache.py +91 -0
- app/core/middleware/inflight.py +126 -0
- app/core/middleware/multipart_content_encoding.py +129 -0
- app/core/middleware/path_rewrite.py +148 -0
- app/core/middleware/request_body_limit.py +196 -0
- app/core/middleware/request_decompression.py +209 -0
- app/core/middleware/request_id.py +72 -0
- app/core/middleware/required_capability_http.py +116 -0
- app/core/middleware/trusted_proxy_headers.py +36 -0
- app/core/multipart.py +237 -0
- app/core/multipart_fields.py +91 -0
- app/core/openai/__init__.py +0 -0
- app/core/openai/chat_requests.py +493 -0
- app/core/openai/chat_responses.py +931 -0
- app/core/openai/contracts.py +46 -0
- app/core/openai/exceptions.py +24 -0
- app/core/openai/host_models.py +14 -0
- app/core/openai/images.py +413 -0
- app/core/openai/message_coercion.py +376 -0
- app/core/openai/model_refresh_scheduler.py +423 -0
- app/core/openai/model_registry.py +997 -0
- app/core/openai/model_registry_store.py +548 -0
- app/core/openai/models.py +172 -0
- app/core/openai/parsing.py +97 -0
- app/core/openai/requests.py +1980 -0
- app/core/openai/strict_schema.py +199 -0
- app/core/openai/tool_call_safety.py +99 -0
- app/core/openai/v1_requests.py +187 -0
- app/core/plan_types.py +90 -0
- app/core/prestop.py +202 -0
- app/core/rate_limiter/__init__.py +0 -0
- app/core/rate_limiter/db_rate_limiter.py +215 -0
- app/core/request_locality.py +473 -0
- app/core/resilience/__init__.py +0 -0
- app/core/resilience/backpressure.py +57 -0
- app/core/resilience/bulkhead.py +202 -0
- app/core/resilience/circuit_breaker.py +150 -0
- app/core/resilience/degradation.py +55 -0
- app/core/resilience/loop_lag_monitor.py +60 -0
- app/core/resilience/memory_monitor.py +137 -0
- app/core/resilience/network_recovery.py +256 -0
- app/core/resilience/overload.py +98 -0
- app/core/resilience/toggles.py +114 -0
- app/core/retention/__init__.py +0 -0
- app/core/retention/job.py +240 -0
- app/core/retention/scheduler.py +69 -0
- app/core/runtime_logging.py +601 -0
- app/core/scheduling/__init__.py +0 -0
- app/core/scheduling/leader_election.py +993 -0
- app/core/scheduling/leader_election_handle.py +25 -0
- app/core/server.py +140 -0
- app/core/shutdown.py +326 -0
- app/core/socket_peer.py +22 -0
- app/core/startup.py +41 -0
- app/core/timeout_invariants.py +347 -0
- app/core/tracing/__init__.py +0 -0
- app/core/tracing/otel.py +207 -0
- app/core/types.py +6 -0
- app/core/upstream_proxy/__init__.py +13 -0
- app/core/upstream_proxy/cache.py +130 -0
- app/core/upstream_proxy/resolver.py +189 -0
- app/core/upstream_proxy/types.py +104 -0
- app/core/usage/__init__.py +407 -0
- app/core/usage/depletion.py +109 -0
- app/core/usage/live_hub.py +41 -0
- app/core/usage/live_snapshots.py +174 -0
- app/core/usage/logs.py +212 -0
- app/core/usage/metadata_scheduler.py +118 -0
- app/core/usage/models.py +54 -0
- app/core/usage/pricing.py +576 -0
- app/core/usage/pricing_catalog.py +241 -0
- app/core/usage/pricing_snapshot.json +821 -0
- app/core/usage/quota.py +133 -0
- app/core/usage/refresh_policy.py +21 -0
- app/core/usage/refresh_scheduler.py +713 -0
- app/core/usage/reset_credits_refresh_scheduler.py +435 -0
- app/core/usage/types.py +168 -0
- app/core/utils/__init__.py +0 -0
- app/core/utils/json_guards.py +18 -0
- app/core/utils/locks.py +33 -0
- app/core/utils/masking.py +20 -0
- app/core/utils/proxy_env.py +104 -0
- app/core/utils/request_id.py +65 -0
- app/core/utils/retry.py +77 -0
- app/core/utils/shared_future.py +172 -0
- app/core/utils/sse.py +286 -0
- app/core/utils/time.py +24 -0
- app/db/__init__.py +0 -0
- app/db/account_identity_lock.py +56 -0
- app/db/alembic/__init__.py +1 -0
- app/db/alembic/env.py +70 -0
- app/db/alembic/revision_ids.py +43 -0
- app/db/alembic/versions/20260213_000000_base_schema.py +248 -0
- app/db/alembic/versions/20260213_000100_normalize_account_plan_types.py +45 -0
- app/db/alembic/versions/20260213_000200_add_request_logs_reasoning_effort.py +45 -0
- app/db/alembic/versions/20260213_000300_add_accounts_reset_at.py +45 -0
- app/db/alembic/versions/20260213_000400_add_accounts_chatgpt_account_id.py +45 -0
- app/db/alembic/versions/20260213_000500_add_dashboard_settings.py +70 -0
- app/db/alembic/versions/20260213_000600_add_dashboard_settings_totp.py +64 -0
- app/db/alembic/versions/20260213_000700_add_dashboard_settings_password.py +59 -0
- app/db/alembic/versions/20260213_000800_add_api_keys.py +87 -0
- app/db/alembic/versions/20260214_000000_add_api_key_limits.py +143 -0
- app/db/alembic/versions/20260215_000000_add_idx_logs_requested_at.py +38 -0
- app/db/alembic/versions/20260218_000000_add_api_key_usage_reservations.py +117 -0
- app/db/alembic/versions/20260218_000100_add_import_without_overwrite_and_drop_accounts_email_unique.py +198 -0
- app/db/alembic/versions/20260225_000000_add_dashboard_settings_routing_strategy.py +53 -0
- app/db/alembic/versions/20260228_020000_align_api_key_limit_enum_types.py +104 -0
- app/db/alembic/versions/20260228_030000_add_api_firewall_allowlist.py +42 -0
- app/db/alembic/versions/20260306_000000_add_request_logs_service_tier.py +45 -0
- app/db/alembic/versions/20260307_000000_add_api_key_enforcement_fields.py +56 -0
- app/db/alembic/versions/20260308_000000_add_sqlite_performance_indexes.py +41 -0
- app/db/alembic/versions/20260309_000000_add_additional_usage_history.py +71 -0
- app/db/alembic/versions/20260309_000000_request_logs_nullable_account_id.py +28 -0
- app/db/alembic/versions/20260310_000000_add_request_logs_transport.py +45 -0
- app/db/alembic/versions/20260310_000000_fix_postgresql_enum_value_casing.py +79 -0
- app/db/alembic/versions/20260310_120000_add_sticky_session_kinds_and_affinity_ttl.py +110 -0
- app/db/alembic/versions/20260312_000000_add_additional_usage_quota_key.py +193 -0
- app/db/alembic/versions/20260312_000000_split_sticky_sessions_primary_key_by_kind.py +96 -0
- app/db/alembic/versions/20260312_010000_merge_additional_usage_and_sticky_session_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_feature_heads.py +25 -0
- app/db/alembic/versions/20260312_020000_merge_request_logs_transport_and_sticky_heads.py +25 -0
- app/db/alembic/versions/20260312_120000_add_dashboard_upstream_stream_transport.py +62 -0
- app/db/alembic/versions/20260313_024500_merge_request_log_transport_heads.py +27 -0
- app/db/alembic/versions/20260319_100937_increase_prompt_cache_ttl_to_1800s.py +73 -0
- app/db/alembic/versions/20260319_111500_merge_prompt_cache_ttl_and_upstream_transport_heads.py +25 -0
- app/db/alembic/versions/20260319_183000_normalize_sqlite_account_status_casing.py +48 -0
- app/db/alembic/versions/20260320_000000_add_request_log_requested_actual_tiers.py +51 -0
- app/db/alembic/versions/20260321_190000_tighten_dashboard_db_indexes.py +64 -0
- app/db/alembic/versions/20260321_210000_merge_request_log_tiers_and_dashboard_index_heads.py +25 -0
- app/db/alembic/versions/20260325_000000_add_request_log_cost.py +193 -0
- app/db/alembic/versions/20260328_000000_add_rate_limit_attempts.py +48 -0
- app/db/alembic/versions/20260328_120000_add_request_log_ttft.py +37 -0
- app/db/alembic/versions/20260328_130000_add_audit_logs_table.py +45 -0
- app/db/alembic/versions/20260328_140000_add_scheduler_leader_table.py +39 -0
- app/db/alembic/versions/20260330_000000_add_cache_locality_settings.py +69 -0
- app/db/alembic/versions/20260330_010000_merge_scheduler_leader_and_cache_locality_heads.py +25 -0
- app/db/alembic/versions/20260330_020000_add_bridge_ring_members.py +46 -0
- app/db/alembic/versions/20260401_000000_add_api_key_enforced_service_tier.py +52 -0
- app/db/alembic/versions/20260401_000000_add_cache_invalidation.py +41 -0
- app/db/alembic/versions/20260401_020000_merge_cache_invalidation_and_api_key_service_tier_heads.py +22 -0
- app/db/alembic/versions/20260402_000000_switch_dashboard_routing_default_to_capacity_weighted.py +66 -0
- app/db/alembic/versions/20260403_000000_add_credit_api_key_limit_values.py +44 -0
- app/db/alembic/versions/20260406_000000_add_api_key_account_assignments.py +57 -0
- app/db/alembic/versions/20260406_010000_add_api_key_assignment_scope_flag.py +75 -0
- app/db/alembic/versions/20260407_000000_add_bridge_gateway_safe_mode.py +51 -0
- app/db/alembic/versions/20260407_010000_merge_api_key_assignment_and_bridge_gateway_heads.py +25 -0
- app/db/alembic/versions/20260408_000000_switch_import_without_overwrite_default_to_true.py +73 -0
- app/db/alembic/versions/20260408_010000_merge_import_without_overwrite_and_assignment_heads.py +25 -0
- app/db/alembic/versions/20260409_000000_add_http_bridge_sessions.py +130 -0
- app/db/alembic/versions/20260409_000000_switch_sticky_threads_and_prefer_earlier_reset_defaults_to_true.py +89 -0
- app/db/alembic/versions/20260409_010000_add_dashboard_settings_bootstrap_token.py +57 -0
- app/db/alembic/versions/20260409_010000_merge_http_bridge_and_import_without_overwrite_heads.py +25 -0
- app/db/alembic/versions/20260409_020000_fix_http_bridge_last_seen_index.py +46 -0
- app/db/alembic/versions/20260410_000000_backfill_pristine_dashboard_settings_defaults.py +77 -0
- app/db/alembic/versions/20260410_030000_restore_import_without_overwrite_default_true.py +73 -0
- app/db/alembic/versions/20260410_040000_merge_dashboard_defaults_and_import_default_heads.py +25 -0
- app/db/alembic/versions/20260413_000000_add_accounts_blocked_at.py +44 -0
- app/db/alembic/versions/20260415_160000_add_automations_tables.py +189 -0
- app/db/alembic/versions/20260415_160000_add_request_logs_response_lookup_index.py +67 -0
- app/db/alembic/versions/20260417_000000_add_request_log_plan_type.py +44 -0
- app/db/alembic/versions/20260418_190000_add_automation_runs_cycle_key.py +87 -0
- app/db/alembic/versions/20260419_000000_add_automation_run_cycle_metadata.py +56 -0
- app/db/alembic/versions/20260419_020000_add_automation_run_cycles_snapshot_tables.py +342 -0
- app/db/alembic/versions/20260421_000000_add_dashboard_session_ttl_setting.py +51 -0
- app/db/alembic/versions/20260421_120000_merge_request_log_lookup_and_plan_type_heads.py +25 -0
- app/db/alembic/versions/20260421_130000_merge_automation_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260422_103000_normalize_legacy_automation_run_cycle_keys.py +648 -0
- app/db/alembic/versions/20260423_120000_add_api_key_limit_reset_at_index.py +43 -0
- app/db/alembic/versions/20260424_000000_merge_dashboard_session_ttl_and_request_log_heads.py +25 -0
- app/db/alembic/versions/20260426_000000_add_dashboard_relative_availability_settings.py +68 -0
- app/db/alembic/versions/20260427_130000_add_warmup_model_and_request_kind.py +75 -0
- app/db/alembic/versions/20260506_000000_add_account_routing_policy.py +37 -0
- app/db/alembic/versions/20260509_000000_add_api_key_traffic_class.py +37 -0
- app/db/alembic/versions/20260509_010000_add_additional_quota_routing_policies.py +37 -0
- app/db/alembic/versions/20260513_000000_add_accounts_alias.py +44 -0
- app/db/alembic/versions/20260513_000000_add_api_key_apply_to_codex_model.py +59 -0
- app/db/alembic/versions/20260514_000000_add_request_logs_api_key_time_index.py +43 -0
- app/db/alembic/versions/20260515_000000_add_prefer_earlier_reset_window.py +47 -0
- app/db/alembic/versions/20260515_000000_soft_delete_request_logs_on_account_delete.py +83 -0
- app/db/alembic/versions/20260515_020000_add_split_sticky_budget_thresholds.py +62 -0
- app/db/alembic/versions/20260516_000000_add_sqlite_hot_path_indexes.py +75 -0
- app/db/alembic/versions/20260517_000000_merge_sqlite_hot_path_and_request_log_delete_heads.py +24 -0
- app/db/alembic/versions/20260518_000000_add_http_bridge_durable_input_prefix.py +44 -0
- app/db/alembic/versions/20260518_010000_merge_http_bridge_and_sqlite_recovery_heads.py +25 -0
- app/db/alembic/versions/20260518_120000_add_dashboard_guest_access.py +57 -0
- app/db/alembic/versions/20260520_000000_merge_api_key_and_http_bridge_heads.py +26 -0
- app/db/alembic/versions/20260520_010000_add_request_logs_api_key_account_index.py +45 -0
- app/db/alembic/versions/20260520_010000_merge_automation_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260520_030000_add_quota_planner.py +194 -0
- app/db/alembic/versions/20260520_040000_merge_quota_planner_and_request_log_index_heads.py +24 -0
- app/db/alembic/versions/20260521_000000_add_account_security_work_authorized.py +46 -0
- app/db/alembic/versions/20260521_000000_merge_dashboard_guest_and_api_key_heads.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_prefer_reset_window_head.py +26 -0
- app/db/alembic/versions/20260521_000000_merge_warmup_and_request_log_index_heads.py +25 -0
- app/db/alembic/versions/20260522_000000_add_limit_warmup_trigger.py +195 -0
- app/db/alembic/versions/20260524_000000_merge_accounts_alias_and_reset_window_heads.py +24 -0
- app/db/alembic/versions/20260525_000000_add_usage_raw_window_latest_index.py +31 -0
- app/db/alembic/versions/20260526_000000_add_request_log_failure_metadata.py +100 -0
- app/db/alembic/versions/20260531_000000_add_account_workspace_identity.py +39 -0
- app/db/alembic/versions/20260531_000000_add_single_account_routing.py +42 -0
- app/db/alembic/versions/20260601_000000_merge_relative_availability_and_usage_raw_heads.py +24 -0
- app/db/alembic/versions/20260601_010000_add_api_key_usage_sections.py +58 -0
- app/db/alembic/versions/20260601_010000_add_routing_policy_persistence.py +67 -0
- app/db/alembic/versions/20260601_010000_merge_security_work_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_split_sticky_and_relative_usage_heads.py +25 -0
- app/db/alembic/versions/20260601_010000_merge_warmup_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_additional_quota_routing_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_quota_planner_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_reset_window_and_relative_availability_heads.py +25 -0
- app/db/alembic/versions/20260601_020000_merge_routing_policy_and_split_threshold_heads.py +24 -0
- app/db/alembic/versions/20260601_020000_merge_warmup_and_request_log_failure_heads.py +25 -0
- app/db/alembic/versions/20260601_030000_merge_single_account_and_routing_quota_heads.py +29 -0
- app/db/alembic/versions/20260601_040000_merge_security_work_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260601_050000_merge_quota_planner_and_routing_consolidation_heads.py +25 -0
- app/db/alembic/versions/20260602_050000_add_upstream_proxy_routing.py +164 -0
- app/db/alembic/versions/20260602_060000_merge_account_workspace_and_failure_heads.py +25 -0
- app/db/alembic/versions/20260602_070000_add_request_log_useragent_fields.py +86 -0
- app/db/alembic/versions/20260602_070000_merge_quota_planner_and_account_workspace_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_upstream_proxy_and_quota_planner_heads.py +25 -0
- app/db/alembic/versions/20260602_080000_merge_useragent_and_upstream_proxy_heads.py +24 -0
- app/db/alembic/versions/20260603_000000_add_weekly_pace_working_days.py +52 -0
- app/db/alembic/versions/20260603_000000_free_account_monthly_window.py +60 -0
- app/db/alembic/versions/20260604_000000_add_reauth_required_account_status.py +159 -0
- app/db/alembic/versions/20260604_010000_merge_automations_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_dashboard_guest_and_reauth_status_heads.py +25 -0
- app/db/alembic/versions/20260604_010000_merge_useragent_and_reauth_heads.py +24 -0
- app/db/alembic/versions/20260607_000000_merge_weekly_monthly_useragent_heads.py +28 -0
- app/db/alembic/versions/20260608_000000_add_hide_upstream_quota_from_api_keys.py +50 -0
- app/db/alembic/versions/20260611_000000_add_request_log_archive_request_id.py +47 -0
- app/db/alembic/versions/20260611_000000_merge_automations_and_weekly_monthly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_000000_merge_dashboard_guest_and_weekly_useragent_heads.py +26 -0
- app/db/alembic/versions/20260611_010000_add_request_log_client_ip.py +57 -0
- app/db/alembic/versions/20260611_020000_merge_request_log_archive_and_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260611_030000_merge_dashboard_guest_and_request_log_client_ip_heads.py +26 -0
- app/db/alembic/versions/20260613_000000_add_accounts_codex_installation_id.py +57 -0
- app/db/alembic/versions/20260613_010000_merge_installation_id_and_dashboard_weekly_heads.py +26 -0
- app/db/alembic/versions/20260615_000000_merge_automations_and_dashboard_guest_heads.py +26 -0
- app/db/alembic/versions/20260623_000000_add_limit_warmup_exhausted_threshold.py +51 -0
- app/db/alembic/versions/20260626_000000_add_http_downstream_transport_policy.py +46 -0
- app/db/alembic/versions/20260626_010000_add_request_logs_upstream_transport.py +31 -0
- app/db/alembic/versions/20260629_000000_add_dashboard_query_hot_path_indexes.py +53 -0
- app/db/alembic/versions/20260630_000000_add_limit_warmup_staggered_idle.py +47 -0
- app/db/alembic/versions/20260630_000000_merge_request_log_client_ip_and_dashboard_hot_path_heads.py +26 -0
- app/db/alembic/versions/20260630_010000_merge_warmup_and_request_log_dashboard_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_automations_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_020000_merge_warmup_threshold_and_main_heads.py +26 -0
- app/db/alembic/versions/20260630_030000_add_automation_run_model_snapshot.py +59 -0
- app/db/alembic/versions/20260630_040000_merge_automation_snapshot_and_warmup_threshold_heads.py +26 -0
- app/db/alembic/versions/20260630_050000_add_automation_run_prompt_snapshot.py +52 -0
- app/db/alembic/versions/20260701_000000_add_weekly_pace_smoothing_minutes.py +51 -0
- app/db/alembic/versions/20260702_000000_add_model_sources.py +176 -0
- app/db/alembic/versions/20260705_000000_add_model_source_audio_transcriptions.py +50 -0
- app/db/alembic/versions/20260705_000000_harden_dashboard_session_ttl.py +62 -0
- app/db/alembic/versions/20260706_000000_add_model_source_audio_per_minute.py +43 -0
- app/db/alembic/versions/20260707_000000_merge_model_sources_and_session_ttl.py +24 -0
- app/db/alembic/versions/20260707_010000_merge_automations_and_model_sources_heads.py +24 -0
- app/db/alembic/versions/20260707_020000_add_automation_job_account_scope.py +125 -0
- app/db/alembic/versions/20260709_000000_add_ttft_phase_observability.py +66 -0
- app/db/alembic/versions/20260711_000000_add_dashboard_prohibit_fast_mode.py +42 -0
- app/db/alembic/versions/20260711_010000_add_account_chatgpt_user_id.py +40 -0
- app/db/alembic/versions/20260711_020000_add_dashboard_account_concurrency_caps.py +71 -0
- app/db/alembic/versions/20260711_030000_add_limit_warmup_idle_threshold.py +51 -0
- app/db/alembic/versions/20260712_010000_add_account_usage_rollups.py +56 -0
- app/db/alembic/versions/20260712_020000_add_api_key_usage_rollups.py +53 -0
- app/db/alembic/versions/20260713_020000_add_model_registry_snapshot.py +42 -0
- app/db/alembic/versions/20260713_040000_add_account_refresh_claims.py +42 -0
- app/db/alembic/versions/20260713_040000_add_replica_guardrails.py +64 -0
- app/db/alembic/versions/20260713_070000_add_reset_credit_redeem_tables.py +58 -0
- app/db/alembic/versions/20260714_000000_add_oauth_flow_states.py +63 -0
- app/db/alembic/versions/20260715_000000_add_request_log_queue_latency.py +44 -0
- app/db/alembic/versions/20260716_000000_add_oauth_device_flow_slots.py +41 -0
- app/db/alembic/versions/20260716_010000_add_dashboard_retention_settings.py +48 -0
- app/db/alembic/versions/20260716_010000_add_reset_credit_display_settings.py +73 -0
- app/db/alembic/versions/20260717_000000_add_http_bridge_retry_circuits.py +47 -0
- app/db/alembic/versions/20260717_000000_merge_retention_and_reset_credit_display_heads.py +26 -0
- app/db/alembic/versions/20260717_000000_optimize_dashboard_hot_path_indexes.py +162 -0
- app/db/alembic/versions/20260717_000001_merge_retry_circuits_and_dashboard_indexes.py +22 -0
- app/db/alembic/versions/20260720_000000_add_request_log_conversation_id.py +57 -0
- app/db/alembic/versions/20260722_000000_backfill_request_log_useragent_families.py +40 -0
- app/db/alembic/versions/20260724_000000_add_request_usage_time_rollups.py +176 -0
- app/db/alembic/versions/20260724_000000_merge_request_log_schema_heads.py +33 -0
- app/db/alembic/versions/20260725_000000_add_http_bridge_pending_tool_calls.py +43 -0
- app/db/alembic/versions/20260726_000000_add_account_plan_downgrade_observations.py +44 -0
- app/db/alembic/versions/20260726_000000_repair_request_usage_rollups_after_merge.py +37 -0
- app/db/alembic/versions/20260727_000000_add_sticky_session_continuity_abandoned_at.py +45 -0
- app/db/alembic/versions/20260728_000000_merge_pending_tool_calls_and_rollup_repair_heads.py +31 -0
- app/db/alembic/versions/20260730_000000_add_api_key_fair_share_threshold.py +44 -0
- app/db/alembic/versions/20260730_000000_add_http_bridge_recovery_attempts.py +60 -0
- app/db/alembic/versions/20260731_000000_add_capability_lineage_markers.py +38 -0
- app/db/alembic/versions/20260803_000000_merge_http_bridge_recovery_and_capability_lineage_heads.py +30 -0
- app/db/alembic/versions/20260804_000000_add_http_bridge_operations.py +63 -0
- app/db/alembic/versions/20260804_000001_add_global_http_bridge_operation_fingerprint.py +53 -0
- app/db/alembic/versions/20260804_230000_add_request_log_connection_request_kind.py +43 -0
- app/db/alembic/versions/20260805_000000_add_http_bridge_operation_spool.py +75 -0
- app/db/alembic/versions/20260805_000001_finalize_http_bridge_operation_spool.py +65 -0
- app/db/alembic/versions/20260806_000000_add_additional_usage_alias_probe_indexes.py +84 -0
- app/db/alembic/versions/20260806_000000_add_anonymous_telemetry.py +49 -0
- app/db/alembic/versions/20260806_010000_add_conversation_presence_rollup.py +72 -0
- app/db/alembic/versions/20260806_020000_add_usage_history_bulk_covering_indexes.py +99 -0
- app/db/alembic/versions/20260806_030000_add_api_key_allowed_reasoning_efforts.py +65 -0
- app/db/alembic/versions/20260806_120000_add_http_bridge_owner_process_epoch.py +57 -0
- app/db/alembic/versions/20260807_000000_merge_http_bridge_operation_spool_and_latest_main.py +24 -0
- app/db/alembic/versions/20260808_000000_tune_usage_history_autovacuum.py +54 -0
- app/db/alembic/versions/20260810_000000_add_http_bridge_recovery_dispatch_count.py +44 -0
- app/db/alembic/versions/20260811_000000_add_hourly_rollup_cancelled_count.py +92 -0
- app/db/alembic/versions/20260812_000000_merge_recovery_dispatch_and_hourly_cancelled_heads.py +25 -0
- app/db/alembic/versions/20260812_120000_add_sticky_abandonment_scope.py +43 -0
- app/db/alembic/versions/20260813_000000_add_file_account_pins.py +40 -0
- app/db/alembic/versions/20260816_000000_add_account_pending_deletion.py +81 -0
- app/db/alembic/versions/20260816_000000_add_model_source_embeddings.py +50 -0
- app/db/alembic/versions/20260821_000000_add_retry_circuit_admission_generation.py +49 -0
- app/db/alembic/versions/20260826_000000_add_http_bridge_event_chunks.py +107 -0
- app/db/alembic/versions/20260828_000000_add_accounts_chatgpt_identity_index.py +55 -0
- app/db/alembic/versions/20260830_000000_add_quota_warmup_claim_expiry.py +83 -0
- app/db/alembic/versions/20260908_000000_add_guest_session_generation.py +37 -0
- app/db/alembic/versions/20260908_000000_add_subscription_overflow.py +140 -0
- app/db/alembic/versions/20260908_000000_replace_upstream_stream_transport_default_sentinel.py +50 -0
- app/db/alembic/versions/20260908_020000_merge_overflow_transport_heads.py +27 -0
- app/db/alembic/versions/20260909_000000_add_dashboard_roles.py +72 -0
- app/db/alembic/versions/20260909_010000_add_dashboard_users.py +183 -0
- app/db/alembic/versions/20260909_020000_reproject_compat_admin_credentials.py +105 -0
- app/db/alembic/versions/20260909_030000_add_audit_actor_columns.py +103 -0
- app/db/alembic/versions/20260909_030000_dashboard_resilience_toggle_settings.py +63 -0
- app/db/alembic/versions/20260909_040000_add_dashboard_user_invites.py +54 -0
- app/db/alembic/versions/20260909_040000_dashboard_timeout_settings.py +68 -0
- app/db/alembic/versions/20260909_050000_dashboard_routing_overload_settings.py +65 -0
- app/db/alembic/versions/20260909_060000_add_report_rollup.py +74 -0
- app/db/alembic/versions/20260909_070000_automation_run_claim_budget.py +50 -0
- app/db/alembic/versions/20260909_080000_dashboard_stream_bridge_budgets.py +62 -0
- app/db/alembic/versions/20260909_090000_dashboard_background_job_toggles.py +63 -0
- app/db/alembic/versions/20260909_100000_dashboard_codex_prewarm.py +49 -0
- app/db/alembic/versions/20260909_110000_model_context_window_overrides.py +45 -0
- app/db/alembic/versions/20260909_120000_dashboard_conversation_archive.py +48 -0
- app/db/alembic/versions/20260909_130000_add_request_logs_live_facet_indexes.py +96 -0
- app/db/alembic/versions/20260910_000000_add_totp_required_for_admin_role.py +44 -0
- app/db/alembic/versions/20260910_000000_request_logs_missing_cost_index.py +52 -0
- app/db/alembic/versions/20260910_010000_add_dashboard_auth_providers.py +134 -0
- app/db/alembic/versions/20260910_010000_dashboard_spool_retention.py +52 -0
- app/db/alembic/versions/20260910_020000_add_dashboard_role_mappings.py +74 -0
- app/db/alembic/versions/20260910_143000_request_log_affinity.py +28 -0
- app/db/alembic/versions/20260910_180000_merge_affinity_guest_heads.py +17 -0
- app/db/alembic/versions/20260910_200000_merge_affinity_identity_heads.py +17 -0
- app/db/alembic/versions/20260910_220000_merge_affinity_invite_heads.py +17 -0
- app/db/alembic/versions/20260911_000000_model_source_pins_kind_expires_index.py +43 -0
- app/db/alembic/versions/20260911_010000_merge_pin_index_and_affinity_heads.py +17 -0
- app/db/alembic/versions/20260911_020000_add_http_bridge_terminal_append_phase.py +54 -0
- app/db/alembic/versions/20260911_030000_add_local_login_policy.py +48 -0
- app/db/alembic/versions/20260911_040000_add_thread_cache_identity_mode.py +48 -0
- app/db/alembic/versions/20260911_060000_add_bridge_session_continuity_abandonment.py +40 -0
- app/db/alembic/versions/20260912_000000_merge_thread_cache_and_bridge_retirement_heads.py +22 -0
- app/db/alembic/versions/20260912_010000_drop_legacy_dashboard_credentials.py +140 -0
- app/db/alembic/versions/20260913_000000_add_oidc_provider_flow.py +134 -0
- app/db/alembic/versions/20260914_000000_add_scim_tokens.py +78 -0
- app/db/alembic/versions/20260914_000000_drop_subscription_overflow_schema.py +132 -0
- app/db/alembic/versions/20260918_000000_merge_scim_and_overflow_heads.py +17 -0
- app/db/alembic/versions/__init__.py +1 -0
- app/db/backup.py +64 -0
- app/db/migrate.py +1249 -0
- app/db/migration_lock.py +160 -0
- app/db/migration_url.py +18 -0
- app/db/models.py +3100 -0
- app/db/recover.py +244 -0
- app/db/session.py +1295 -0
- app/db/snapshot.py +46 -0
- app/db/sqlite_lock_retry.py +190 -0
- app/db/sqlite_utils.py +549 -0
- app/dependencies.py +462 -0
- app/main.py +1240 -0
- app/modules/__init__.py +0 -0
- app/modules/accounts/__init__.py +0 -0
- app/modules/accounts/api.py +426 -0
- app/modules/accounts/auth_manager.py +1439 -0
- app/modules/accounts/background_repository.py +160 -0
- app/modules/accounts/deletion.py +540 -0
- app/modules/accounts/mappers.py +654 -0
- app/modules/accounts/refresh_claims.py +352 -0
- app/modules/accounts/repository.py +1679 -0
- app/modules/accounts/schemas.py +308 -0
- app/modules/accounts/service.py +809 -0
- app/modules/accounts/usage_rollup.py +486 -0
- app/modules/accounts/usage_rollup_scheduler.py +75 -0
- app/modules/accounts/usage_time_rollup.py +1037 -0
- app/modules/accounts/usage_time_rollup_read.py +522 -0
- app/modules/api_keys/__init__.py +1 -0
- app/modules/api_keys/api.py +344 -0
- app/modules/api_keys/last_used_coalescer.py +216 -0
- app/modules/api_keys/limit_windows.py +41 -0
- app/modules/api_keys/repository.py +1200 -0
- app/modules/api_keys/reset_scheduler.py +85 -0
- app/modules/api_keys/schemas.py +135 -0
- app/modules/api_keys/service.py +2123 -0
- app/modules/audit/__init__.py +0 -0
- app/modules/audit/api.py +88 -0
- app/modules/audit/repository.py +74 -0
- app/modules/audit/schemas.py +33 -0
- app/modules/audit/service.py +71 -0
- app/modules/auth_providers/__init__.py +0 -0
- app/modules/auth_providers/api.py +127 -0
- app/modules/auth_providers/config.py +343 -0
- app/modules/auth_providers/repository.py +101 -0
- app/modules/auth_providers/schemas.py +88 -0
- app/modules/auth_providers/seed.py +105 -0
- app/modules/auth_providers/service.py +289 -0
- app/modules/automations/__init__.py +1 -0
- app/modules/automations/api.py +369 -0
- app/modules/automations/repository.py +1896 -0
- app/modules/automations/scheduler.py +107 -0
- app/modules/automations/schemas.py +158 -0
- app/modules/automations/service.py +2548 -0
- app/modules/cache_isolation_probe/__init__.py +0 -0
- app/modules/cache_isolation_probe/api.py +188 -0
- app/modules/cache_isolation_probe/prefix.py +144 -0
- app/modules/cache_isolation_probe/schemas.py +94 -0
- app/modules/cache_isolation_probe/sender.py +229 -0
- app/modules/cache_isolation_probe/service.py +525 -0
- app/modules/conversation_archive/__init__.py +1 -0
- app/modules/conversation_archive/api.py +101 -0
- app/modules/conversation_archive/schemas.py +28 -0
- app/modules/conversation_archive/service.py +165 -0
- app/modules/dashboard/__init__.py +1 -0
- app/modules/dashboard/api.py +106 -0
- app/modules/dashboard/builders.py +107 -0
- app/modules/dashboard/repository.py +304 -0
- app/modules/dashboard/schemas.py +148 -0
- app/modules/dashboard/service.py +534 -0
- app/modules/dashboard/timeframes.py +19 -0
- app/modules/dashboard/weekly_pace.py +617 -0
- app/modules/dashboard_auth/__init__.py +1 -0
- app/modules/dashboard_auth/api.py +1344 -0
- app/modules/dashboard_auth/oidc_api.py +753 -0
- app/modules/dashboard_auth/oidc_flows.py +330 -0
- app/modules/dashboard_auth/repository.py +452 -0
- app/modules/dashboard_auth/schemas.py +232 -0
- app/modules/dashboard_auth/service.py +1384 -0
- app/modules/dashboard_roles/__init__.py +0 -0
- app/modules/dashboard_roles/api.py +64 -0
- app/modules/dashboard_roles/repository.py +54 -0
- app/modules/dashboard_roles/schemas.py +29 -0
- app/modules/dashboard_roles/seed.py +80 -0
- app/modules/dashboard_roles/service.py +142 -0
- app/modules/dashboard_users/__init__.py +0 -0
- app/modules/dashboard_users/api.py +278 -0
- app/modules/dashboard_users/break_glass.py +169 -0
- app/modules/dashboard_users/credentials.py +18 -0
- app/modules/dashboard_users/identity_resolver.py +665 -0
- app/modules/dashboard_users/repository.py +725 -0
- app/modules/dashboard_users/schemas.py +117 -0
- app/modules/dashboard_users/service.py +1054 -0
- app/modules/firewall/__init__.py +1 -0
- app/modules/firewall/api.py +78 -0
- app/modules/firewall/repository.py +51 -0
- app/modules/firewall/schemas.py +28 -0
- app/modules/firewall/service.py +93 -0
- app/modules/fleet/__init__.py +1 -0
- app/modules/fleet/api.py +177 -0
- app/modules/fleet/mappers.py +56 -0
- app/modules/fleet/observability.py +372 -0
- app/modules/fleet/schemas.py +119 -0
- app/modules/health/__init__.py +0 -0
- app/modules/health/api.py +246 -0
- app/modules/health/schemas.py +27 -0
- app/modules/limit_warmup/__init__.py +1 -0
- app/modules/limit_warmup/repository.py +159 -0
- app/modules/limit_warmup/service.py +1001 -0
- app/modules/model_sources/__init__.py +1 -0
- app/modules/model_sources/api.py +99 -0
- app/modules/model_sources/catalog.py +307 -0
- app/modules/model_sources/forwarding.py +1435 -0
- app/modules/model_sources/projection.py +55 -0
- app/modules/model_sources/repository.py +201 -0
- app/modules/model_sources/schemas.py +79 -0
- app/modules/model_sources/selection.py +166 -0
- app/modules/model_sources/service.py +236 -0
- app/modules/oauth/__init__.py +0 -0
- app/modules/oauth/api.py +101 -0
- app/modules/oauth/repository.py +238 -0
- app/modules/oauth/schemas.py +49 -0
- app/modules/oauth/service.py +1196 -0
- app/modules/oauth/templates/oauth_success.html +122 -0
- app/modules/proxy/__init__.py +0 -0
- app/modules/proxy/_load_balancer/__init__.py +1 -0
- app/modules/proxy/_load_balancer/error_rate.py +116 -0
- app/modules/proxy/_load_balancer/latency_cohort.py +154 -0
- app/modules/proxy/_load_balancer/model_eligibility.py +250 -0
- app/modules/proxy/_load_balancer/opportunistic_admission.py +350 -0
- app/modules/proxy/_load_balancer/overload_backoff.py +394 -0
- app/modules/proxy/_load_balancer/sticky_selection.py +2417 -0
- app/modules/proxy/_load_balancer/throughput_cohort.py +233 -0
- app/modules/proxy/_load_balancer/ttft_cohort.py +214 -0
- app/modules/proxy/_load_balancer/tunables.py +97 -0
- app/modules/proxy/_load_balancer/types.py +96 -0
- app/modules/proxy/_load_balancer/unbound_selection.py +534 -0
- app/modules/proxy/_service/__init__.py +6 -0
- app/modules/proxy/_service/api_key_usage.py +745 -0
- app/modules/proxy/_service/clock_budget.py +38 -0
- app/modules/proxy/_service/codex_control.py +611 -0
- app/modules/proxy/_service/compact.py +2211 -0
- app/modules/proxy/_service/file_ops.py +749 -0
- app/modules/proxy/_service/http_bridge/__init__.py +3 -0
- app/modules/proxy/_service/http_bridge/accepted_replay.py +331 -0
- app/modules/proxy/_service/http_bridge/account_sessions.py +57 -0
- app/modules/proxy/_service/http_bridge/activity.py +178 -0
- app/modules/proxy/_service/http_bridge/helpers.py +3959 -0
- app/modules/proxy/_service/http_bridge/mixin.py +2435 -0
- app/modules/proxy/_service/http_bridge/owner_forwarding.py +609 -0
- app/modules/proxy/_service/http_bridge/protocol.py +167 -0
- app/modules/proxy/_service/http_bridge/proxy_failover.py +58 -0
- app/modules/proxy/_service/http_bridge/quarantine.py +443 -0
- app/modules/proxy/_service/http_bridge/request_submit.py +4582 -0
- app/modules/proxy/_service/http_bridge/retry_circuit.py +2442 -0
- app/modules/proxy/_service/http_bridge/service_stubs.py +492 -0
- app/modules/proxy/_service/http_bridge/session_registry.py +778 -0
- app/modules/proxy/_service/http_bridge/streaming.py +5328 -0
- app/modules/proxy/_service/http_bridge/upstream_events.py +4538 -0
- app/modules/proxy/_service/observability.py +410 -0
- app/modules/proxy/_service/rate_limit.py +361 -0
- app/modules/proxy/_service/realtime_live.py +655 -0
- app/modules/proxy/_service/refresh.py +154 -0
- app/modules/proxy/_service/request_log.py +585 -0
- app/modules/proxy/_service/response_create.py +930 -0
- app/modules/proxy/_service/streaming/__init__.py +3 -0
- app/modules/proxy/_service/streaming/helpers.py +1317 -0
- app/modules/proxy/_service/streaming/mixin.py +1100 -0
- app/modules/proxy/_service/streaming/protocol.py +32 -0
- app/modules/proxy/_service/streaming/retry.py +3762 -0
- app/modules/proxy/_service/support.py +2122 -0
- app/modules/proxy/_service/transcribe.py +434 -0
- app/modules/proxy/_service/warmup.py +483 -0
- app/modules/proxy/_service/websocket/__init__.py +3 -0
- app/modules/proxy/_service/websocket/helpers.py +2360 -0
- app/modules/proxy/_service/websocket/mixin.py +7285 -0
- app/modules/proxy/_service/websocket/protocol.py +74 -0
- app/modules/proxy/account_cache.py +229 -0
- app/modules/proxy/account_eligibility.py +87 -0
- app/modules/proxy/additional_model_limits.py +40 -0
- app/modules/proxy/affinity.py +830 -0
- app/modules/proxy/affinity_observation.py +101 -0
- app/modules/proxy/api.py +10254 -0
- app/modules/proxy/api_key_usage.py +113 -0
- app/modules/proxy/cap_partitioning.py +280 -0
- app/modules/proxy/capability_lineage.py +65 -0
- app/modules/proxy/capability_lineage_repository.py +82 -0
- app/modules/proxy/capability_routing.py +253 -0
- app/modules/proxy/continuity.py +112 -0
- app/modules/proxy/downstream_delivery.py +277 -0
- app/modules/proxy/durable_bridge_coordinator.py +1217 -0
- app/modules/proxy/durable_bridge_repository.py +4516 -0
- app/modules/proxy/durable_bridge_runtime.py +9 -0
- app/modules/proxy/durable_bridge_transcript_codec.py +122 -0
- app/modules/proxy/fair_share.py +131 -0
- app/modules/proxy/file_pin_repository.py +210 -0
- app/modules/proxy/helpers.py +439 -0
- app/modules/proxy/http_bridge_event_batcher.py +613 -0
- app/modules/proxy/http_bridge_forwarding.py +797 -0
- app/modules/proxy/http_continuation.py +76 -0
- app/modules/proxy/images_observability.py +62 -0
- app/modules/proxy/images_service.py +1054 -0
- app/modules/proxy/load_balancer.py +3016 -0
- app/modules/proxy/rate_limit_cache.py +46 -0
- app/modules/proxy/replay_safety.py +1063 -0
- app/modules/proxy/repo_bundle.py +31 -0
- app/modules/proxy/request_policy.py +1054 -0
- app/modules/proxy/ring_membership.py +251 -0
- app/modules/proxy/schemas.py +345 -0
- app/modules/proxy/selection_errors.py +41 -0
- app/modules/proxy/service.py +2600 -0
- app/modules/proxy/source_admission.py +125 -0
- app/modules/proxy/source_dispatch.py +999 -0
- app/modules/proxy/sticky_repository.py +831 -0
- app/modules/proxy/thread_anchors.py +491 -0
- app/modules/proxy/tool_call_dedupe.py +802 -0
- app/modules/proxy/types.py +54 -0
- app/modules/proxy/work_admission.py +121 -0
- app/modules/quota_planner/__init__.py +1 -0
- app/modules/quota_planner/api.py +276 -0
- app/modules/quota_planner/logic.py +743 -0
- app/modules/quota_planner/repository.py +722 -0
- app/modules/quota_planner/scheduler.py +212 -0
- app/modules/quota_planner/schemas.py +102 -0
- app/modules/quota_planner/warmup.py +693 -0
- app/modules/rate_limit_reset_credits/__init__.py +0 -0
- app/modules/rate_limit_reset_credits/api.py +601 -0
- app/modules/rate_limit_reset_credits/redeem_coordination.py +274 -0
- app/modules/rate_limit_reset_credits/store.py +114 -0
- app/modules/reports/__init__.py +1 -0
- app/modules/reports/api.py +134 -0
- app/modules/reports/cache.py +74 -0
- app/modules/reports/filters.py +27 -0
- app/modules/reports/repository.py +525 -0
- app/modules/reports/rollup.py +150 -0
- app/modules/reports/rollup_read.py +148 -0
- app/modules/reports/schemas.py +123 -0
- app/modules/reports/service.py +287 -0
- app/modules/reports/thread_identity.py +256 -0
- app/modules/request_logs/__init__.py +0 -0
- app/modules/request_logs/api.py +224 -0
- app/modules/request_logs/cost_backfill.py +154 -0
- app/modules/request_logs/mappers.py +97 -0
- app/modules/request_logs/repository.py +1765 -0
- app/modules/request_logs/schemas.py +145 -0
- app/modules/request_logs/service.py +348 -0
- app/modules/role_mappings/__init__.py +0 -0
- app/modules/role_mappings/api.py +206 -0
- app/modules/role_mappings/matching.py +55 -0
- app/modules/role_mappings/repository.py +106 -0
- app/modules/role_mappings/schemas.py +67 -0
- app/modules/role_mappings/service.py +334 -0
- app/modules/runtime/__init__.py +1 -0
- app/modules/runtime/api.py +18 -0
- app/modules/runtime/schemas.py +14 -0
- app/modules/runtime/service.py +218 -0
- app/modules/scim/__init__.py +1 -0
- app/modules/scim/api.py +194 -0
- app/modules/scim/dependencies.py +112 -0
- app/modules/scim/errors.py +77 -0
- app/modules/scim/management_api.py +156 -0
- app/modules/scim/repository.py +212 -0
- app/modules/scim/schemas.py +145 -0
- app/modules/scim/service.py +786 -0
- app/modules/scim/tokens.py +43 -0
- app/modules/settings/__init__.py +1 -0
- app/modules/settings/api.py +1677 -0
- app/modules/settings/repository.py +606 -0
- app/modules/settings/schemas.py +496 -0
- app/modules/settings/service.py +769 -0
- app/modules/shared/__init__.py +0 -0
- app/modules/shared/schemas.py +22 -0
- app/modules/sticky_sessions/__init__.py +1 -0
- app/modules/sticky_sessions/api.py +118 -0
- app/modules/sticky_sessions/cleanup_scheduler.py +487 -0
- app/modules/sticky_sessions/schemas.py +78 -0
- app/modules/sticky_sessions/service.py +196 -0
- app/modules/telemetry/__init__.py +5 -0
- app/modules/telemetry/api.py +115 -0
- app/modules/telemetry/clients.py +62 -0
- app/modules/telemetry/consent.py +115 -0
- app/modules/telemetry/scheduler.py +85 -0
- app/modules/telemetry/schemas.py +163 -0
- app/modules/telemetry/sender.py +221 -0
- app/modules/telemetry/snapshot.py +490 -0
- app/modules/usage/__init__.py +0 -0
- app/modules/usage/additional_quota_keys.py +379 -0
- app/modules/usage/api.py +45 -0
- app/modules/usage/background_repository.py +147 -0
- app/modules/usage/builders.py +559 -0
- app/modules/usage/depletion_service.py +277 -0
- app/modules/usage/live_ingest.py +356 -0
- app/modules/usage/mappers.py +29 -0
- app/modules/usage/plan_downgrade_observations.py +488 -0
- app/modules/usage/repository.py +1890 -0
- app/modules/usage/schemas.py +68 -0
- app/modules/usage/service.py +102 -0
- app/modules/usage/updater.py +1488 -0
- app/static/assets/Trans-Cj-J9mVA.js +1 -0
- app/static/assets/access-page-DHzZiXZi.js +1 -0
- app/static/assets/account-cost-donut-DGhfA4U8.js +1 -0
- app/static/assets/account-identifiers-CxbdLshZ.js +1 -0
- app/static/assets/account-status-DIsU5sUS.js +1 -0
- app/static/assets/account-trend-chart-BptlfwwQ.js +1 -0
- app/static/assets/accounts-page-Bbai8ift.js +3 -0
- app/static/assets/api-Dwa7XxPe.js +1 -0
- app/static/assets/api-client-dugX1Pti.js +55 -0
- app/static/assets/api-key-create-dialog-DnRm0oYN.js +1 -0
- app/static/assets/api-key-created-dialog-B6jkDG9f.js +1 -0
- app/static/assets/api-key-edit-dialog-Ck3Bnc3V.js +1 -0
- app/static/assets/api-trend-chart-BchCw7Hj.js +1 -0
- app/static/assets/apis-page-BvXz9c6i.js +2 -0
- app/static/assets/arrow-up-Dqh_KzJo.js +1 -0
- app/static/assets/arrow-up-down-LBRgVDGY.js +1 -0
- app/static/assets/automations-page-DQgNAJOR.js +3 -0
- app/static/assets/badge-DqMb9es0.js +1 -0
- app/static/assets/blur-email-HEvKagG2.js +1 -0
- app/static/assets/button-BjVmmiGW.js +1 -0
- app/static/assets/checkbox-apsl9vzk.js +1 -0
- app/static/assets/chevron-left-1d_pHVa5.js +1 -0
- app/static/assets/circle-alert-BS0xd_o1.js +1 -0
- app/static/assets/clsx-DB0hHKMi.js +1 -0
- app/static/assets/colors-BBy8GEtr.js +1 -0
- app/static/assets/copy-button-D7nAvOKR.js +1 -0
- app/static/assets/cost-per-day-chart-YdqlkZVM.js +1 -0
- app/static/assets/createLucideIcon-DBLzLNh2.js +1 -0
- app/static/assets/dashboard-page-CSdhBy0f.js +2 -0
- app/static/assets/distribution-metric-format-yZTy1ZEu.js +1 -0
- app/static/assets/donut-chart-Bxc69cra.js +1 -0
- app/static/assets/download-kQteIcsB.js +1 -0
- app/static/assets/ellipsis-B_5OgIre.js +1 -0
- app/static/assets/empty-state-JcA7tc8m.js +1 -0
- app/static/assets/es2015-bMH8YKZT.js +41 -0
- app/static/assets/es6-B264ni6W.js +69 -0
- app/static/assets/external-link-UVAOgMQ0.js +1 -0
- app/static/assets/i18n-BWqZ9Ua2.js +1 -0
- app/static/assets/inbox-B_RM2s1J.js +1 -0
- app/static/assets/index-DIpd264Z.js +23 -0
- app/static/assets/index-DMeLKRJx.css +2 -0
- app/static/assets/inherit-badge-QhTYVaU6.js +1 -0
- app/static/assets/invite-dialog-0CYh0GiQ.js +1 -0
- app/static/assets/iso-Bpe5Ihbb.js +1 -0
- app/static/assets/label-CGaLmLNf.js +1 -0
- app/static/assets/lazy-recharts-Pv8p9ArM.js +2 -0
- app/static/assets/loading-overlay-CzKQfHLI.js +1 -0
- app/static/assets/mini-quota-bar-cGT_t8Ae.js +1 -0
- app/static/assets/model-distribution-donut-BKjAfT48.js +1 -0
- app/static/assets/model-source-multi-select-D2d06Vc7.js +1 -0
- app/static/assets/multi-select-filter-KIA5qU6Z.js +1 -0
- app/static/assets/oauth-dialog-XeaGGDjS.js +1 -0
- app/static/assets/pagination-controls-D1Ypd0RU.js +1 -0
- app/static/assets/payload-CIYnGZz9.js +1 -0
- app/static/assets/queue-wait-chart-VoV7Yg9L.js +1 -0
- app/static/assets/report-chart-card-BXDliwcc.js +1 -0
- app/static/assets/reports-page-CGCJeMHG.js +3 -0
- app/static/assets/reset-credit-confirm-dialog-QOifTnVB.js +1 -0
- app/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- app/static/assets/search-HfhqwERt.js +1 -0
- app/static/assets/select-Cl33bz-g.js +1 -0
- app/static/assets/settings-page-mZj01HYl.js +2 -0
- app/static/assets/shim-CZwyM4Q7.js +1 -0
- app/static/assets/skeleton-C_EUc4Rp.js +1 -0
- app/static/assets/sparkline-chart-DdNNsddM.js +1 -0
- app/static/assets/status-badge-CHHA5X6c.js +1 -0
- app/static/assets/switch-CzxgokcM.js +1 -0
- app/static/assets/table-DXaNSQ0G.js +1 -0
- app/static/assets/time-to-first-token-chart--68L0u_v.js +1 -0
- app/static/assets/tokens-per-day-chart-Cs82BFuE.js +1 -0
- app/static/assets/tokens-per-second-chart-AD2dfOn6.js +1 -0
- app/static/assets/totp-settings-CRHtxQ7j.js +1 -0
- app/static/assets/trash-D3KyWr1h.js +1 -0
- app/static/assets/use-account-quota-display-BPmrTBTC.js +1 -0
- app/static/assets/use-accounts-PWlVGw3z.js +1 -0
- app/static/assets/use-auth-CuDlW3iC.js +1 -0
- app/static/assets/use-chart-colors-DIu6tClI.js +1 -0
- app/static/assets/use-model-sources-Bul24JBu.js +1 -0
- app/static/assets/use-models-DUCPUuZg.js +1 -0
- app/static/assets/use-reduced-motion-hAm5r6_d.js +1 -0
- app/static/assets/useTranslation--56nHhlH.js +1 -0
- app/static/assets/useragent-distribution-donut-Dup6sf_n.js +1 -0
- app/static/assets/users-uIC3TIlE.js +1 -0
- app/static/assets/vendor-query-_bHZuxEx.js +1 -0
- app/static/assets/vendor-react-BVL0YZPW.js +9 -0
- app/static/favicon.svg +10 -0
- app/static/fonts/GeistSans-Variable.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- app/static/fonts/jetbrains-mono-latin.woff2 +0 -0
- app/static/index.html +29 -0
- codexlb_up-1.25.0b10.dist-info/METADATA +124 -0
- codexlb_up-1.25.0b10.dist-info/RECORD +827 -0
- codexlb_up-1.25.0b10.dist-info/WHEEL +4 -0
- codexlb_up-1.25.0b10.dist-info/entry_points.txt +3 -0
- codexlb_up-1.25.0b10.dist-info/licenses/LICENSE +21 -0
- config/additional_quota_registry.json +11 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Replica-local, fleet-relative output-throughput weight per (account, model).
|
|
2
|
+
|
|
3
|
+
First-token latency (``ttft_cohort.py``) misses the dominant per-account speed
|
|
4
|
+
difference seen in production: on ``gpt-5.6-sol`` the fleet splits into two
|
|
5
|
+
output-throughput cohorts (nine accounts near 40 tok/s, ten near 66-75 tok/s)
|
|
6
|
+
whose first-token latency is the same, while on ``gpt-6-astra`` the same
|
|
7
|
+
accounts are uniform. Throughput is therefore a property of the (account,
|
|
8
|
+
model) pair, not of the account, and a slow-on-one-model account must not be
|
|
9
|
+
discounted on a model where it is not slow.
|
|
10
|
+
|
|
11
|
+
This module keeps, per account and per model, a bounded list of recent output
|
|
12
|
+
throughputs -- total ``output_tokens`` over the generation span
|
|
13
|
+
``latency_ms - latency_first_token_ms`` -- for successful ``normal`` turns
|
|
14
|
+
with at least 200 output tokens, a single upstream attempt and no local
|
|
15
|
+
queueing (the same funnel filters as the TTFT sampler), on any input size and
|
|
16
|
+
reasoning effort. ``output_tokens`` is the total the upstream reports,
|
|
17
|
+
reasoning tokens included. Where the reasoning lands depends on whether the
|
|
18
|
+
client asked for reasoning summaries: with summaries the first summary delta
|
|
19
|
+
is the first token, the reasoning runs inside the span and the total is what
|
|
20
|
+
the span produced; without summaries the whole reasoning prefix sits inside
|
|
21
|
+
the TTFT and the sample overstates the span's throughput by ``total /
|
|
22
|
+
visible`` -- several-fold on a turn that reasons for longer than it answers,
|
|
23
|
+
not a small bias. No numerator is exact for both kinds of turn (visible tokens
|
|
24
|
+
alone would under-read every summary-streaming turn by the same factor). The
|
|
25
|
+
total is kept because the comparison is fleet-relative: fresh draws spread
|
|
26
|
+
clients across accounts, so the summary/no-summary mix is shared and the
|
|
27
|
+
per-account medians move together; a per-account median only shifts when more
|
|
28
|
+
than half of that account's in-window samples are inflated, and the fleet
|
|
29
|
+
reference only when more than half of the accounts' medians are, so a spurious
|
|
30
|
+
discount needs an uneven mix that persists over most of an hour on most of the
|
|
31
|
+
fleet. Inflation reads *fast*, so it never discounts the account it lands on.
|
|
32
|
+
|
|
33
|
+
Estimator: the per-(account, model) estimate is the median of the sample
|
|
34
|
+
throughputs. Unlike first-token latency, throughput is not bimodal -- it is
|
|
35
|
+
the serving path's speed, largely independent of the prompt -- so a median
|
|
36
|
+
neither hides a mode share nor follows a single stalled stream (a network
|
|
37
|
+
pause or a slow client) or a short burst; the 200-token floor keeps the
|
|
38
|
+
quantization of ``tokens / duration`` small. The fleet reference is the median
|
|
39
|
+
of the per-account estimates for the requested model; an account below the
|
|
40
|
+
reference by more than the deadband is discounted to ``max(floor, account /
|
|
41
|
+
fleet)``. ``latency_cohort.apply_latency_cohort_weights`` combines this with the
|
|
42
|
+
TTFT multiplier as the minimum of the two (never their product) and only for
|
|
43
|
+
fresh weighted draws.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
from collections.abc import Mapping
|
|
49
|
+
from statistics import median
|
|
50
|
+
from typing import Any
|
|
51
|
+
|
|
52
|
+
from app.modules.proxy._load_balancer.ttft_cohort import (
|
|
53
|
+
TTFT_MAX_SAMPLES,
|
|
54
|
+
TTFT_MIN_ACCOUNTS,
|
|
55
|
+
TTFT_SAMPLE_REQUEST_KIND,
|
|
56
|
+
TTFT_SAMPLE_WINDOW_SECONDS,
|
|
57
|
+
)
|
|
58
|
+
from app.modules.proxy._load_balancer.types import RuntimeState
|
|
59
|
+
|
|
60
|
+
# Same window/cap pattern as the TTFT sampler, per (account, model).
|
|
61
|
+
TPS_SAMPLE_WINDOW_SECONDS = TTFT_SAMPLE_WINDOW_SECONDS
|
|
62
|
+
TPS_MAX_SAMPLES = TTFT_MAX_SAMPLES
|
|
63
|
+
# Below this many samples for a model an account's estimate is noise and stays neutral.
|
|
64
|
+
TPS_MIN_SAMPLES = 8
|
|
65
|
+
# Short answers quantize ``tokens / duration`` too coarsely to compare.
|
|
66
|
+
TPS_SAMPLE_MIN_OUTPUT_TOKENS = 200
|
|
67
|
+
# Rows written without a model (direct WebSocket rows carry ``""``) or with the
|
|
68
|
+
# placeholder cannot be attributed to a per-model cohort.
|
|
69
|
+
_UNKNOWN_MODEL = "unknown"
|
|
70
|
+
# An account within 15% below the fleet reference is not discounted at all.
|
|
71
|
+
TPS_WEIGHT_DEADBAND = 0.15
|
|
72
|
+
# A 10x slower account still keeps half of its weight: the window keeps
|
|
73
|
+
# sampling it and the discount lifts as soon as its throughput recovers.
|
|
74
|
+
TPS_WEIGHT_FLOOR = 0.5
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _sampleable_model(model: str | None) -> str | None:
|
|
78
|
+
if not model or model == _UNKNOWN_MODEL:
|
|
79
|
+
return None
|
|
80
|
+
return model
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _prune(samples_by_model: dict[str, list[tuple[float, float]]], now: float) -> None:
|
|
84
|
+
oldest_kept = now - TPS_SAMPLE_WINDOW_SECONDS
|
|
85
|
+
for model in list(samples_by_model):
|
|
86
|
+
samples = samples_by_model[model]
|
|
87
|
+
stale = 0
|
|
88
|
+
for recorded_at, _ in samples:
|
|
89
|
+
if recorded_at >= oldest_kept:
|
|
90
|
+
break
|
|
91
|
+
stale += 1
|
|
92
|
+
if stale:
|
|
93
|
+
del samples[:stale]
|
|
94
|
+
excess = len(samples) - TPS_MAX_SAMPLES
|
|
95
|
+
if excess > 0:
|
|
96
|
+
del samples[:excess]
|
|
97
|
+
if not samples:
|
|
98
|
+
del samples_by_model[model]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _throughput_sample(
|
|
102
|
+
*,
|
|
103
|
+
account_id: str | None,
|
|
104
|
+
status: str,
|
|
105
|
+
request_kind: str,
|
|
106
|
+
model: str | None,
|
|
107
|
+
latency_ms: int | None,
|
|
108
|
+
latency_first_token_ms: int | None,
|
|
109
|
+
output_tokens: int | None,
|
|
110
|
+
queued_wait_ms: int,
|
|
111
|
+
retried: bool,
|
|
112
|
+
) -> tuple[str, float] | None:
|
|
113
|
+
"""``(model, tokens_per_second)`` for an eligible row, else ``None``."""
|
|
114
|
+
sample_model = _sampleable_model(model)
|
|
115
|
+
if (
|
|
116
|
+
not account_id
|
|
117
|
+
or status != "success"
|
|
118
|
+
or request_kind != TTFT_SAMPLE_REQUEST_KIND
|
|
119
|
+
or retried
|
|
120
|
+
or sample_model is None
|
|
121
|
+
or latency_first_token_ms is None
|
|
122
|
+
or latency_first_token_ms < 0
|
|
123
|
+
or latency_ms is None
|
|
124
|
+
or latency_ms <= latency_first_token_ms
|
|
125
|
+
or output_tokens is None
|
|
126
|
+
or output_tokens < TPS_SAMPLE_MIN_OUTPUT_TOKENS
|
|
127
|
+
or queued_wait_ms > 0
|
|
128
|
+
):
|
|
129
|
+
return None
|
|
130
|
+
return sample_model, output_tokens / ((latency_ms - latency_first_token_ms) / 1000.0)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def record_tps_sample(
|
|
134
|
+
balancer: Any,
|
|
135
|
+
*,
|
|
136
|
+
account_id: str | None,
|
|
137
|
+
status: str,
|
|
138
|
+
request_kind: str,
|
|
139
|
+
model: str | None,
|
|
140
|
+
latency_ms: int | None,
|
|
141
|
+
latency_first_token_ms: int | None,
|
|
142
|
+
output_tokens: int | None,
|
|
143
|
+
queued_wait_ms: int,
|
|
144
|
+
retried: bool = False,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Record one eligible output throughput for ``(account_id, model)`` at the balancer clock.
|
|
147
|
+
|
|
148
|
+
``retried`` and ``queued_wait_ms`` have the meaning documented on
|
|
149
|
+
``ttft_cohort.record_ttft_sample``: a retried send or a capacity wait
|
|
150
|
+
leaves a failed attempt inside ``latency_ms``, and a queued row's span is
|
|
151
|
+
not the upstream's. ``latency_ms`` MUST end at the upstream terminal event,
|
|
152
|
+
not at the end of downstream delivery or local settlement/cleanup: the
|
|
153
|
+
request-log funnel passes the ``latency_upstream_terminal_ms`` each path
|
|
154
|
+
stamps when it parses the terminal frame (HTTP stream settlement, bridge
|
|
155
|
+
reader, WebSocket reader) in place of the row's latency. The throughput is
|
|
156
|
+
``output_tokens`` over the generation span
|
|
157
|
+
``latency_ms - latency_first_token_ms``; rows without a
|
|
158
|
+
model, with the ``unknown`` placeholder, without a first token, with a
|
|
159
|
+
non-positive span or with fewer than ``TPS_SAMPLE_MIN_OUTPUT_TOKENS``
|
|
160
|
+
output tokens add nothing.
|
|
161
|
+
|
|
162
|
+
Same concurrency contract as the TTFT sampler: synchronous, lock-free, no
|
|
163
|
+
await between reading and writing the per-account map; no-op when
|
|
164
|
+
``balancer`` does not expose the runtime map or clock (test doubles).
|
|
165
|
+
"""
|
|
166
|
+
runtime_map = getattr(balancer, "_runtime", None)
|
|
167
|
+
clock = getattr(balancer, "_clock", None)
|
|
168
|
+
if not isinstance(runtime_map, dict) or clock is None or not account_id:
|
|
169
|
+
return
|
|
170
|
+
sample = _throughput_sample(
|
|
171
|
+
account_id=account_id,
|
|
172
|
+
status=status,
|
|
173
|
+
request_kind=request_kind,
|
|
174
|
+
model=model,
|
|
175
|
+
latency_ms=latency_ms,
|
|
176
|
+
latency_first_token_ms=latency_first_token_ms,
|
|
177
|
+
output_tokens=output_tokens,
|
|
178
|
+
queued_wait_ms=queued_wait_ms,
|
|
179
|
+
retried=retried,
|
|
180
|
+
)
|
|
181
|
+
if sample is None:
|
|
182
|
+
return
|
|
183
|
+
sample_model, tokens_per_second = sample
|
|
184
|
+
now = float(clock.time())
|
|
185
|
+
runtime = runtime_map.setdefault(account_id, RuntimeState())
|
|
186
|
+
samples_by_model = runtime.tps_samples
|
|
187
|
+
if samples_by_model is None:
|
|
188
|
+
samples_by_model = runtime.tps_samples = {}
|
|
189
|
+
samples_by_model.setdefault(sample_model, []).append((now, tokens_per_second))
|
|
190
|
+
_prune(samples_by_model, now)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def account_tps_estimate(runtime: RuntimeState | None, model: str | None, now: float) -> float | None:
|
|
194
|
+
"""Median in-window throughput (tok/s) of ``runtime`` on ``model``; ``None`` below ``TPS_MIN_SAMPLES``."""
|
|
195
|
+
if runtime is None or not runtime.tps_samples or not model:
|
|
196
|
+
return None
|
|
197
|
+
samples = runtime.tps_samples.get(model)
|
|
198
|
+
if not samples:
|
|
199
|
+
return None
|
|
200
|
+
oldest_kept = now - TPS_SAMPLE_WINDOW_SECONDS
|
|
201
|
+
values = [tps for recorded_at, tps in samples if recorded_at >= oldest_kept]
|
|
202
|
+
if len(values) < TPS_MIN_SAMPLES:
|
|
203
|
+
return None
|
|
204
|
+
return float(median(values))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def tps_weight_multiplier(account_tps: float, fleet_tps: float) -> float:
|
|
208
|
+
"""Draw-weight multiplier in ``[floor, 1.0]`` for an account throughput relative to the fleet."""
|
|
209
|
+
if fleet_tps <= 0.0 or account_tps <= 0.0 or account_tps >= fleet_tps * (1.0 - TPS_WEIGHT_DEADBAND):
|
|
210
|
+
return 1.0
|
|
211
|
+
return max(TPS_WEIGHT_FLOOR, account_tps / fleet_tps)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def fleet_tps_estimates(
|
|
215
|
+
runtime_by_account_id: Mapping[str, RuntimeState],
|
|
216
|
+
model: str | None,
|
|
217
|
+
now: float,
|
|
218
|
+
) -> tuple[dict[str, float], float | None]:
|
|
219
|
+
"""Per-account estimates on ``model`` and the fleet reference (their median).
|
|
220
|
+
|
|
221
|
+
The reference is ``None`` -- every multiplier neutral -- when no model is
|
|
222
|
+
requested or fewer than ``TTFT_MIN_ACCOUNTS`` accounts hold enough samples
|
|
223
|
+
for that model; evidence on other models never contributes.
|
|
224
|
+
"""
|
|
225
|
+
if not model:
|
|
226
|
+
return {}, None
|
|
227
|
+
estimates = {
|
|
228
|
+
account_id: estimate
|
|
229
|
+
for account_id, runtime in runtime_by_account_id.items()
|
|
230
|
+
if (estimate := account_tps_estimate(runtime, model, now)) is not None
|
|
231
|
+
}
|
|
232
|
+
fleet_tps = median(estimates.values()) if len(estimates) >= TTFT_MIN_ACCOUNTS else None
|
|
233
|
+
return estimates, fleet_tps
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Replica-local, fleet-relative first-token latency weight per account.
|
|
2
|
+
|
|
3
|
+
The balancer has no latency signal: two accounts with equal remaining credits
|
|
4
|
+
receive the same share of fresh weighted draws even when one of them answers
|
|
5
|
+
the same small prompts noticeably later (a cohort whose upstream reasons on a
|
|
6
|
+
large share of turns, or one on a slower upstream path). Every conversation
|
|
7
|
+
that lands there pays that cohort for its whole sticky lifetime.
|
|
8
|
+
|
|
9
|
+
This module keeps a bounded list of recent first-token latencies per account
|
|
10
|
+
for one narrow slice of traffic -- successful ``normal`` turns with a small
|
|
11
|
+
uncached input, no or low reasoning effort, a single upstream attempt and no
|
|
12
|
+
local queueing (the request kinds ``warmup``, ``compaction`` and
|
|
13
|
+
``realtime_live``, any row that waited on the response-create gate or bridge
|
|
14
|
+
queue, and any WebSocket/bridge row whose first-token latency spans a retried
|
|
15
|
+
send or an account-capacity wait are excluded) -- and derives a
|
|
16
|
+
draw-weight multiplier relative to the fleet: an account whose estimate sits
|
|
17
|
+
above the fleet median by more than the deadband is discounted to
|
|
18
|
+
``max(floor, fleet / account)``. ``latency_cohort.apply_latency_cohort_weights``
|
|
19
|
+
combines it with the per-model output-throughput multiplier
|
|
20
|
+
(``throughput_cohort.py``) as the minimum of the two and applies it to fresh
|
|
21
|
+
weighted draws; sticky owners, deterministic probes and deterministic
|
|
22
|
+
strategies never consult it. The signal is replica-local and never persisted.
|
|
23
|
+
|
|
24
|
+
Estimator: the per-account estimate is the mean of the samples below the top
|
|
25
|
+
decile (an upper-trimmed mean). The slow cohort observed in production is
|
|
26
|
+
bimodal -- a large share of its turns reason before the first token -- so a
|
|
27
|
+
plain median sits on the no-reasoning mode and misses the share, while a plain
|
|
28
|
+
mean is dominated by a single 30 s stall; trimming the tail keeps the mode
|
|
29
|
+
share visible and drops the stall. The fleet reference is the median of the
|
|
30
|
+
per-account estimates, so a uniformly slow fleet (upstream-wide degradation)
|
|
31
|
+
stays neutral. Estimator changes are one-function edits here, not settings.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import math
|
|
37
|
+
from collections.abc import Mapping
|
|
38
|
+
from statistics import median
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
from app.modules.proxy._load_balancer.types import RuntimeState
|
|
42
|
+
|
|
43
|
+
# One hour of eligible turns: long enough for a quiet account to keep evidence,
|
|
44
|
+
# short enough that an account changing cohort is re-weighted within the hour.
|
|
45
|
+
TTFT_SAMPLE_WINDOW_SECONDS = 3600.0
|
|
46
|
+
# Memory bound per account when an account exceeds ~one eligible turn a
|
|
47
|
+
# minute; the window bounds staleness, the cap bounds the list.
|
|
48
|
+
TTFT_MAX_SAMPLES = 64
|
|
49
|
+
# Below this many samples an account's estimate is noise and stays neutral.
|
|
50
|
+
TTFT_MIN_SAMPLES = 8
|
|
51
|
+
# Below this many accounts with evidence there is no fleet to be relative to.
|
|
52
|
+
TTFT_MIN_ACCOUNTS = 3
|
|
53
|
+
# Only the small-input slice is comparable across accounts; the cap applies
|
|
54
|
+
# to uncached input tokens (input minus cached prefix) so long-lived sticky
|
|
55
|
+
# sessions with a large cached prefix keep sampling.
|
|
56
|
+
TTFT_SAMPLE_MAX_INPUT_TOKENS = 20_000
|
|
57
|
+
TTFT_SAMPLE_EFFORTS: frozenset[str | None] = frozenset({None, "minimal", "low"})
|
|
58
|
+
TTFT_SAMPLE_REQUEST_KIND = "normal"
|
|
59
|
+
# Share of the slowest samples dropped from each account's estimate.
|
|
60
|
+
TTFT_TRIM_FRACTION = 0.1
|
|
61
|
+
# An account within 15% of the fleet reference is not discounted at all.
|
|
62
|
+
TTFT_WEIGHT_DEADBAND = 0.15
|
|
63
|
+
# A 10x outlier still keeps half of its weight: the window keeps sampling it
|
|
64
|
+
# and the discount lifts as soon as its samples recover.
|
|
65
|
+
TTFT_WEIGHT_FLOOR = 0.5
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _prune(samples: list[tuple[float, int]], now: float) -> None:
|
|
69
|
+
oldest_kept = now - TTFT_SAMPLE_WINDOW_SECONDS
|
|
70
|
+
stale = 0
|
|
71
|
+
for recorded_at, _ in samples:
|
|
72
|
+
if recorded_at >= oldest_kept:
|
|
73
|
+
break
|
|
74
|
+
stale += 1
|
|
75
|
+
if stale:
|
|
76
|
+
del samples[:stale]
|
|
77
|
+
excess = len(samples) - TTFT_MAX_SAMPLES
|
|
78
|
+
if excess > 0:
|
|
79
|
+
del samples[:excess]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _eligible(
|
|
83
|
+
*,
|
|
84
|
+
account_id: str | None,
|
|
85
|
+
status: str,
|
|
86
|
+
request_kind: str,
|
|
87
|
+
latency_first_token_ms: int | None,
|
|
88
|
+
input_tokens: int | None,
|
|
89
|
+
cached_input_tokens: int | None,
|
|
90
|
+
reasoning_effort: str | None,
|
|
91
|
+
latency_upstream_send_ms: int | None,
|
|
92
|
+
queued_wait_ms: int,
|
|
93
|
+
retried: bool,
|
|
94
|
+
) -> bool:
|
|
95
|
+
return (
|
|
96
|
+
bool(account_id)
|
|
97
|
+
and status == "success"
|
|
98
|
+
and request_kind == TTFT_SAMPLE_REQUEST_KIND
|
|
99
|
+
and not retried
|
|
100
|
+
and latency_first_token_ms is not None
|
|
101
|
+
and latency_upstream_send_ms is not None
|
|
102
|
+
and 0 <= latency_upstream_send_ms <= latency_first_token_ms
|
|
103
|
+
and input_tokens is not None
|
|
104
|
+
and input_tokens - (cached_input_tokens or 0) < TTFT_SAMPLE_MAX_INPUT_TOKENS
|
|
105
|
+
and reasoning_effort in TTFT_SAMPLE_EFFORTS
|
|
106
|
+
and queued_wait_ms <= 0
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def record_ttft_sample(
|
|
111
|
+
balancer: Any,
|
|
112
|
+
*,
|
|
113
|
+
account_id: str | None,
|
|
114
|
+
status: str,
|
|
115
|
+
request_kind: str,
|
|
116
|
+
latency_first_token_ms: int | None,
|
|
117
|
+
input_tokens: int | None,
|
|
118
|
+
cached_input_tokens: int | None = None,
|
|
119
|
+
reasoning_effort: str | None,
|
|
120
|
+
latency_upstream_send_ms: int | None,
|
|
121
|
+
queued_wait_ms: int,
|
|
122
|
+
retried: bool = False,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Record one eligible first-token latency for ``account_id`` at the balancer clock.
|
|
125
|
+
|
|
126
|
+
The sample is ``latency_first_token_ms - latency_upstream_send_ms``: the
|
|
127
|
+
row's first-token latency runs from the request start, and on the bridge
|
|
128
|
+
path the start precedes session lookup, reconnect, prewarm, image inlining
|
|
129
|
+
and payload slimming, so the account is only charged from its
|
|
130
|
+
``response.create`` send. ``latency_upstream_send_ms`` is the start-to-send
|
|
131
|
+
offset (``0`` when the caller's clock already starts at the send, as the
|
|
132
|
+
HTTP stream's attempt clock does); a row without one (``None``) or with a
|
|
133
|
+
send after its first token has no usable anchor and is never sampled.
|
|
134
|
+
``retried`` marks a row whose first-token latency spans more than one
|
|
135
|
+
upstream send or an account-capacity wait (bridge retries, transparent
|
|
136
|
+
direct-WebSocket replays after a dropped upstream, retries after a
|
|
137
|
+
capacity wait or an owner-pinned quota error); such a latency includes
|
|
138
|
+
the failed attempt and the reconnect and, after an account switch, would
|
|
139
|
+
be charged to the destination account, so it is never sampled.
|
|
140
|
+
``queued_wait_ms`` is the response-create gate wait (which includes the
|
|
141
|
+
global response-create admission wait) plus the bridge-queue wait.
|
|
142
|
+
|
|
143
|
+
Called from the request-log funnel on stream close. Synchronous and
|
|
144
|
+
lock-free on purpose: there is no await between reading and writing the
|
|
145
|
+
per-account list, so on the single-threaded loop it cannot interleave
|
|
146
|
+
with selection, and the stream close never waits on the per-account lock
|
|
147
|
+
that ``record_errors`` holds across a database write. No-op when
|
|
148
|
+
``balancer`` does not expose the runtime map or clock (test doubles).
|
|
149
|
+
"""
|
|
150
|
+
runtime_map = getattr(balancer, "_runtime", None)
|
|
151
|
+
clock = getattr(balancer, "_clock", None)
|
|
152
|
+
if not isinstance(runtime_map, dict) or clock is None:
|
|
153
|
+
return
|
|
154
|
+
if not _eligible(
|
|
155
|
+
account_id=account_id,
|
|
156
|
+
status=status,
|
|
157
|
+
request_kind=request_kind,
|
|
158
|
+
latency_first_token_ms=latency_first_token_ms,
|
|
159
|
+
input_tokens=input_tokens,
|
|
160
|
+
cached_input_tokens=cached_input_tokens,
|
|
161
|
+
reasoning_effort=reasoning_effort,
|
|
162
|
+
latency_upstream_send_ms=latency_upstream_send_ms,
|
|
163
|
+
queued_wait_ms=queued_wait_ms,
|
|
164
|
+
retried=retried,
|
|
165
|
+
):
|
|
166
|
+
return
|
|
167
|
+
if account_id is None or latency_first_token_ms is None or latency_upstream_send_ms is None:
|
|
168
|
+
return # narrowed by ``_eligible``
|
|
169
|
+
now = float(clock.time())
|
|
170
|
+
runtime = runtime_map.setdefault(account_id, RuntimeState())
|
|
171
|
+
samples = runtime.ttft_samples
|
|
172
|
+
if samples is None:
|
|
173
|
+
samples = runtime.ttft_samples = []
|
|
174
|
+
samples.append((now, int(latency_first_token_ms - latency_upstream_send_ms)))
|
|
175
|
+
_prune(samples, now)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def account_ttft_estimate_ms(runtime: RuntimeState | None, now: float) -> float | None:
|
|
179
|
+
"""Upper-trimmed mean of the in-window samples; ``None`` below ``TTFT_MIN_SAMPLES``."""
|
|
180
|
+
if runtime is None or not runtime.ttft_samples:
|
|
181
|
+
return None
|
|
182
|
+
oldest_kept = now - TTFT_SAMPLE_WINDOW_SECONDS
|
|
183
|
+
values = sorted(ttft_ms for recorded_at, ttft_ms in runtime.ttft_samples if recorded_at >= oldest_kept)
|
|
184
|
+
if len(values) < TTFT_MIN_SAMPLES:
|
|
185
|
+
return None
|
|
186
|
+
kept = values[: len(values) - math.ceil(len(values) * TTFT_TRIM_FRACTION)]
|
|
187
|
+
return sum(kept) / len(kept)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def ttft_weight_multiplier(account_ms: float, fleet_ms: float) -> float:
|
|
191
|
+
"""Draw-weight multiplier in ``[floor, 1.0]`` for an account estimate relative to the fleet."""
|
|
192
|
+
if fleet_ms <= 0.0 or account_ms <= fleet_ms * (1.0 + TTFT_WEIGHT_DEADBAND):
|
|
193
|
+
return 1.0
|
|
194
|
+
return max(TTFT_WEIGHT_FLOOR, fleet_ms / account_ms)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def fleet_ttft_estimates(
|
|
198
|
+
runtime_by_account_id: Mapping[str, RuntimeState],
|
|
199
|
+
now: float,
|
|
200
|
+
) -> tuple[dict[str, float], float | None]:
|
|
201
|
+
"""Per-account estimates and the fleet reference (their median).
|
|
202
|
+
|
|
203
|
+
Computed over the whole runtime map so a selection narrowed to a subset is
|
|
204
|
+
still weighed against the full pool; the reference is ``None`` -- every
|
|
205
|
+
multiplier neutral -- while fewer than ``TTFT_MIN_ACCOUNTS`` accounts hold
|
|
206
|
+
enough samples.
|
|
207
|
+
"""
|
|
208
|
+
estimates = {
|
|
209
|
+
account_id: estimate
|
|
210
|
+
for account_id, runtime in runtime_by_account_id.items()
|
|
211
|
+
if (estimate := account_ttft_estimate_ms(runtime, now)) is not None
|
|
212
|
+
}
|
|
213
|
+
fleet_ms = median(estimates.values()) if len(estimates) >= TTFT_MIN_ACCOUNTS else None
|
|
214
|
+
return estimates, fleet_ms
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Dashboard-managed routing weights and overload isolation (C2-2 routing/overload).
|
|
2
|
+
|
|
3
|
+
Five balancer knobs -- the overload isolation window, the error-rate weighting
|
|
4
|
+
kill switch, the in-flight pressure penalty, the leased-token weight and the
|
|
5
|
+
account lease TTL -- live in ``dashboard_settings`` with the process
|
|
6
|
+
environment as the fallback the dashboard inherits while its column is NULL
|
|
7
|
+
(``configuration-tiers``: code default < environment < dashboard).
|
|
8
|
+
|
|
9
|
+
Selection is a hot path that must not touch the database and must not read
|
|
10
|
+
settings while holding a runtime lock, so the values are resolved *once per
|
|
11
|
+
selection or lease operation* from the ``SettingsCache`` snapshot the proxy
|
|
12
|
+
service already holds for that stage (the same snapshot the concurrency caps
|
|
13
|
+
come from), frozen into a :class:`RoutingTunables`, and threaded through the
|
|
14
|
+
selection entry points next to ``routing_strategy`` and the caps; nothing is
|
|
15
|
+
re-read inside a lock section. Stages of one request share the cache's TTL
|
|
16
|
+
window. A caller without a snapshot (tests, tools) resolves from the
|
|
17
|
+
environment alone.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
|
|
24
|
+
from app.core.config.inheritable import resolve_inheritable
|
|
25
|
+
from app.modules.proxy._load_balancer.types import AccountLeaseKind
|
|
26
|
+
|
|
27
|
+
# Code defaults, kept in step with the ``Settings`` fields of the same name so
|
|
28
|
+
# a caller without any settings object still gets the shipped behaviour.
|
|
29
|
+
DEFAULT_OVERLOAD_ISOLATION_SECONDS = 1800
|
|
30
|
+
DEFAULT_ERROR_RATE_WEIGHTING_ENABLED = True
|
|
31
|
+
DEFAULT_INFLIGHT_PENALTY_PCT = 2.5
|
|
32
|
+
DEFAULT_LEASE_TOKEN_WEIGHT = 1.0
|
|
33
|
+
DEFAULT_LEASE_TTL_SECONDS = 900.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class RoutingTunables:
|
|
38
|
+
"""Effective routing/overload knobs for one selection or lease operation."""
|
|
39
|
+
|
|
40
|
+
overload_isolation_seconds: float = float(DEFAULT_OVERLOAD_ISOLATION_SECONDS)
|
|
41
|
+
error_rate_weighting_enabled: bool = DEFAULT_ERROR_RATE_WEIGHTING_ENABLED
|
|
42
|
+
inflight_penalty_pct: float = DEFAULT_INFLIGHT_PENALTY_PCT
|
|
43
|
+
lease_token_weight: float = DEFAULT_LEASE_TOKEN_WEIGHT
|
|
44
|
+
lease_ttl_seconds: float = DEFAULT_LEASE_TTL_SECONDS
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_routing_tunables(
|
|
48
|
+
dashboard_settings: object | None,
|
|
49
|
+
*,
|
|
50
|
+
startup_settings: object,
|
|
51
|
+
) -> RoutingTunables:
|
|
52
|
+
"""Resolve the five knobs as code default < environment < dashboard.
|
|
53
|
+
|
|
54
|
+
``dashboard_settings`` is the cached ``dashboard_settings`` row (or ``None``
|
|
55
|
+
when the caller has no snapshot); ``startup_settings`` is the process
|
|
56
|
+
``Settings``. Both are read with ``getattr`` so partial stand-ins used by
|
|
57
|
+
tests resolve to the code default for the fields they omit.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def pick[T: (int, float, bool)](name: str, default: T) -> T:
|
|
61
|
+
column = getattr(dashboard_settings, name, None)
|
|
62
|
+
environment = getattr(startup_settings, name, None)
|
|
63
|
+
return resolve_inheritable(column, environment, default).value
|
|
64
|
+
|
|
65
|
+
return RoutingTunables(
|
|
66
|
+
overload_isolation_seconds=float(pick("proxy_overload_isolation_seconds", DEFAULT_OVERLOAD_ISOLATION_SECONDS)),
|
|
67
|
+
error_rate_weighting_enabled=bool(
|
|
68
|
+
pick("proxy_account_error_rate_weighting_enabled", DEFAULT_ERROR_RATE_WEIGHTING_ENABLED)
|
|
69
|
+
),
|
|
70
|
+
inflight_penalty_pct=float(pick("proxy_account_inflight_penalty_pct", DEFAULT_INFLIGHT_PENALTY_PCT)),
|
|
71
|
+
lease_token_weight=float(pick("proxy_account_lease_token_weight", DEFAULT_LEASE_TOKEN_WEIGHT)),
|
|
72
|
+
lease_ttl_seconds=float(pick("proxy_account_lease_ttl_seconds", DEFAULT_LEASE_TTL_SECONDS)),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_ACCOUNT_STREAM_LEASE_STALE_GRACE_SECONDS = 60.0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def account_lease_stale_ttl_seconds(
|
|
80
|
+
kind: AccountLeaseKind, settings: object, *, routing_tunables: RoutingTunables
|
|
81
|
+
) -> float:
|
|
82
|
+
"""Age after which a lease of ``kind`` is reclaimed as stale.
|
|
83
|
+
|
|
84
|
+
The lease TTL comes from the dashboard snapshot; a stream lease also
|
|
85
|
+
outlives the effective request budgets (``settings`` carries the C2-1
|
|
86
|
+
dashboard overlay) plus a grace period.
|
|
87
|
+
"""
|
|
88
|
+
ttl_seconds = float(routing_tunables.lease_ttl_seconds)
|
|
89
|
+
if kind != "stream":
|
|
90
|
+
return ttl_seconds
|
|
91
|
+
valid_stream_budget_seconds = max(
|
|
92
|
+
ttl_seconds,
|
|
93
|
+
float(getattr(settings, "proxy_request_budget_seconds", ttl_seconds)),
|
|
94
|
+
float(getattr(settings, "http_responses_stream_request_budget_seconds", ttl_seconds)),
|
|
95
|
+
float(getattr(settings, "http_responses_session_bridge_request_budget_seconds", ttl_seconds)),
|
|
96
|
+
)
|
|
97
|
+
return max(ttl_seconds, valid_stream_budget_seconds + _ACCOUNT_STREAM_LEASE_STALE_GRACE_SECONDS)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
AccountLeaseKind = Literal["response_create", "stream"]
|
|
7
|
+
|
|
8
|
+
MAX_SELECTION_ATTEMPTS = 4
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class RuntimeState:
|
|
13
|
+
reset_at: float | None = None
|
|
14
|
+
cooldown_until: float | None = None
|
|
15
|
+
last_error_at: float | None = None
|
|
16
|
+
last_selected_at: float | None = None
|
|
17
|
+
error_count: int = 0
|
|
18
|
+
version: int = 0
|
|
19
|
+
health_version: int = 0
|
|
20
|
+
blocked_at: float | None = None
|
|
21
|
+
health_tier: int = 0
|
|
22
|
+
drain_entered_at: float | None = None
|
|
23
|
+
probe_success_streak: int = 0
|
|
24
|
+
inflight_response_creates: int = 0
|
|
25
|
+
inflight_streams: int = 0
|
|
26
|
+
leased_tokens: float = 0.0
|
|
27
|
+
leases: dict[str, AccountLease] | None = None
|
|
28
|
+
stream_key_inflight: dict[str, int] | None = None
|
|
29
|
+
# Upstream-overload soft backoff (see ``_load_balancer/overload_backoff.py``).
|
|
30
|
+
# Recent ``server_is_overloaded`` rejection times inside the trip window,
|
|
31
|
+
# the deprioritization deadline once tripped, the exponential level, and
|
|
32
|
+
# when the level last tripped (for decay). Replica-local, never persisted.
|
|
33
|
+
overload_rejections: list[float] | None = None
|
|
34
|
+
# Recent *soft* overload observations: bare ``server_error`` terminals that
|
|
35
|
+
# upstream returns for the same admission-rejection condition but without
|
|
36
|
+
# the explicit overload code. Counted at a fractional weight so a genuine
|
|
37
|
+
# one-off fault never trips the window on its own, while a sustained
|
|
38
|
+
# ``server_error`` refusal still deprioritizes the account.
|
|
39
|
+
soft_overload_rejections: list[float] | None = None
|
|
40
|
+
overload_backoff_until: float | None = None
|
|
41
|
+
overload_backoff_level: int = 0
|
|
42
|
+
overload_last_trip_at: float | None = None
|
|
43
|
+
# Isolation stage deadline (subset of the backoff deadline): while set and
|
|
44
|
+
# in the future, soft sticky owners are rerouted too.
|
|
45
|
+
overload_isolated_until: float | None = None
|
|
46
|
+
# Short burst cooldown set by a code-less upstream HTTP 429 (per-account
|
|
47
|
+
# burst/concurrency rejection, no ``rate_limit_exceeded`` / usage code):
|
|
48
|
+
# fresh (unbound) selection and fresh sticky bindings avoid the account
|
|
49
|
+
# until this deadline while another candidate exists. Established sticky
|
|
50
|
+
# owners and hard continuity owners are untouched. Replica-local, never
|
|
51
|
+
# persisted, independent of ``cooldown_until`` / persisted RATE_LIMITED.
|
|
52
|
+
burst_backoff_until: float | None = None
|
|
53
|
+
# Recent upstream outcome window (see ``_load_balancer/error_rate.py``):
|
|
54
|
+
# minute bucket -> [successes, failures], pruned to the configured window.
|
|
55
|
+
# Feeds the selection weight multiplier; replica-local, never persisted.
|
|
56
|
+
outcome_buckets: dict[int, list[int]] | None = None
|
|
57
|
+
# Recent eligible first-token latencies (see ``_load_balancer/ttft_cohort.py``):
|
|
58
|
+
# ``(recorded_at, ttft_ms)`` pruned to the window/cap. Replica-local, never persisted.
|
|
59
|
+
ttft_samples: list[tuple[float, int]] | None = None
|
|
60
|
+
# Recent eligible output throughputs per model (see
|
|
61
|
+
# ``_load_balancer/throughput_cohort.py``): model -> ``(recorded_at, tokens_per_second)``
|
|
62
|
+
# pruned to the window/cap; models without in-window samples are dropped.
|
|
63
|
+
tps_samples: dict[str, list[tuple[float, float]]] | None = None
|
|
64
|
+
# Transition-log gates of ``_load_balancer/latency_cohort.py``: the last
|
|
65
|
+
# applied first-token multiplier (model-agnostic) and the last applied
|
|
66
|
+
# non-neutral throughput multiplier per model (neutral models are dropped,
|
|
67
|
+
# so the map only grows with models the account is discounted on).
|
|
68
|
+
ttft_weight: float = 1.0
|
|
69
|
+
tps_weights: dict[str, float] | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True, slots=True)
|
|
73
|
+
class ProbeReservation:
|
|
74
|
+
account_id: str
|
|
75
|
+
previous_last_selected_at: float | None
|
|
76
|
+
reserved_at: float
|
|
77
|
+
expected_runtime_version: int
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True, slots=True)
|
|
81
|
+
class AccountLease:
|
|
82
|
+
lease_id: str
|
|
83
|
+
account_id: str
|
|
84
|
+
kind: AccountLeaseKind
|
|
85
|
+
acquired_at: float
|
|
86
|
+
estimated_tokens: float = 0.0
|
|
87
|
+
api_key_id: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True, slots=True)
|
|
91
|
+
class AccountConcurrencyCaps:
|
|
92
|
+
response_create_limit: int
|
|
93
|
+
stream_limit: int
|
|
94
|
+
configured_response_create_limit: int | None = None
|
|
95
|
+
configured_stream_limit: int | None = None
|
|
96
|
+
replica_count: int = 1
|