diff --git a/docs/ADVANCED.md b/docs/ADVANCED.md index 96997e9..2df6626 100644 --- a/docs/ADVANCED.md +++ b/docs/ADVANCED.md @@ -581,6 +581,35 @@ foreach (var s in summaries) --- +## SDK Integration Header + +When using the managed client, the `X-Weaviate-Client-Integration` header is automatically sent with every request so the Weaviate server can identify and track managed client traffic in metrics. + +### DI path + +`AddWeaviateContext` sets the header automatically. If you are building a higher-level framework on top of the managed client, append your own identity at the core DI layer: + +```csharp +// X-Weaviate-Client-Integration: weaviate-client-csharp-managed/1.x.x my-framework/2.3.0 +builder.Services.AddWeaviate(opts => + opts.AddIntegration("my-framework/2.3.0")); +builder.Services.AddWeaviateContext(); +``` + +### Non-DI path + +When constructing `WeaviateContext` directly (without DI), call `WithManagedIntegrationHeader()` on your `ClientConfiguration`: + +```csharp +var config = new ClientConfiguration("localhost") + .WithManagedIntegrationHeader(); + +var client = new WeaviateClient(config); +var context = new MyContext(client); +``` + +--- + ## Collection Lifecycle Hooks The `OnCollectionConfig` pattern allows intercepting collection creation. diff --git a/docs/DEPENDENCY_INJECTION.md b/docs/DEPENDENCY_INJECTION.md index 8565c86..9be0b08 100644 --- a/docs/DEPENDENCY_INJECTION.md +++ b/docs/DEPENDENCY_INJECTION.md @@ -78,6 +78,19 @@ builder.Services.AddWeaviateContext( ); ``` +### SDK Integrations (X-Weaviate-Client-Integration Header) + +When `AddWeaviateContext` is called, it automatically sets the `X-Weaviate-Client-Integration` header so the Weaviate server can identify traffic from the managed client. + +If you are building a higher-level SDK or framework on top of `Weaviate.Client.Managed`, append your own identity at the core DI layer via `AddWeaviate()`. Multiple tokens are space-separated in the header value: + +```csharp +// Results in: X-Weaviate-Client-Integration: weaviate-client-csharp-managed/1.x.x my-framework/2.3.0 +builder.Services.AddWeaviate(opts => + opts.AddIntegration("my-framework/2.3.0")); +builder.Services.AddWeaviateContext(); +``` + ### OnConfiguring Override Context instances can override `OnConfiguring()` to set defaults, which take precedence over DI configuration: diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..95e879e --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Example/packages.lock.json b/src/Example/packages.lock.json index 31bad48..485472f 100644 --- a/src/Example/packages.lock.json +++ b/src/Example/packages.lock.json @@ -362,8 +362,8 @@ }, "Weaviate.Client": { "type": "Transitive", - "resolved": "1.0.1", - "contentHash": "Ff7/q5DpshU532DDZJ5Zh86G1nxnlO6zwTqzo4x/UOBY1s3zLRfbNyArQ012gMHtxx/odpULgi2Gld7zfwVI5A==", + "resolved": "1.0.2", + "contentHash": "B/QmsqYDf/R2m3/GJtg+SuKStg/mQ6Dkv1C5KcROLyqpyt96gH3RWLUFGB6SKFxhJAAyBEp/+Hk+eSFOcoShSg==", "dependencies": { "Duende.IdentityModel": "7.1.0", "Google.Protobuf": "3.30.2", @@ -383,7 +383,7 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "[9.0.8, )", "Microsoft.Extensions.Hosting.Abstractions": "[9.0.8, )", "Microsoft.Extensions.Options": "[9.0.8, )", - "Weaviate.Client": "[1.0.1, )" + "Weaviate.Client": "[1.0.2, )" } } } diff --git a/src/Weaviate.Client.Managed.Tests/DependencyInjection/WeaviateContextDITests.cs b/src/Weaviate.Client.Managed.Tests/DependencyInjection/WeaviateContextDITests.cs index aba2c28..43cec5e 100644 --- a/src/Weaviate.Client.Managed.Tests/DependencyInjection/WeaviateContextDITests.cs +++ b/src/Weaviate.Client.Managed.Tests/DependencyInjection/WeaviateContextDITests.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Weaviate.Client.DependencyInjection; using Xunit; @@ -233,6 +234,24 @@ public void AddWeaviateContext_ExposesUnderlyingClient() Assert.Same(client, context.Client); } + [Fact] + public void AddWeaviateContext_SetsIntegrationHeader() + { + var services = new ServiceCollection(); + services.AddWeaviateLocal(eagerInitialization: false); + services.AddWeaviateContext(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.NotNull(options.Headers); + Assert.True(options.Headers.ContainsKey("X-Weaviate-Client-Integration")); + Assert.Matches( + @"^weaviate-client-csharp-managed/\d+", + options.Headers["X-Weaviate-Client-Integration"] + ); + } + #region Test Types [WeaviateCollection("Products")] diff --git a/src/Weaviate.Client.Managed.Tests/Extensions/WeaviateClientExtensionsTests.cs b/src/Weaviate.Client.Managed.Tests/Extensions/WeaviateClientExtensionsTests.cs new file mode 100644 index 0000000..0751061 --- /dev/null +++ b/src/Weaviate.Client.Managed.Tests/Extensions/WeaviateClientExtensionsTests.cs @@ -0,0 +1,49 @@ +using Weaviate.Client.Managed.Extensions; +using Xunit; + +namespace Weaviate.Client.Managed.Tests.Extensions; + +public class WeaviateClientExtensionsTests +{ + [Fact] + public void WithManagedIntegrationHeader_AddsHeader() + { + var config = new ClientConfiguration(); + var result = config.WithManagedIntegrationHeader(); + + Assert.NotNull(result.Headers); + Assert.True(result.Headers.ContainsKey(WeaviateDefaults.IntegrationHeader)); + Assert.Matches( + @"^weaviate-client-csharp-managed/\d+", + result.Headers[WeaviateDefaults.IntegrationHeader] + ); + } + + [Fact] + public void WithManagedIntegrationHeader_DoesNotOverwriteExistingValue() + { + var config = new ClientConfiguration( + Headers: new Dictionary + { + [WeaviateDefaults.IntegrationHeader] = "existing/1.0", + } + ); + var result = config.WithManagedIntegrationHeader(); + + var value = result.Headers![WeaviateDefaults.IntegrationHeader]; + // Existing value is preserved; managed segment is appended + Assert.Contains("existing/1.0", value); + Assert.Contains("weaviate-client-csharp-managed/", value); + } + + [Fact] + public void WithManagedIntegrationHeader_DoesNotMutateOriginal() + { + var config = new ClientConfiguration(); + var result = config.WithManagedIntegrationHeader(); + + // Original is unchanged (record with syntax returns new instance) + Assert.Null(config.Headers); + Assert.NotNull(result.Headers); + } +} diff --git a/src/Weaviate.Client.Managed.Tests/Weaviate.Client.Managed.Tests.csproj b/src/Weaviate.Client.Managed.Tests/Weaviate.Client.Managed.Tests.csproj index 12436b3..aefe82e 100644 --- a/src/Weaviate.Client.Managed.Tests/Weaviate.Client.Managed.Tests.csproj +++ b/src/Weaviate.Client.Managed.Tests/Weaviate.Client.Managed.Tests.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/Weaviate.Client.Managed/DependencyInjection/WeaviateManagedServiceCollectionExtensions.cs b/src/Weaviate.Client.Managed/DependencyInjection/WeaviateManagedServiceCollectionExtensions.cs index 799a68c..b26d1a4 100644 --- a/src/Weaviate.Client.Managed/DependencyInjection/WeaviateManagedServiceCollectionExtensions.cs +++ b/src/Weaviate.Client.Managed/DependencyInjection/WeaviateManagedServiceCollectionExtensions.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -using Weaviate.Client.Managed.Context; +using Weaviate.Client.DependencyInjection; namespace Weaviate.Client.Managed.DependencyInjection; @@ -12,6 +12,11 @@ namespace Weaviate.Client.Managed.DependencyInjection; /// public static class WeaviateManagedServiceCollectionExtensions { + /// + /// The integration package name for the managed client. + /// + internal const string IntegrationName = "weaviate-client-csharp-managed"; + /// /// Registers a subclass with the dependency injection container. /// @@ -32,6 +37,14 @@ public static IServiceCollection AddWeaviateContext( ) where TContext : WeaviateContext { + // Append managed client identity to X-Weaviate-Client-Integration via IConfigureOptions + // so it is applied before WeaviateClient constructs its HttpClient/gRPC client. + services + .AddOptions() + .Configure(opts => + opts.AddIntegration(WeaviateDefaults.IntegrationAgent(IntegrationName)) + ); + // Configure typed options for this context type if (configureOptions != null) { diff --git a/src/Weaviate.Client.Managed/Extensions/WeaviateClientExtensions.cs b/src/Weaviate.Client.Managed/Extensions/WeaviateClientExtensions.cs index 8954f5f..1926eaf 100644 --- a/src/Weaviate.Client.Managed/Extensions/WeaviateClientExtensions.cs +++ b/src/Weaviate.Client.Managed/Extensions/WeaviateClientExtensions.cs @@ -8,6 +8,21 @@ namespace Weaviate.Client.Managed.Extensions; /// public static class WeaviateClientExtensions { + /// + /// Returns a new with the + /// X-Weaviate-Client-Integration header set for the managed client. + /// Use this when constructing without dependency injection. + /// + /// The base configuration. + public static ClientConfiguration WithManagedIntegrationHeader( + this ClientConfiguration config + ) => + config.WithIntegration( + WeaviateDefaults.IntegrationAgent( + DependencyInjection.WeaviateManagedServiceCollectionExtensions.IntegrationName + ) + ); + /// /// Creates a Weaviate collection from a C# class decorated with ORM attributes. /// The class must have a [WeaviateCollection] attribute or the class name will be used as the collection name. diff --git a/src/Weaviate.Client.Managed/PublicAPI.Unshipped.txt b/src/Weaviate.Client.Managed/PublicAPI.Unshipped.txt index 1960d26..234a9d1 100644 --- a/src/Weaviate.Client.Managed/PublicAPI.Unshipped.txt +++ b/src/Weaviate.Client.Managed/PublicAPI.Unshipped.txt @@ -162,3 +162,4 @@ static Weaviate.Client.Managed.Query.WeaviateQueryableExtensions.WithMetadata static Weaviate.Client.Managed.Query.WeaviateQueryableExtensions.WithReferences(this System.Linq.IQueryable! source, params System.Linq.Expressions.Expression!>![]! references) -> Weaviate.Client.Managed.Query.WeaviateQueryable! static Weaviate.Client.Managed.Query.WeaviateQueryableExtensions.WithCancellation(this System.Linq.IQueryable! source, System.Threading.CancellationToken cancellationToken) -> Weaviate.Client.Managed.Query.WeaviateQueryable! static Weaviate.Client.Managed.Query.WeaviateQueryableExtensions.WithVectors(this System.Linq.IQueryable! source, params System.Linq.Expressions.Expression!>![]! vectors) -> Weaviate.Client.Managed.Query.WeaviateQueryable! +static Weaviate.Client.Managed.Extensions.WeaviateClientExtensions.WithManagedIntegrationHeader(this Weaviate.Client.ClientConfiguration! config) -> Weaviate.Client.ClientConfiguration! diff --git a/src/Weaviate.Client.Managed/Weaviate.Client.Managed.csproj b/src/Weaviate.Client.Managed/Weaviate.Client.Managed.csproj index f4add46..1c6246e 100644 --- a/src/Weaviate.Client.Managed/Weaviate.Client.Managed.csproj +++ b/src/Weaviate.Client.Managed/Weaviate.Client.Managed.csproj @@ -76,7 +76,7 @@ - +