diff --git a/llmstack/common/blocks/data/store/vectorstore/weaviate.py b/llmstack/common/blocks/data/store/vectorstore/weaviate.py index 3f6860762db..e114db6d592 100644 --- a/llmstack/common/blocks/data/store/vectorstore/weaviate.py +++ b/llmstack/common/blocks/data/store/vectorstore/weaviate.py @@ -15,6 +15,7 @@ DocumentQuery, VectorStoreInterface, ) +from llmstack.processors.providers.weaviate import with_integration_header logger = logging.getLogger(__name__) @@ -136,7 +137,7 @@ def check_batch_result( ), ) - headers = configuration.additional_headers + headers = with_integration_header(configuration.additional_headers) if configuration.openai_key is not None: headers["X-OpenAI-Api-Key"] = configuration.openai_key if configuration.cohere_api_key is not None: diff --git a/llmstack/data/destinations/vector_stores/weaviate.py b/llmstack/data/destinations/vector_stores/weaviate.py index 47ddf00e384..171c7a47fb8 100644 --- a/llmstack/data/destinations/vector_stores/weaviate.py +++ b/llmstack/data/destinations/vector_stores/weaviate.py @@ -28,6 +28,7 @@ WeaviateCloudInstance, WeaviateLocalInstance, WeaviateProviderConfig, + with_integration_header, ) logger = logging.getLogger(__name__) @@ -124,6 +125,8 @@ def _create_weaviate_client( weaviate_config: Union[WeaviateLocalInstance, WeaviateCloudInstance], auth=None, additional_headers=None ): weaviate_client = None + # Passed at construction so the header reaches every REST request and the gRPC metadata. + additional_headers = with_integration_header(additional_headers) if isinstance(weaviate_config, WeaviateLocalInstance): weaviate_client = connect_to_custom( http_host=weaviate_config.http_host, diff --git a/llmstack/processors/providers/weaviate/__init__.py b/llmstack/processors/providers/weaviate/__init__.py index 9debc541630..29a96841f43 100644 --- a/llmstack/processors/providers/weaviate/__init__.py +++ b/llmstack/processors/providers/weaviate/__init__.py @@ -1,4 +1,5 @@ -from typing import Annotated, List, Optional, Union +import importlib.metadata +from typing import Annotated, Dict, List, Optional, Union from pydantic import BaseModel, Field @@ -93,3 +94,27 @@ class WeaviateProviderConfig(ProviderConfig): @property def additional_headers_dict(self): return {header.key: header.value for header in self.additional_headers} + + +# Weaviate (1.38+) reads this header as `{name}/{version}` to attribute requests to +# the integration that sent them. Older servers ignore it. +INTEGRATION_HEADER = "X-Weaviate-Client-Integration" + + +def _integration_header_value() -> str: + try: + version = importlib.metadata.version("llmstack").strip() or "unknown" + except Exception: + version = "unknown" + return f"llmstack/{version}" + + +def with_integration_header(headers: Optional[Dict[str, str]]) -> Dict[str, str]: + """Return a copy of `headers` with the X-Weaviate-Client-Integration header added. + + A value already supplied by the user (any letter case) is kept as is. + """ + headers = dict(headers or {}) + if not any(key.lower() == INTEGRATION_HEADER.lower() for key in headers): + headers[INTEGRATION_HEADER] = _integration_header_value() + return headers