Skip to content

Commit ff04fd0

Browse files
committed
feat: support ext. api keys in header
1 parent a36d2f3 commit ff04fd0

File tree

5 files changed

+140
-2
lines changed

5 files changed

+140
-2
lines changed

src/external_api_keys.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::collections::HashMap;
2+
3+
use tonic::metadata::{MetadataKey, MetadataValue};
4+
use tonic::service::Interceptor;
5+
use tonic::{Request, Status};
6+
7+
pub struct ExternalApiKeysInterceptor {
8+
external_api_keys: Option<HashMap<String, String>>,
9+
}
10+
11+
impl ExternalApiKeysInterceptor {
12+
pub fn new(external_api_keys: Option<HashMap<String, String>>) -> Self {
13+
Self { external_api_keys }
14+
}
15+
}
16+
17+
impl Interceptor for ExternalApiKeysInterceptor {
18+
fn call(&mut self, mut request: Request<()>) -> anyhow::Result<Request<()>, Status> {
19+
if let Some(ext_api_keys) = &self.external_api_keys {
20+
for (k, v) in ext_api_keys {
21+
let key = MetadataKey::from_bytes(k.as_bytes())
22+
.map_err(|_| Status::invalid_argument(format!("Invalid metadata key: {k}")))?;
23+
let value = MetadataValue::try_from(v.as_str()).map_err(|_| {
24+
Status::invalid_argument(format!("Invalid metadata value for {k}: {v}"))
25+
})?;
26+
request.metadata_mut().insert(key, value);
27+
}
28+
}
29+
Ok(request)
30+
}
31+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ mod builder_types;
138138
mod builders;
139139
mod channel_pool;
140140
mod expressions;
141+
mod external_api_keys;
141142
mod filters;
142143
mod grpc_conversions;
143144
mod grpc_macros;

src/qdrant_client/config.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::time::Duration;
23

34
use crate::{Qdrant, QdrantError};
@@ -33,6 +34,9 @@ pub struct QdrantConfig {
3334
/// Optional API key or token to use for authorization
3435
pub api_key: Option<String>,
3536

37+
/// Optional API keys for external embedding providers (OpenAI, JINA, Cohere, OpenRouter)
38+
pub external_api_keys: Option<HashMap<String, String>>,
39+
3640
/// Optional compression schema to use for API requests
3741
pub compression: Option<CompressionEncoding>,
3842

@@ -89,6 +93,22 @@ impl QdrantConfig {
8993
self
9094
}
9195

96+
/// Set an optional map of external API keys for embedding providers (OpenAI, JINA, Cohere, OpenRouter)
97+
///
98+
/// # Examples
99+
/// ```rust,no_run
100+
///# use std::collections::HashMap;
101+
///# let config: HashMap<&str, String> = HashMap::new();
102+
///# use qdrant_client::Qdrant;
103+
/// let client = Qdrant::from_url("http://localhost:6334")
104+
/// .external_api_keys(config.get("external_api_keys"))
105+
/// .build();
106+
/// ```
107+
pub fn external_api_keys(mut self, external_api_keys: impl AsOptionExternalApiKeys) -> Self {
108+
self.external_api_keys = external_api_keys.external_api_keys();
109+
self
110+
}
111+
92112
/// Keep the connection alive while idle
93113
pub fn keep_alive_while_idle(mut self) -> Self {
94114
self.keep_alive_while_idle = true;
@@ -201,6 +221,7 @@ impl Default for QdrantConfig {
201221
connect_timeout: Duration::from_secs(5),
202222
keep_alive_while_idle: true,
203223
api_key: None,
224+
external_api_keys: None,
204225
compression: None,
205226
check_compatibility: true,
206227
pool_size: 3,
@@ -303,3 +324,69 @@ impl<E: Sized> AsOptionApiKey for Result<String, E> {
303324
self.ok()
304325
}
305326
}
327+
328+
/// Set an optional API key from various types
329+
///
330+
/// For example:
331+
///
332+
/// ```rust
333+
///# use std::time::Duration;
334+
///# use qdrant_client::Qdrant;
335+
///# let mut config = Qdrant::from_url("http://localhost:6334");
336+
/// config
337+
/// .external_api_keys(("openai-api-key", "<YOUR_OPENAI_API_KEY>"))
338+
/// .external_api_keys((String::from("openai-api-key"), String::from("<YOUR_OPENAI_API_KEY>")))
339+
/// .external_api_keys((String::from("openai-api-key").unwrap(), std::env::var("OPENAI_API_KEY").unwrap()));
340+
/// ```
341+
///
342+
/// /// ```rust
343+
///# use std::time::Duration;
344+
///# use qdrant_client::Qdrant;
345+
///# let mut config = Qdrant::from_url("http://localhost:6334");
346+
///# let ext_api_keys = HashMap::from([("openai-api-key", "<YOUR_OPENAI_API_KEY>"), ("cohere-api-key", "<YOUR_COHERE_API_KEY>")])
347+
/// config
348+
/// .external_api_keys(ext_api_keys);
349+
/// ```
350+
pub trait AsOptionExternalApiKeys {
351+
fn external_api_keys(self) -> Option<HashMap<String, String>>;
352+
}
353+
354+
impl<K, V> AsOptionExternalApiKeys for (K, V)
355+
where
356+
K: Into<String>,
357+
V: Into<String>,
358+
{
359+
fn external_api_keys(self) -> Option<HashMap<String, String>> {
360+
let (k, v) = self;
361+
Some(HashMap::from([(k.into(), v.into())]))
362+
}
363+
}
364+
365+
impl<K, V> AsOptionExternalApiKeys for Option<(K, V)>
366+
where
367+
K: Into<String>,
368+
V: Into<String>,
369+
{
370+
fn external_api_keys(self) -> Option<HashMap<String, String>> {
371+
let (k, v) = self?;
372+
Some(HashMap::from([(k.into(), v.into())]))
373+
}
374+
}
375+
376+
impl AsOptionExternalApiKeys for Option<HashMap<String, String>> {
377+
fn external_api_keys(self) -> Option<HashMap<String, String>> {
378+
self
379+
}
380+
}
381+
382+
impl AsOptionExternalApiKeys for HashMap<String, String> {
383+
fn external_api_keys(self) -> Option<HashMap<String, String>> {
384+
Some(self)
385+
}
386+
}
387+
388+
impl<E: Sized> AsOptionExternalApiKeys for Result<HashMap<String, String>, E> {
389+
fn external_api_keys(self) -> Option<HashMap<String, String>> {
390+
self.ok()
391+
}
392+
}

src/qdrant_client/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ use tonic::codegen::InterceptedService;
2020
use tonic::transport::{Channel, Uri};
2121
use tonic::Status;
2222

23-
use crate::auth::TokenInterceptor;
2423
use crate::channel_pool::ChannelPool;
2524
use crate::qdrant::{qdrant_client, HealthCheckReply, HealthCheckRequest};
2625
use crate::qdrant_client::config::QdrantConfig;
2726
use crate::qdrant_client::version_check::is_compatible;
27+
use crate::auth::TokenInterceptor;
28+
use crate::external_api_keys::ExternalApiKeysInterceptor;
2829
use crate::QdrantError;
2930

3031
/// [`Qdrant`] client result
@@ -184,6 +185,15 @@ impl Qdrant {
184185
InterceptedService::new(channel, interceptor)
185186
}
186187

188+
/// Wraps a service with external API keys interceptor
189+
fn with_external_api_keys<S>(
190+
&self,
191+
service: S,
192+
) -> InterceptedService<S, ExternalApiKeysInterceptor> {
193+
let interceptor = ExternalApiKeysInterceptor::new(self.config.external_api_keys.clone());
194+
InterceptedService::new(service, interceptor)
195+
}
196+
187197
// Access to raw root qdrant API
188198
async fn with_root_qdrant_client<T, O: Future<Output = Result<T, Status>>>(
189199
&self,

src/qdrant_client/points.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use tonic::transport::Channel;
55
use tonic::Status;
66

77
use crate::auth::TokenInterceptor;
8+
use crate::external_api_keys::ExternalApiKeysInterceptor;
89
use crate::qdrant::points_client::PointsClient;
910
use crate::qdrant::{
1011
CountPoints, CountResponse, DeletePointVectors, DeletePoints, FacetCounts, FacetResponse,
@@ -22,13 +23,21 @@ use crate::qdrant_client::{Qdrant, QdrantResult};
2223
impl Qdrant {
2324
pub(crate) async fn with_points_client<T, O: Future<Output = Result<T, Status>>>(
2425
&self,
25-
f: impl Fn(PointsClient<InterceptedService<Channel, TokenInterceptor>>) -> O,
26+
f: impl Fn(
27+
PointsClient<
28+
InterceptedService<
29+
InterceptedService<Channel, TokenInterceptor>,
30+
ExternalApiKeysInterceptor,
31+
>,
32+
>,
33+
) -> O,
2634
) -> QdrantResult<T> {
2735
let result = self
2836
.channel
2937
.with_channel(
3038
|channel| {
3139
let service = self.with_api_key(channel);
40+
let service = self.with_external_api_keys(service);
3241
let mut client =
3342
PointsClient::new(service).max_decoding_message_size(usize::MAX);
3443
if let Some(compression) = self.config.compression {

0 commit comments

Comments
 (0)