diff --git a/dotnet/src/Connectors/Connectors.Memory.Pinecone/Http/SecureHttpHandler.cs b/dotnet/src/Connectors/Connectors.Memory.Pinecone/Http/SecureHttpHandler.cs
deleted file mode 100644
index a983e44637d0..000000000000
--- a/dotnet/src/Connectors/Connectors.Memory.Pinecone/Http/SecureHttpHandler.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Net.Http;
-
-namespace Microsoft.SemanticKernel.Connectors.Memory.Pinecone.Http;
-
-internal static class HttpHandlers
-{
- public static HttpClientHandler CheckCertificateRevocation { get; } = new()
- {
- CheckCertificateRevocationList = false,
- AllowAutoRedirect = true
- };
-}
diff --git a/dotnet/src/Connectors/Connectors.Memory.Pinecone/IPineconeClient.cs b/dotnet/src/Connectors/Connectors.Memory.Pinecone/IPineconeClient.cs
index a78f0f8c6e86..ea8e7eeca6fb 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Pinecone/IPineconeClient.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Pinecone/IPineconeClient.cs
@@ -21,7 +21,7 @@ public interface IPineconeClient
/// Whether to include the vector values
/// The cancellation token
/// A list of vector records
- public IAsyncEnumerable FetchVectorsAsync(
+ IAsyncEnumerable FetchVectorsAsync(
string indexName,
IEnumerable ids,
string indexNamespace = "",
@@ -38,7 +38,7 @@ public interface IPineconeClient
/// whether to include the metadata
///
/// a list of query matches
- public IAsyncEnumerable QueryAsync(
+ IAsyncEnumerable QueryAsync(
string indexName,
Query query,
bool includeValues = false,
@@ -57,7 +57,7 @@ public interface IPineconeClient
/// The name assigned to a collection of vectors.
/// A filter to apply to the results
/// Cancellation token.
- public IAsyncEnumerable<(PineconeDocument, double)> GetMostRelevantAsync(
+ IAsyncEnumerable<(PineconeDocument, double)> GetMostRelevantAsync(
string indexName,
IEnumerable vector,
double threshold,
@@ -160,7 +160,7 @@ Task UpdateAsync(
///
/// The name assigned to a collection of vectors.
/// Cancellation Token.
- public Task DoesIndexExistAsync(string indexName, CancellationToken cancellationToken = default);
+ Task DoesIndexExistAsync(string indexName, CancellationToken cancellationToken = default);
///
/// Describe index
diff --git a/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs
index c52a0f86fa0a..f5fdf2192d0c 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs
@@ -12,7 +12,6 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.SemanticKernel.Connectors.Memory.Pinecone.Http;
using Microsoft.SemanticKernel.Connectors.Memory.Pinecone.Http.ApiSchema;
using Microsoft.SemanticKernel.Connectors.Memory.Pinecone.Model;
@@ -21,21 +20,22 @@ namespace Microsoft.SemanticKernel.Connectors.Memory.Pinecone;
///
/// A client for the Pinecone API
///
-public sealed class PineconeClient : IPineconeClient, IDisposable
+public sealed class PineconeClient : IPineconeClient
{
///
/// Initializes a new instance of the class.
///
- ///
- ///
- ///
- public PineconeClient(string pineconeEnvironment, string apiKey, ILogger? logger = null)
+ /// The environment for Pinecone.
+ /// The API key for accessing Pinecone services.
+ /// An optional logger instance for logging.
+ /// An optional HttpClient instance for making HTTP requests.
+ public PineconeClient(string pineconeEnvironment, string apiKey, ILogger? logger = null, HttpClient? httpClient = null)
{
this._pineconeEnvironment = pineconeEnvironment;
this._authHeader = new KeyValuePair("Api-Key", apiKey);
this._jsonSerializerOptions = PineconeUtils.DefaultSerializerOptions;
this._logger = logger ?? NullLogger.Instance;
- this._httpClient = new HttpClient(HttpHandlers.CheckCertificateRevocation);
+ this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false);
this._indexHostMapping = new ConcurrentDictionary();
}
@@ -523,12 +523,6 @@ public async Task ConfigureIndexAsync(string indexName, int replicas = 1, PodTyp
this._logger.LogDebug("Collection created. {0}", indexName);
}
- ///
- public void Dispose()
- {
- this._httpClient.Dispose();
- }
-
#region private ================================================================================
private readonly string _pineconeEnvironment;
diff --git a/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeKernelBuilderExtensions.cs
new file mode 100644
index 000000000000..e43b84ef56b8
--- /dev/null
+++ b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeKernelBuilderExtensions.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Net.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel.Connectors.Memory.Pinecone;
+
+#pragma warning disable IDE0130
+namespace Microsoft.SemanticKernel;
+#pragma warning restore IDE0130
+
+///
+/// Provides extension methods for the class to configure Pinecone connectors.
+///
+public static class PineconeKernelBuilderExtensions
+{
+ ///
+ /// Registers Pinecone Memory Store.
+ ///
+ /// The instance
+ /// The environment for Pinecone.
+ /// The API key for accessing Pinecone services.
+ /// An optional HttpClient instance for making HTTP requests.
+ /// Self instance
+ public static KernelBuilder WithPineconeMemoryStore(this KernelBuilder builder,
+ string environment,
+ string apiKey,
+ HttpClient? httpClient = null)
+ {
+ builder.WithMemoryStorage((parameters) =>
+ {
+ var client = new PineconeClient(
+ environment,
+ apiKey,
+ parameters.Logger,
+ GetHttpClient(parameters.Config, httpClient, parameters.Logger));
+
+ return new PineconeMemoryStore(client, parameters.Logger);
+ });
+
+ return builder;
+ }
+
+ ///
+ /// Retrieves an instance of HttpClient.
+ ///
+ /// The kernel configuration.
+ /// An optional pre-existing instance of HttpClient.
+ /// An optional logger.
+ /// An instance of HttpClient.
+ private static HttpClient GetHttpClient(KernelConfig config, HttpClient? httpClient, ILogger? logger)
+ {
+ if (httpClient == null)
+ {
+ var retryHandler = config.HttpHandlerFactory.Create(logger);
+ retryHandler.InnerHandler = NonDisposableHttpClientHandler.Instance;
+ return new HttpClient(retryHandler, false); // We should refrain from disposing the underlying SK default HttpClient handler as it would impact other HTTP clients that utilize the same handler.
+ }
+
+ return httpClient;
+ }
+}
diff --git a/dotnet/src/Connectors/Connectors.UnitTests/Memory/Pinecone/PineconeKernelBuilderExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/Memory/Pinecone/PineconeKernelBuilderExtensionsTests.cs
new file mode 100644
index 000000000000..2fc1abd2d1e7
--- /dev/null
+++ b/dotnet/src/Connectors/Connectors.UnitTests/Memory/Pinecone/PineconeKernelBuilderExtensionsTests.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Mime;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.SemanticKernel;
+using Xunit;
+
+namespace SemanticKernel.Connectors.UnitTests.Memory.Pinecone;
+
+public sealed class PineconeKernelBuilderExtensionsTests : IDisposable
+{
+ private HttpMessageHandlerStub messageHandlerStub;
+ private HttpClient httpClient;
+
+ public PineconeKernelBuilderExtensionsTests()
+ {
+ this.messageHandlerStub = new HttpMessageHandlerStub();
+
+ this.httpClient = new HttpClient(this.messageHandlerStub, false);
+ }
+
+ [Fact]
+ public async Task PineconeMemoryStoreShouldBeProperlyInitialized()
+ {
+ //Arrange
+ this.messageHandlerStub.ResponseToReturn.Content = new StringContent("[\"fake-index1\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
+
+ var builder = new KernelBuilder();
+ builder.WithPineconeMemoryStore("fake-environment", "fake-api-key", this.httpClient);
+ builder.WithAzureTextEmbeddingGenerationService("fake-deployment-name", "https://fake-random-test-host/fake-path", "fake -api-key");
+ var kernel = builder.Build(); //This call triggers the internal factory registered by WithPineconeMemoryStore method to create an instance of the PineconeMemoryStore class.
+
+ //Act
+ await kernel.Memory.GetCollectionsAsync(); //This call triggers a subsequent call to Pinecone memory store.
+
+ //Assert
+ Assert.Equal("https://controller.fake-environment.pinecone.io/databases", this.messageHandlerStub?.RequestUri?.AbsoluteUri);
+
+ var headerValues = Enumerable.Empty();
+ var headerExists = this.messageHandlerStub?.RequestHeaders?.TryGetValues("Api-Key", out headerValues);
+ Assert.True(headerExists);
+ Assert.Contains(headerValues!, (value) => value == "fake-api-key");
+ }
+
+ public void Dispose()
+ {
+ this.httpClient.Dispose();
+ this.messageHandlerStub.Dispose();
+ }
+}
diff --git a/dotnet/src/SemanticKernel/KernelBuilder.cs b/dotnet/src/SemanticKernel/KernelBuilder.cs
index 8ef434707dce..929b3df4421f 100644
--- a/dotnet/src/SemanticKernel/KernelBuilder.cs
+++ b/dotnet/src/SemanticKernel/KernelBuilder.cs
@@ -23,7 +23,7 @@ public sealed class KernelBuilder
private KernelConfig _config = new();
private ISemanticTextMemory _memory = NullMemory.Instance;
private ILogger _logger = NullLogger.Instance;
- private IMemoryStore? _memoryStorage = null;
+ private Func? _memoryStorageFactory = null;
private IDelegatingHandlerFactory? _httpHandlerFactory = null;
private IPromptTemplateEngine? _promptTemplateEngine;
private readonly AIServiceCollection _aiServices = new();
@@ -61,9 +61,9 @@ public IKernel Build()
);
// TODO: decouple this from 'UseMemory' kernel extension
- if (this._memoryStorage != null)
+ if (this._memoryStorageFactory != null)
{
- instance.UseMemory(this._memoryStorage);
+ instance.UseMemory(this._memoryStorageFactory.Invoke());
}
return instance;
@@ -101,7 +101,19 @@ public KernelBuilder WithMemory(ISemanticTextMemory memory)
public KernelBuilder WithMemoryStorage(IMemoryStore storage)
{
Verify.NotNull(storage);
- this._memoryStorage = storage;
+ this._memoryStorageFactory = () => storage;
+ return this;
+ }
+
+ ///
+ /// Add memory storage factory to the kernel.
+ ///
+ /// The storage factory.
+ /// Updated kernel builder including the memory storage.
+ public KernelBuilder WithMemoryStorage(Func<(ILogger Logger, KernelConfig Config), TStore> factory) where TStore : IMemoryStore
+ {
+ Verify.NotNull(factory);
+ this._memoryStorageFactory = () => factory((this._logger, this._config));
return this;
}
diff --git a/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs b/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs
index 9c048fe8d676..7ee2c94ebbf0 100644
--- a/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs
+++ b/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs
@@ -32,6 +32,7 @@ public static async Task RunAsync()
.WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"))
.WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY"))
.WithMemoryStorage(memoryStore)
+ //.WithPineconeMemoryStore(pineconeEnvironment, apiKey) // This method offers an alternative approach to registering Pinecone memory storage.
.Build();
Console.WriteLine("== Printing Collections in DB ==");