From 9b983441d71cadd9f85ebdcad7e5dd9bfde72443 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 13 Aug 2026 18:24:10 +0000 Subject: [PATCH] fix(chromadb): honor cloud tenant and database Closes #4486 --- .../src/components/layout/ContentArea.vue | 3 +- .../src/components/vector/VectorBrowser.vue | 12 +- .../vector/__tests__/VectorBrowser.spec.ts | 28 ++- apps/desktop/src/stores/connectionStore.ts | 2 +- crates/dbx-core/src/agent_tools.rs | 44 +++-- crates/dbx-core/src/db/vector_driver.rs | 184 ++++++++++++++---- src-tauri/src/commands/connection.rs | 6 +- 7 files changed, 217 insertions(+), 62 deletions(-) diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 8d4396e336..3a331f26d1 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -292,6 +292,7 @@ const activeResultConnectionId = computed(() => activeResultExecutionTarget.valu const activeResultDatabase = computed(() => activeResultExecutionTarget.value?.database ?? props.activeTab.database); const activeResultSchema = computed(() => activeResultExecutionTarget.value?.schema ?? props.activeTab.schema); const activeEffectiveDatabaseType = computed(() => effectiveDatabaseTypeForConnection(activeResultConnection.value)); +const activeVectorConnection = computed(() => connectionStore.getConfig(props.activeTab.connectionId) ?? props.activeConnection); const activeDataTabExecutionDatabase = computed(() => dataTabExecutionDatabase(props.activeConnection, props.activeTab.database, activeDataTabTableMeta.value?.catalog)); const activeProductionContext = computed(() => productionContextForDatabase(props.activeConnection, props.activeTab.database)); const productionWatermarkText = computed(() => (locale.value.startsWith("zh") ? "生产环境" : "PROD")); @@ -2071,7 +2072,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand diff --git a/apps/desktop/src/components/vector/VectorBrowser.vue b/apps/desktop/src/components/vector/VectorBrowser.vue index aadd2286aa..e29ed8df9a 100644 --- a/apps/desktop/src/components/vector/VectorBrowser.vue +++ b/apps/desktop/src/components/vector/VectorBrowser.vue @@ -21,6 +21,7 @@ const props = defineProps<{ collectionLabel?: string; databaseType?: DatabaseType; dimension?: number; + tenant?: string; }>(); const loading = ref(false); @@ -248,16 +249,19 @@ function defaultRequestText(databaseType: DatabaseType | undefined, database: st } if (databaseType === "chromadb") { const collectionId = collection || "collection-id"; + const tenant = encodeURIComponent(props.tenant?.trim() || "default_tenant"); + const chromaDatabase = encodeURIComponent(database.trim() || "default_database"); + const collectionPath = `/api/v2/tenants/${tenant}/databases/${chromaDatabase}/collections/${encodeURIComponent(collectionId)}`; if (mode === "delete") { - return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/delete\n${JSON.stringify({ ids: ["id1"] }, null, 2)}`; + return `POST ${collectionPath}/delete\n${JSON.stringify({ ids: ["id1"] }, null, 2)}`; } if (mode === "upsert") { - return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/upsert\n${JSON.stringify({ ids: ["id1"], embeddings: [sampleVector()], documents: ["sample document"], metadatas: [{}] }, null, 2)}`; + return `POST ${collectionPath}/upsert\n${JSON.stringify({ ids: ["id1"], embeddings: [sampleVector()], documents: ["sample document"], metadatas: [{}] }, null, 2)}`; } if (mode === "search") { - return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/query\n${JSON.stringify({ query_embeddings: [tryParseVector(searchVector.value)], n_results: searchTopK.value, include: ["documents", "metadatas", "distances"] }, null, 2)}`; + return `POST ${collectionPath}/query\n${JSON.stringify({ query_embeddings: [tryParseVector(searchVector.value)], n_results: searchTopK.value, include: ["documents", "metadatas", "distances"] }, null, 2)}`; } - return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/get\n${JSON.stringify({ limit: 100, include: ["documents", "metadatas"] }, null, 2)}`; + return `POST ${collectionPath}/get\n${JSON.stringify({ limit: 100, include: ["documents", "metadatas"] }, null, 2)}`; } const collectionPath = pathSegment(collection); if (mode === "delete") { diff --git a/apps/desktop/src/components/vector/__tests__/VectorBrowser.spec.ts b/apps/desktop/src/components/vector/__tests__/VectorBrowser.spec.ts index ed207c3ab7..ade0fd3b4b 100644 --- a/apps/desktop/src/components/vector/__tests__/VectorBrowser.spec.ts +++ b/apps/desktop/src/components/vector/__tests__/VectorBrowser.spec.ts @@ -100,7 +100,8 @@ describe("VectorBrowser default requests", () => { ["weaviate", "GET /v1/objects?class=demo&limit=100"], ["chromadb", "POST /api/v2/tenants/default_tenant/databases/default_database/collections/demo/get"], ] as const) { - app = createApp(VectorBrowser, { connectionId: "vector-1", database: "default", collection: "demo", databaseType }); + const database = databaseType === "chromadb" ? "default_database" : "default"; + app = createApp(VectorBrowser, { connectionId: "vector-1", database, collection: "demo", databaseType }); app.mount(root!); await flushUi(); expect(root!.querySelector("textarea")!.value).toContain(expected); @@ -111,6 +112,31 @@ describe("VectorBrowser default requests", () => { expect(backend.vectorGetCollectionDetail).not.toHaveBeenCalled(); }); + it("uses the configured Chroma Cloud namespace in generated requests", async () => { + app = createApp(VectorBrowser, { + connectionId: "chroma-cloud-1", + database: "support/kb", + collection: "collection/id", + databaseType: "chromadb", + tenant: "tenant /eu", + }); + app.mount(root!); + await flushUi(); + + const prefix = "/api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections/collection%2Fid"; + expect(root!.querySelector("textarea")!.value).toContain(`POST ${prefix}/get`); + + for (const [button, operation] of [ + ["vector.search", "query"], + ["vector.upsert", "upsert"], + ["vector.delete", "delete"], + ] as const) { + buttonWithText(button).click(); + await nextTick(); + expect(root!.querySelector("textarea")!.value).toContain(`POST ${prefix}/${operation}`); + } + }); + it("resolves the schema before executing a generated upsert request", async () => { let resolveDetail: (value: CollectionInfo) => void; backend.vectorGetCollectionDetail.mockReturnValue( diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index e06e06e411..2ba3eddb15 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -4161,7 +4161,7 @@ export const useConnectionStore = defineStore("connection", () => { async function loadVectorCollections(connectionId: string, database?: string) { const config = getConfig(connectionId); const isMilvus = config?.db_type === "milvus"; - const effectiveDb = database || config?.database || "default"; + const effectiveDb = database || config?.database || (config?.db_type === "chromadb" ? "default_database" : "default"); // Milvus groups collections under a per-database node; other vector stores stay flat under the connection. const node = isMilvus && database ? findNode(treeNodes.value, `${connectionId}:${database}`) : findConnectionNode(connectionId); if (!node) return; diff --git a/crates/dbx-core/src/agent_tools.rs b/crates/dbx-core/src/agent_tools.rs index 04318362f9..94d0d4e71d 100644 --- a/crates/dbx-core/src/agent_tools.rs +++ b/crates/dbx-core/src/agent_tools.rs @@ -1109,7 +1109,12 @@ async fn execute_browse_collection( collection.to_string() }; - let query = build_browse_query(db_type, &collection_id, database, limit)?; + let tenant = if *db_type == DatabaseType::ChromaDb { + state.configs.read().await.get(connection_id).map(|config| config.username.clone()).unwrap_or_default() + } else { + String::new() + }; + let query = build_browse_query(db_type, &collection_id, database, &tenant, limit)?; let options = QueryExecutionOptions { max_rows: Some(limit), timeout_secs: Some(30), ..Default::default() }; let result = @@ -1125,6 +1130,7 @@ fn build_browse_query( db_type: &DatabaseType, collection: &str, database: &str, + tenant: &str, limit: usize, ) -> Result { let collection = collection.trim(); @@ -1151,11 +1157,9 @@ fn build_browse_query( DatabaseType::Weaviate => { Ok(format!("GET /v1/objects?class={}&limit={}", vector_driver::query_value(collection), limit)) } - // TODO: ChromaDB Cloud 支持自定义租户和数据库,当前只实现了本地部署 - // (固定 default_tenant / default_database),后续支持云服务时需改为可配置。 DatabaseType::ChromaDb => Ok(format!( - "POST /api/v2/tenants/default_tenant/databases/default_database/collections/{}/get\n{}", - collection, + "POST {}/get\n{}", + vector_driver::chroma_collection_path(tenant, database, collection, None), serde_json::json!({ "limit": limit, "include": ["documents", "metadatas"] }) )), _ => Err(format!("Unsupported database type: {:?}", db_type)), @@ -1721,7 +1725,7 @@ for line in sys.stdin: #[test] fn build_browse_query_qdrant() { - let q = build_browse_query(&DatabaseType::Qdrant, "articles", "", 10).unwrap(); + let q = build_browse_query(&DatabaseType::Qdrant, "articles", "", "", 10).unwrap(); assert!(q.starts_with("POST /collections/articles/points/scroll")); assert!(q.contains("\"limit\":10")); assert!(q.contains("\"with_payload\":true")); @@ -1729,13 +1733,13 @@ for line in sys.stdin: #[test] fn build_browse_query_qdrant_encodes_url_chars() { - let q = build_browse_query(&DatabaseType::Qdrant, "my collection", "", 10).unwrap(); + let q = build_browse_query(&DatabaseType::Qdrant, "my collection", "", "", 10).unwrap(); assert!(q.starts_with("POST /collections/my%20collection/points/scroll")); } #[test] fn build_browse_query_milvus() { - let q = build_browse_query(&DatabaseType::Milvus, "articles", "custom_db", 20).unwrap(); + let q = build_browse_query(&DatabaseType::Milvus, "articles", "custom_db", "", 20).unwrap(); assert!(q.starts_with("POST /v2/vectordb/entities/query")); assert!(q.contains("\"dbName\":\"custom_db\"")); assert!(q.contains("\"collectionName\":\"articles\"")); @@ -1745,40 +1749,46 @@ for line in sys.stdin: #[test] fn build_browse_query_milvus_default_db() { - let q = build_browse_query(&DatabaseType::Milvus, "articles", "", 10).unwrap(); + let q = build_browse_query(&DatabaseType::Milvus, "articles", "", "", 10).unwrap(); assert!(q.contains("\"dbName\":\"default\"")); } #[test] fn build_browse_query_weaviate() { - let q = build_browse_query(&DatabaseType::Weaviate, "Articles", "", 5).unwrap(); + let q = build_browse_query(&DatabaseType::Weaviate, "Articles", "", "", 5).unwrap(); assert_eq!(q, "GET /v1/objects?class=Articles&limit=5"); } #[test] fn build_browse_query_weaviate_encodes_query_param() { - let q = build_browse_query(&DatabaseType::Weaviate, "A&B", "", 5).unwrap(); + let q = build_browse_query(&DatabaseType::Weaviate, "A&B", "", "", 5).unwrap(); assert!(q.contains("class=A%26B")); } #[test] fn build_browse_query_chromadb() { - let q = build_browse_query(&DatabaseType::ChromaDb, "uuid-123", "", 15).unwrap(); - assert!( - q.starts_with("POST /api/v2/tenants/default_tenant/databases/default_database/collections/uuid-123/get") - ); + let q = build_browse_query(&DatabaseType::ChromaDb, "uuid-123", "cloud/db", "tenant /eu", 15).unwrap(); + assert!(q.starts_with("POST /api/v2/tenants/tenant%20%2Feu/databases/cloud%2Fdb/collections/uuid-123/get")); assert!(q.contains("\"limit\":15")); } + #[test] + fn build_browse_query_chromadb_keeps_local_defaults() { + let q = build_browse_query(&DatabaseType::ChromaDb, "uuid-123", "", "", 15).unwrap(); + assert!( + q.starts_with("POST /api/v2/tenants/default_tenant/databases/default_database/collections/uuid-123/get") + ); + } + #[test] fn build_browse_query_rejects_empty_collection() { - let result = build_browse_query(&DatabaseType::Qdrant, " ", "", 10); + let result = build_browse_query(&DatabaseType::Qdrant, " ", "", "", 10); assert!(result.is_err()); } #[test] fn build_browse_query_rejects_unsupported_type() { - let result = build_browse_query(&DatabaseType::Postgres, "articles", "", 10); + let result = build_browse_query(&DatabaseType::Postgres, "articles", "", "", 10); assert!(result.is_err()); } diff --git a/crates/dbx-core/src/db/vector_driver.rs b/crates/dbx-core/src/db/vector_driver.rs index 9d1c3edff9..61b5804d75 100644 --- a/crates/dbx-core/src/db/vector_driver.rs +++ b/crates/dbx-core/src/db/vector_driver.rs @@ -89,6 +89,7 @@ pub struct VectorClient { http: HttpClient, base_url: String, auth: Option, + tenant: Option, database: Option, } @@ -111,9 +112,14 @@ impl VectorClient { ) -> Self { let base_url = url.trim_end_matches('/').to_string(); let auth = vector_auth(kind, username, password); + let tenant = if kind == VectorDbKind::ChromaDb { + username.map(str::trim).filter(|tenant| !tenant.is_empty()).map(str::to_string) + } else { + None + }; let builder = http_client_builder(timeout).danger_accept_invalid_certs(accept_invalid_certs); let http = builder.build().unwrap_or_else(|_| HttpClient::new()); - Self { kind, http, base_url, auth, database: None } + Self { kind, http, base_url, auth, tenant, database: None } } pub fn with_database(mut self, database: Option<&str>) -> Self { @@ -125,6 +131,22 @@ impl VectorClient { self.database.as_deref().unwrap_or("default") } + fn chroma_tenant(&self) -> &str { + self.tenant.as_deref().unwrap_or("default_tenant") + } + + fn chroma_database(&self) -> &str { + self.database.as_deref().unwrap_or("default_database") + } + + fn chroma_collections_path(&self) -> String { + chroma_collections_path(self.chroma_tenant(), self.chroma_database()) + } + + fn chroma_collection_path(&self, collection: &str, operation: Option<&str>) -> String { + chroma_collection_path(self.chroma_tenant(), self.chroma_database(), collection, operation) + } + fn get(&self, path: &str) -> reqwest::RequestBuilder { self.with_auth(self.http.get(format!("{}{}", self.base_url, path))) } @@ -153,17 +175,13 @@ impl VectorClient { } fn test_connection_request(client: &VectorClient) -> reqwest::RequestBuilder { - let path = match client.kind { - VectorDbKind::Qdrant => "/collections", - VectorDbKind::Milvus => "/v2/vectordb/collections/list", - VectorDbKind::Weaviate => "/v1/meta", - VectorDbKind::ChromaDb => "/api/v2/heartbeat", - }; match client.kind { - VectorDbKind::Qdrant => client.get(path), - VectorDbKind::Milvus => client.post(path).json(&serde_json::json!({ "dbName": client.database_or_default() })), - VectorDbKind::Weaviate => client.get(path), - VectorDbKind::ChromaDb => client.get(path), + VectorDbKind::Qdrant => client.get("/collections"), + VectorDbKind::Milvus => client + .post("/v2/vectordb/collections/list") + .json(&serde_json::json!({ "dbName": client.database_or_default() })), + VectorDbKind::Weaviate => client.get("/v1/meta"), + VectorDbKind::ChromaDb => client.get(&client.chroma_collections_path()), } } @@ -310,9 +328,7 @@ async fn list_weaviate_collections(client: &VectorClient) -> Result Result, String> { - let body = - send_json(client.get("/api/v2/tenants/default_tenant/databases/default_database/collections"), client.kind) - .await?; + let body = send_json(client.get(&client.chroma_collections_path()), client.kind).await?; let mut infos: Vec = body .as_array() .into_iter() @@ -473,14 +489,7 @@ async fn get_milvus_collection_detail( } async fn get_chroma_collection_detail(client: &VectorClient, collection: &str) -> Result { - let body = send_json( - client.get(&format!( - "/api/v2/tenants/default_tenant/databases/default_database/collections/{}", - path_segment(collection) - )), - client.kind, - ) - .await?; + let body = send_json(client.get(&client.chroma_collection_path(collection, None)), client.kind).await?; let name = body.get("name").and_then(Value::as_str).unwrap_or(collection); let id = body.get("id").and_then(Value::as_str).unwrap_or(collection); let dimension = body.get("dimension").and_then(|v| v.as_u64()).map(|d| d as u32); @@ -577,13 +586,8 @@ pub async fn find_documents( ) -> Result { if client.kind == VectorDbKind::ChromaDb { let start = std::time::Instant::now(); - let url = format!( - "{}/api/v2/tenants/default_tenant/databases/default_database/collections/{}/get", - client.base_url, - path_segment(collection), - ); let resp = client - .with_auth(client.http.post(&url)) + .post(&client.chroma_collection_path(collection, Some("get"))) .json(&serde_json::json!({ "limit": limit.max(1) as u64, "offset": skip, @@ -753,10 +757,7 @@ fn default_collection_query(client: &VectorClient, collection: &str) -> Result Ok(client.get(&format!("/v1/objects?class={}&limit=100", query_value(collection)))), VectorDbKind::ChromaDb => Ok(client - .post(&format!( - "/api/v2/tenants/default_tenant/databases/default_database/collections/{}/get", - path_segment(collection) - )) + .post(&client.chroma_collection_path(collection, Some("get"))) .json(&serde_json::json!({"limit": 100, "include": ["documents", "metadatas"]}))), } } @@ -769,6 +770,30 @@ pub(crate) fn path_segment(value: &str) -> String { utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string() } +pub(crate) fn chroma_collections_path(tenant: &str, database: &str) -> String { + let tenant = tenant.trim(); + let database = database.trim(); + format!( + "/api/v2/tenants/{}/databases/{}/collections", + path_segment(if tenant.is_empty() { "default_tenant" } else { tenant }), + path_segment(if database.is_empty() { "default_database" } else { database }) + ) +} + +pub(crate) fn chroma_collection_path( + tenant: &str, + database: &str, + collection: &str, + operation: Option<&str>, +) -> String { + let mut path = format!("{}/{}", chroma_collections_path(tenant, database), path_segment(collection)); + if let Some(operation) = operation { + path.push('/'); + path.push_str(&path_segment(operation)); + } + path +} + pub(crate) fn query_value(value: &str) -> String { utf8_percent_encode(value, QUERY_VALUE_ENCODE_SET).to_string() } @@ -909,10 +934,10 @@ fn format_reqwest_error(err: &reqwest::Error) -> String { #[cfg(test)] mod tests { use super::{ - chroma_get_response_to_rows, milvus_collection_schema, milvus_database_names, rest_query_result, - starts_with_http_method, test_connection, test_connection_request, values_to_query_result, vector_auth, - weaviate_collection_names_from_schema, weaviate_vector_dimension_from_graphql, CollectionInfo, VectorAuth, - VectorClient, VectorDbKind, + chroma_get_response_to_rows, default_collection_query, milvus_collection_schema, milvus_database_names, + rest_query_result, starts_with_http_method, test_connection, test_connection_request, values_to_query_result, + vector_auth, weaviate_collection_names_from_schema, weaviate_vector_dimension_from_graphql, CollectionInfo, + VectorAuth, VectorClient, VectorDbKind, }; use serde_json::{json, Value}; use std::time::{Duration, Instant}; @@ -939,6 +964,28 @@ mod tests { (format!("http://{address}"), server) } + async fn spawn_recording_json_response_server( + body: Value, + ) -> (String, tokio::sync::oneshot::Receiver, tokio::task::JoinHandle<()>) { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let body = body.to_string(); + let (request_tx, request_rx) = tokio::sync::oneshot::channel(); + let server = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await.unwrap(); + let mut request = [0_u8; 8192]; + let read = stream.read(&mut request).await.unwrap(); + request_tx.send(String::from_utf8_lossy(&request[..read]).into_owned()).unwrap(); + let response = format!( + "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}", + body.len(), + body + ); + stream.write_all(response.as_bytes()).await.unwrap(); + }); + (format!("http://{address}"), request_rx, server) + } + #[test] fn detects_rest_queries_case_insensitively() { assert!(starts_with_http_method("post /collections/foo")); @@ -1177,6 +1224,71 @@ mod tests { assert_eq!(vector_auth(VectorDbKind::ChromaDb, None, None), None); } + #[tokio::test] + async fn chroma_cloud_connection_test_uses_configured_namespace_and_token() { + let (url, request_rx, server) = spawn_recording_json_response_server(json!([])).await; + let client = VectorClient::new( + VectorDbKind::ChromaDb, + &url, + Some("tenant /eu"), + Some("cloud-api-key"), + false, + Duration::from_secs(1), + ) + .with_database(Some("support/kb")); + + test_connection(&client, Duration::from_secs(1)).await.unwrap(); + let request = request_rx.await.unwrap(); + + assert!( + request.starts_with("GET /api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections HTTP/1.1\r\n") + ); + assert!(request.to_ascii_lowercase().contains("\r\nx-chroma-token: cloud-api-key\r\n")); + server.await.unwrap(); + } + + #[test] + fn chroma_local_connection_test_keeps_default_namespace() { + let client = VectorClient::new( + VectorDbKind::ChromaDb, + "http://localhost:8000", + None, + None, + false, + Duration::from_secs(1), + ); + + let request = test_connection_request(&client).build().unwrap(); + + assert_eq!( + request.url().as_str(), + "http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections" + ); + assert!(request.headers().get("x-chroma-token").is_none()); + } + + #[test] + fn chroma_collection_paths_encode_every_dynamic_segment() { + let client = VectorClient::new( + VectorDbKind::ChromaDb, + "https://api.trychroma.com", + Some("tenant /eu"), + None, + false, + Duration::from_secs(1), + ) + .with_database(Some("support/kb")); + + assert_eq!( + client.chroma_collection_path("collection/id", Some("get/by-id")), + "/api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections/collection%2Fid/get%2Fby-id" + ); + assert_eq!( + default_collection_query(&client, "collection/id").unwrap().build().unwrap().url().path(), + "/api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections/collection%2Fid/get" + ); + } + #[test] fn parses_chroma_collection_list() { let body = json!([ diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index a54166385e..66ab8e81f9 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -1040,7 +1040,8 @@ async fn test_connection_with_info_inner( Some(&config.password), config.ssl, connect_timeout, - ); + ) + .with_database(config.database.as_deref()); db::vector_driver::test_connection(&client, connect_timeout) .await .map(|_| "Connection successful".to_string()) @@ -1420,7 +1421,8 @@ pub async fn connect_db( Some(&db_config.password), db_config.ssl, connect_timeout, - ); + ) + .with_database(db_config.database.as_deref()); db::vector_driver::test_connection(&client, connect_timeout).await?; PoolKind::VectorDb(client) }