| 100 | |
| 101 | |
| 102 | class ModelExperienceStore: |
| 103 | def __init__( |
| 104 | self, |
| 105 | storage: ModelExperienceStorage | None = None, |
| 106 | *, |
| 107 | alpha: float = 0.25, |
| 108 | now_fn: object = None, |
| 109 | ) -> None: |
| 110 | self._storage = storage or FileModelExperienceStorage() |
| 111 | self._alpha = max(0.05, min(0.9, alpha)) |
| 112 | self._now = now_fn if callable(now_fn) else time.time |
| 113 | self._records: dict[str, ModelExperienceRecord] = {} |
| 114 | self._load() |
| 115 | |
| 116 | def observe( |
| 117 | self, |
| 118 | model: str, |
| 119 | mode: RoutingMode | str, |
| 120 | tier: Tier | str, |
| 121 | *, |
| 122 | success: bool, |
| 123 | ttft_ms: float | None = None, |
| 124 | tps: float | None = None, |
| 125 | total_input_tokens: int | None = None, |
| 126 | uncached_input_tokens: int | None = None, |
| 127 | cache_read_tokens: int = 0, |
| 128 | cache_write_tokens: int = 0, |
| 129 | input_cost_multiplier: float | None = None, |
| 130 | ) -> None: |
| 131 | record = self._get_or_create(model, mode, tier) |
| 132 | record.requests += 1 |
| 133 | if success: |
| 134 | record.successes += 1 |
| 135 | else: |
| 136 | record.failures += 1 |
| 137 | target = 1.0 if success else 0.0 |
| 138 | record.success_ewma = self._blend(record.success_ewma, target) |
| 139 | if ttft_ms is not None and ttft_ms > 0: |
| 140 | record.ttft_ms_ewma = self._blend_metric(record.ttft_ms_ewma, ttft_ms) |
| 141 | if tps is not None and tps > 0: |
| 142 | record.tps_ewma = self._blend_metric(record.tps_ewma, tps) |
| 143 | normalized_total_input = max( |
| 144 | 0, |
| 145 | int(total_input_tokens or 0), |
| 146 | int(uncached_input_tokens or 0) + max(0, int(cache_read_tokens)) + max(0, int(cache_write_tokens)), |
| 147 | ) |
| 148 | if normalized_total_input > 0: |
| 149 | hit_ratio = max(0.0, min(1.0, float(cache_read_tokens) / float(normalized_total_input))) |
| 150 | write_ratio = max(0.0, min(1.0, float(cache_write_tokens) / float(normalized_total_input))) |
| 151 | record.cache_hit_ratio_ewma = self._blend_metric(record.cache_hit_ratio_ewma, hit_ratio) |
| 152 | record.cache_write_ratio_ewma = self._blend_metric(record.cache_write_ratio_ewma, write_ratio) |
| 153 | if input_cost_multiplier is not None and input_cost_multiplier > 0: |
| 154 | record.input_cost_multiplier_ewma = self._blend_metric( |
| 155 | record.input_cost_multiplier_ewma, |
| 156 | input_cost_multiplier, |
| 157 | ) |
| 158 | # Only update reward for FAILURES (real signal). |
| 159 | # HTTP 200 success is an availability signal, not a quality signal. |
no outgoing calls