1use backoff::ExponentialBackoffBuilder;
4use langfuse_core::config::LangfuseConfig;
5use langfuse_core::error::LangfuseError;
6use std::time::Duration;
7
8pub fn build_http_client(config: &LangfuseConfig) -> reqwest::Client {
10 let mut builder = reqwest::Client::builder().timeout(config.timeout);
11
12 if let Some(ref extra) = config.additional_headers {
13 let mut header_map = reqwest::header::HeaderMap::new();
14 for (k, v) in extra {
15 if let (Ok(name), Ok(val)) = (
16 reqwest::header::HeaderName::from_bytes(k.as_bytes()),
17 reqwest::header::HeaderValue::from_str(v),
18 ) {
19 header_map.insert(name, val);
20 }
21 }
22 builder = builder.default_headers(header_map);
23 }
24
25 builder.build().unwrap_or_else(|_| reqwest::Client::new())
26}
27
28pub async fn retry_request<F, Fut, T>(max_retries: usize, f: F) -> Result<T, LangfuseError>
39where
40 F: Fn() -> Fut,
41 Fut: std::future::Future<Output = Result<T, LangfuseError>>,
42{
43 let mut backoff = ExponentialBackoffBuilder::default()
44 .with_initial_interval(Duration::from_millis(100))
45 .with_max_interval(Duration::from_secs(30))
46 .with_max_elapsed_time(None)
47 .build();
48
49 let mut attempt = 0;
50 loop {
51 match f().await {
52 Ok(val) => return Ok(val),
53 Err(e) if should_retry(&e) && attempt < max_retries => {
54 attempt += 1;
55 if let Some(delay) = backoff::backoff::Backoff::next_backoff(&mut backoff) {
56 tokio::time::sleep(delay).await;
57 } else {
58 return Err(e);
59 }
60 }
61 Err(e) => return Err(e),
62 }
63 }
64}
65
66fn should_retry(err: &LangfuseError) -> bool {
68 match err {
69 LangfuseError::Network(_) => true,
70 LangfuseError::Api { status, .. } => *status == 429 || *status >= 500,
71 _ => false,
72 }
73}