Skip to main content

opendev_http/adapters/
groq.rs

1//! Groq adapter.
2//!
3//! Groq's API is OpenAI-compatible (Chat Completions format) with additional
4//! rate-limiting headers (`x-ratelimit-*`). This adapter:
5//! - Passes requests mostly unchanged
6//! - Extracts rate limit information from responses
7//! - Endpoint: `https://api.groq.com/openai/v1/chat/completions`
8
9use serde_json::{Value, json};
10
11const DEFAULT_API_URL: &str = "https://api.groq.com/openai/v1/chat/completions";
12
13/// Rate limit information extracted from Groq response headers.
14#[derive(Debug, Clone, Default)]
15pub struct RateLimitInfo {
16    /// Maximum requests allowed per window.
17    pub limit_requests: Option<u64>,
18    /// Maximum tokens allowed per window.
19    pub limit_tokens: Option<u64>,
20    /// Remaining requests in the current window.
21    pub remaining_requests: Option<u64>,
22    /// Remaining tokens in the current window.
23    pub remaining_tokens: Option<u64>,
24    /// Time until the request limit resets (e.g., "1s", "6m0s").
25    pub reset_requests: Option<String>,
26    /// Time until the token limit resets.
27    pub reset_tokens: Option<String>,
28}
29
30impl RateLimitInfo {
31    /// Parse rate limit info from HTTP response headers.
32    ///
33    /// Groq returns these headers:
34    /// - `x-ratelimit-limit-requests`
35    /// - `x-ratelimit-limit-tokens`
36    /// - `x-ratelimit-remaining-requests`
37    /// - `x-ratelimit-remaining-tokens`
38    /// - `x-ratelimit-reset-requests`
39    /// - `x-ratelimit-reset-tokens`
40    pub fn from_headers(headers: &[(String, String)]) -> Self {
41        let mut info = Self::default();
42        for (key, value) in headers {
43            match key.as_str() {
44                "x-ratelimit-limit-requests" => {
45                    info.limit_requests = value.parse().ok();
46                }
47                "x-ratelimit-limit-tokens" => {
48                    info.limit_tokens = value.parse().ok();
49                }
50                "x-ratelimit-remaining-requests" => {
51                    info.remaining_requests = value.parse().ok();
52                }
53                "x-ratelimit-remaining-tokens" => {
54                    info.remaining_tokens = value.parse().ok();
55                }
56                "x-ratelimit-reset-requests" => {
57                    info.reset_requests = Some(value.clone());
58                }
59                "x-ratelimit-reset-tokens" => {
60                    info.reset_tokens = Some(value.clone());
61                }
62                _ => {}
63            }
64        }
65        info
66    }
67
68    /// Convert rate limit info to a JSON value for logging/debugging.
69    pub fn to_json(&self) -> Value {
70        let mut obj = json!({});
71        if let Some(v) = self.limit_requests {
72            obj["limit_requests"] = json!(v);
73        }
74        if let Some(v) = self.limit_tokens {
75            obj["limit_tokens"] = json!(v);
76        }
77        if let Some(v) = self.remaining_requests {
78            obj["remaining_requests"] = json!(v);
79        }
80        if let Some(v) = self.remaining_tokens {
81            obj["remaining_tokens"] = json!(v);
82        }
83        if let Some(ref v) = self.reset_requests {
84            obj["reset_requests"] = json!(v);
85        }
86        if let Some(ref v) = self.reset_tokens {
87            obj["reset_tokens"] = json!(v);
88        }
89        obj
90    }
91}
92
93/// Adapter for the Groq Chat Completions API.
94///
95/// Groq is OpenAI-compatible so requests pass through with minimal changes.
96/// The main value-add is rate limit header extraction.
97#[derive(Debug, Clone)]
98pub struct GroqAdapter {
99    api_url: String,
100}
101
102impl GroqAdapter {
103    /// Create a new Groq adapter with the default API URL.
104    pub fn new() -> Self {
105        Self {
106            api_url: DEFAULT_API_URL.to_string(),
107        }
108    }
109
110    /// Create with a custom API URL.
111    pub fn with_url(url: impl Into<String>) -> Self {
112        Self {
113            api_url: url.into(),
114        }
115    }
116
117    /// Remove unsupported parameters from the request payload.
118    ///
119    /// Groq does not support some OpenAI-specific parameters.
120    fn clean_request(payload: &mut Value) {
121        if let Some(obj) = payload.as_object_mut() {
122            obj.remove("logprobs");
123            obj.remove("top_logprobs");
124            obj.remove("n");
125        }
126    }
127}
128
129impl Default for GroqAdapter {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135#[async_trait::async_trait]
136impl super::base::ProviderAdapter for GroqAdapter {
137    fn provider_name(&self) -> &str {
138        "groq"
139    }
140
141    fn convert_request(&self, mut payload: Value) -> Value {
142        Self::clean_request(&mut payload);
143        // Strip internal reasoning effort field
144        payload
145            .as_object_mut()
146            .map(|obj| obj.remove("_reasoning_effort"));
147        payload
148    }
149
150    fn convert_response(&self, response: Value) -> Value {
151        // Groq responses are already in Chat Completions format
152        response
153    }
154
155    fn api_url(&self) -> &str {
156        &self.api_url
157    }
158}
159
160#[cfg(test)]
161#[path = "groq_tests.rs"]
162mod tests;