1mod health;
64#[cfg(test)]
65mod tests;
66
67use std::fmt;
68use std::sync::{Arc, Mutex};
69use std::time::Instant;
70
71use async_trait::async_trait;
72use futures_util::future::BoxFuture;
73
74use crate::definition::ProviderDefinition;
75use crate::error::ProviderError;
76use crate::model::ModelInfo;
77use crate::registry::{Provider, ProviderSession, ProviderSessionScope};
78use crate::request::{CompactionRequest, MemorySummarizeRequest, Request};
79use crate::response::{CompactionResponse, MemorySummarizeResponse};
80use crate::stream::ProviderEventStream;
81
82pub use self::health::{FailureKind, GatewayRingPolicy};
83use self::health::{RingHealth, Selection};
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
87#[error("a gateway ring needs at least one member")]
88pub struct EmptyGatewayRing;
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct GatewayMemberRef {
93 pub index: usize,
95 pub label: String,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum GatewayRingEvent {
103 MemberFailed {
105 member: GatewayMemberRef,
106 kind: FailureKind,
107 consecutive_failures: u32,
110 error: String,
111 },
112 Rotated {
115 from: GatewayMemberRef,
116 to: GatewayMemberRef,
117 after_failures: u32,
119 },
120 Returned { member: GatewayMemberRef },
123 Recovered { member: GatewayMemberRef },
125}
126
127impl fmt::Display for GatewayMemberRef {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "#{} ({})", self.index, self.label)
133 }
134}
135
136impl fmt::Display for GatewayRingEvent {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::MemberFailed {
142 member,
143 kind,
144 consecutive_failures,
145 error,
146 } => {
147 let counted = match kind {
148 FailureKind::Counted => "counted",
149 FailureKind::Immediate => "rotates at once",
150 FailureKind::NotTheMembersFault => "not counted: the request's own fault",
151 };
152 write!(
153 f,
154 "gateway {member} failed ({counted}; streak {consecutive_failures}): {error}"
155 )
156 }
157 Self::Rotated {
158 from,
159 to,
160 after_failures: 0,
161 } => write!(f, "gateway ring rotated from {from} to {to} at once"),
162 Self::Rotated {
163 from,
164 to,
165 after_failures,
166 } => write!(
167 f,
168 "gateway ring rotated from {from} to {to} after {after_failures} consecutive failures"
169 ),
170 Self::Returned { member } => {
171 write!(f, "gateway ring returned to {member} on probation")
172 }
173 Self::Recovered { member } => write!(f, "gateway ring settled on {member}"),
174 }
175 }
176}
177
178type Observer = Arc<dyn Fn(GatewayRingEvent) + Send + Sync>;
179
180struct Member {
181 provider: Arc<dyn Provider>,
182 label: String,
183}
184
185impl Member {
186 fn new(provider: Arc<dyn Provider>) -> Self {
187 let definition = provider.definition();
188 let label = definition
189 .descriptor
190 .display_name
191 .clone()
192 .or_else(|| definition.base_url.clone())
193 .unwrap_or_else(|| definition.descriptor.id.to_string());
194 Self { provider, label }
195 }
196
197 fn reference(&self, index: usize) -> GatewayMemberRef {
198 GatewayMemberRef {
199 index,
200 label: self.label.clone(),
201 }
202 }
203}
204
205#[derive(Clone)]
213pub struct GatewayRing {
214 members: Arc<[Member]>,
215 definition: ProviderDefinition,
216 policy: GatewayRingPolicy,
217 health: Arc<Mutex<RingHealth>>,
218 observer: Option<Observer>,
219}
220
221impl fmt::Debug for GatewayRing {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 f.debug_struct("GatewayRing")
224 .field(
225 "members",
226 &self
227 .members
228 .iter()
229 .map(|member| member.label.as_str())
230 .collect::<Vec<_>>(),
231 )
232 .field("policy", &self.policy)
233 .field("current", &self.health().current())
234 .finish_non_exhaustive()
235 }
236}
237
238impl GatewayRing {
239 pub fn new(
244 members: impl IntoIterator<Item = Arc<dyn Provider>>,
245 policy: GatewayRingPolicy,
246 ) -> Result<Self, EmptyGatewayRing> {
247 let members: Arc<[Member]> = members.into_iter().map(Member::new).collect();
248 let first = members.first().ok_or(EmptyGatewayRing)?;
249 let definition = ring_definition(first.provider.definition(), &members);
250 Ok(Self {
251 health: Arc::new(Mutex::new(RingHealth::new(members.len()))),
252 members,
253 definition,
254 policy,
255 observer: None,
256 })
257 }
258
259 #[must_use]
263 pub fn with_observer(
264 self,
265 observer: impl Fn(GatewayRingEvent) + Send + Sync + 'static,
266 ) -> Self {
267 Self {
268 observer: Some(Arc::new(observer)),
269 ..self
270 }
271 }
272
273 pub fn policy(&self) -> GatewayRingPolicy {
275 self.policy
276 }
277
278 pub fn members(&self) -> Vec<GatewayMemberRef> {
280 self.members
281 .iter()
282 .enumerate()
283 .map(|(index, member)| member.reference(index))
284 .collect()
285 }
286
287 pub fn active(&self) -> GatewayMemberRef {
289 let index = self.health().current();
290 self.members[index].reference(index)
291 }
292
293 fn health(&self) -> RingHealth {
294 self.health
295 .lock()
296 .unwrap_or_else(std::sync::PoisonError::into_inner)
297 .clone()
298 }
299
300 fn transition<T>(&self, transition: impl FnOnce(&RingHealth) -> (RingHealth, T)) -> T {
302 let mut guard = self
303 .health
304 .lock()
305 .unwrap_or_else(std::sync::PoisonError::into_inner);
306 let (next, out) = transition(&guard);
307 *guard = next;
308 out
309 }
310
311 fn emit(&self, event: GatewayRingEvent) {
312 if let Some(observer) = &self.observer {
313 observer(event);
314 }
315 }
316
317 async fn with_rotation<A, T>(
325 &self,
326 arg: A,
327 op: impl for<'a> Fn(&'a dyn Provider, &'a A) -> BoxFuture<'a, Result<T, ProviderError>>,
328 ) -> Result<T, ProviderError>
329 where
330 A: Sync,
331 {
332 let mut selection = self.transition(|health| health.select(self.policy, Instant::now()));
333 let mut attempts = 0usize;
334 loop {
335 attempts += 1;
336 let index = selection.index;
337 let member = &self.members[index];
338 if selection.returned {
339 self.emit(GatewayRingEvent::Returned {
340 member: member.reference(index),
341 });
342 }
343 let probation = self.health().on_probation();
344 match op(member.provider.as_ref(), &arg).await {
345 Ok(value) => {
346 self.transition(|health| (health.succeeded(index), ()));
347 if probation {
348 self.emit(GatewayRingEvent::Recovered {
349 member: member.reference(index),
350 });
351 }
352 return Ok(value);
353 }
354 Err(error) => {
355 let kind = classify(&error);
356 if kind == FailureKind::NotTheMembersFault {
357 return Err(error);
358 }
359 let now = Instant::now();
360 let rotation =
361 self.transition(|health| health.failed(index, kind, self.policy, now));
362 self.emit(GatewayRingEvent::MemberFailed {
363 member: member.reference(index),
364 kind,
365 consecutive_failures: rotation
366 .map_or_else(|| self.streak_of(index), |r| r.after_failures),
367 error: error.to_string(),
368 });
369 let Some(rotation) = rotation else {
370 return Err(error);
371 };
372 self.emit(GatewayRingEvent::Rotated {
373 from: member.reference(rotation.from),
374 to: self.members[rotation.to].reference(rotation.to),
375 after_failures: rotation.after_failures,
376 });
377 if attempts >= self.members.len() {
378 return Err(error);
379 }
380 selection = Selection {
381 index: rotation.to,
382 returned: false,
383 };
384 }
385 }
386 }
387 }
388
389 fn streak_of(&self, index: usize) -> u32 {
390 self.health().streak_of(index)
391 }
392}
393
394pub fn classify(error: &ProviderError) -> FailureKind {
397 use reqwest::StatusCode;
398 match error {
399 ProviderError::Transport(_)
400 | ProviderError::Decode(_)
401 | ProviderError::Deserialize(_)
402 | ProviderError::Retryable { .. }
403 | ProviderError::InvalidResponse(_)
404 | ProviderError::MalformedStream(_) => FailureKind::Counted,
405 ProviderError::Http { status, .. } => {
406 if *status == StatusCode::TOO_MANY_REQUESTS || *status == StatusCode::REQUEST_TIMEOUT {
407 FailureKind::Counted
408 } else if status.is_client_error() {
409 FailureKind::Immediate
410 } else {
411 FailureKind::Counted
412 }
413 }
414 ProviderError::ContextLengthExceeded { .. }
415 | ProviderError::Serialize(_)
416 | ProviderError::InvalidRequest(_)
417 | ProviderError::UnsupportedCapability(_) => FailureKind::NotTheMembersFault,
418 }
419}
420
421fn ring_definition(first: ProviderDefinition, members: &[Member]) -> ProviderDefinition {
422 let labels = members
423 .iter()
424 .map(|member| member.label.as_str())
425 .collect::<Vec<_>>()
426 .join(" → ");
427 let mut definition = first;
428 definition.descriptor.display_name = Some(format!("gateway ring: {labels}"));
429 definition
430}
431
432#[async_trait]
433impl Provider for GatewayRing {
434 async fn list_models(&self) -> Result<Vec<ModelInfo>, ProviderError> {
435 self.with_rotation((), |member, ()| member.list_models())
436 .await
437 }
438
439 async fn create_session(&self) -> Result<Box<dyn ProviderSession>, ProviderError> {
440 Ok(Box::new(GatewayRingSession(self.clone())))
441 }
442
443 fn definition(&self) -> ProviderDefinition {
444 self.definition.clone()
445 }
446
447 fn fresh_session_scope(&self) -> Result<ProviderSessionScope, ProviderError> {
448 let members = self
449 .members
450 .iter()
451 .map(|member| {
452 member.provider.fresh_session_scope().map(|scope| Member {
453 provider: Arc::new(scope),
454 label: member.label.clone(),
455 })
456 })
457 .collect::<Result<Arc<[Member]>, _>>()?;
458 Ok(ProviderSessionScope::new(Self {
459 members,
460 definition: self.definition.clone(),
461 policy: self.policy,
462 health: Arc::clone(&self.health),
463 observer: self.observer.clone(),
464 }))
465 }
466
467 async fn stream(&self, request: Request<'_>) -> Result<ProviderEventStream, ProviderError> {
468 self.with_rotation(request, |member, request| member.stream(request.clone()))
469 .await
470 }
471
472 async fn compact(
473 &self,
474 request: CompactionRequest<'_>,
475 ) -> Result<CompactionResponse, ProviderError> {
476 self.with_rotation(request, |member, request| member.compact(request.clone()))
477 .await
478 }
479
480 async fn summarize_memories(
481 &self,
482 request: MemorySummarizeRequest<'_>,
483 ) -> Result<MemorySummarizeResponse, ProviderError> {
484 self.with_rotation(request, |member, request| {
485 member.summarize_memories(request.clone())
486 })
487 .await
488 }
489}
490
491struct GatewayRingSession(GatewayRing);
494
495#[async_trait]
496impl ProviderSession for GatewayRingSession {
497 async fn stream(&self, request: Request<'_>) -> Result<ProviderEventStream, ProviderError> {
498 Provider::stream(&self.0, request).await
499 }
500
501 async fn compact(
502 &self,
503 request: CompactionRequest<'_>,
504 ) -> Result<CompactionResponse, ProviderError> {
505 Provider::compact(&self.0, request).await
506 }
507
508 async fn summarize_memories(
509 &self,
510 request: MemorySummarizeRequest<'_>,
511 ) -> Result<MemorySummarizeResponse, ProviderError> {
512 Provider::summarize_memories(&self.0, request).await
513 }
514}