ChatSessionManager 1.0.5

dotnet add package ChatSessionManager --version 1.0.5                
NuGet\Install-Package ChatSessionManager -Version 1.0.5                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ChatSessionManager" Version="1.0.5" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ChatSessionManager --version 1.0.5                
#r "nuget: ChatSessionManager, 1.0.5"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ChatSessionManager as a Cake Addin
#addin nuget:?package=ChatSessionManager&version=1.0.5

// Install ChatSessionManager as a Cake Tool
#tool nuget:?package=ChatSessionManager&version=1.0.5                

ChatSessionManager

Overview

ChatHistoryManager is a robust and scalable solution designed to store and query chat history data for AI applications. This repository contains the core components necessary to seamlessly integrate chat session storage into your AI-driven applications. It supports multiple implementations, including Azure AI Search and Cosmos DB.

Features

  • Efficient Data Storage: Store chat sessions in a structured format.
  • Easy Retrieval: Query and retrieve chat sessions quickly.
  • Scalability: Designed to handle large volumes of chat data.
  • Extensible: Easily integrate with various AI frameworks and tools.
  • Secure: Ensure data integrity and security.
  • Multiple Implementations: Leverage different backends like Azure AI Search and Cosmos DB to suit your needs.

Installation

  1. Clone the repository:

    git clone https://github.com/muatassim/ChatSessionManager.git
    
  2. Navigate to the project directory:

    cd ChatSessionManager
    
  3. Install dependencies:

    dotnet restore
    
  4. Add to Dependency Injection:

     //For Adding AzureAISearch and AzureAiSearch opitons value : Required
     services.AddAzureAISearchChatHistory(context.Configuration);
     //For Cosmos and CosmosSearch configuration is required
     services.AddCosmosChatHistory(context.Configuration);
    

Chat History Data Service

The IChatHistoryDataService interface provides various methods for managing and querying chat documents in a data source. This guide explains how to use each method with examples.

Configuration

Add the following settings to your appsettings.json file:

{
  "ChatSessionManagerOptions": {
    "AzureAiSearch": {  
      "ServiceName": "--Azure Ai Service Name--",  
      "ApiKey": "--API KEY--",
      "SemanticSearchConfigName": "my-semantic-config",
      "VectorSearchHNSWConfig": "my-hnsw-vector-config",
      "VectorSearchProfile": "my-vector-profile",
      "ModelDimension": "1536",
      "IndexName": "INDEX NAME"
    },
     "CosmosSearch": {
        "DatabaseId": "Database Id",
        "ContainerId": "Container ",
        "AccountEndpoint": "Cosmos End Point",
        "AccountKey": "Account Key"
     }
  }
}

Usage

To integrate IChatHistoryDataService in your project. Follwoing implementation can be used

  1. AzureAISearchChatHistoryDataService which utilizes Azure AI Search:
    • Constructor: [FromKeyedServices(nameof(AzureAISearchChatHistoryDataService))] IChatHistoryDataService dataService
  2. AzureCosmosSearchChatHistoryDataService which utilizes Cosmos DB on preview: for details refer: https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/vector-search
    • Constructor: [FromKeyedServices(nameof(CosmosChatHistoryDataService))] IChatHistoryDataService dataService

Semantic Kernel Chat Example

This section shows how to use the Semantic Kernel chat with history. The example demonstrates how to ask a series of questions where the context from previous answers is important.

[TestMethod]
[DataRow("I'm planning a trip to Paris. Can you tell me the best time of year to visit and some must-see attractions?",
    "Given that I'm interested in art and history, what are some lesser-known museums in Paris that I should visit?")] 
[DataRow("I want to adopt a healthier diet. Can you suggest some nutritious foods to incorporate into my meals?",
   "Based on the foods you suggested, can you give me a simple recipe for a balanced meal?")] 
[DataRow("I'm looking for a good mystery novel to read. Can you suggest one?", "Sounds interesting. What can you tell me about the main character in 'The Girl with the Dragon Tattoo'?")]
[DataRow("I want to start a workout routine to build muscle. Any tips on what exercises I should do?", "Can you suggest a weekly workout plan that includes those exercises?")]
public async Task ChatWithHistoryExampleAsync_Test(string question, string followUpQuestion)
{

    IOptions<AzureOpenAIOptions> options = AppHost.GetServiceProvider().GetRequiredService<IOptions<AzureOpenAIOptions>>();
    Assert.IsNotNull(options);
    //var openAI = new OpenAI(options.Value.Key, options.Value.Endpoint);
    AzureOpenAIOptions azureOpenAIOptions = options.Value as AzureOpenAIOptions;

    IChatHistoryDataService chatHistoryDataService = AppHost.GetServiceProvider().GetKeyedService<IChatHistoryDataService>(nameof(AzureAISearchChatHistoryDataService));
    Assert.IsNotNull(chatHistoryDataService);

    Kernel kernel = AppHost.GetServiceProvider().GetService<Kernel>();

    Assert.IsNotNull(kernel);

    IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
    Assert.IsNotNull(chatCompletionService); 
    ITextEmbeddingGenerationService textEmbeddingGenerationService = kernel.GetRequiredService<ITextEmbeddingGenerationService>();
    Assert.IsNotNull(textEmbeddingGenerationService);
    //Get Question 1 Vector 
    ReadOnlyMemory<float> questionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(question);
    Assert.IsNotNull(questionEmbedding);

    await AskQuestion(question, chatHistoryDataService, chatCompletionService,questionEmbedding);


    //Second Question Vector 
    ReadOnlyMemory<float> followUpQuestionEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(followUpQuestion);
    Assert.IsNotNull(followUpQuestionEmbedding);


    //Start next Question 
    await AskQuestion(followUpQuestion, chatHistoryDataService, chatCompletionService, followUpQuestionEmbedding);
    //Get History Records 


    //Send the Question Again 

}

private static async Task AskQuestion(string question, 
    IChatHistoryDataService chatHistoryDataService,
    IChatCompletionService chatCompletionService,
    ReadOnlyMemory<float> questionEmbedding )
{
    ///Question 1 Record 
    ChatHistory chatHistory = [];
    chatHistory.AddSystemMessage("You are an AI assistant who answers the users questions in a thoughtfull manner and are precise with your answer.");
    //Add history and usermessage 
    var historyContext = await chatHistoryDataService.GetChatHistoryContextAsync(question,questionEmbedding,2,userId,0.5);
    if (historyContext != null)
    {
        chatHistory.AddMessage(AuthorRole.Assistant, historyContext.ToString());
    }
    chatHistory.AddUserMessage(question);

    ChatMessageContent messageContent = await chatCompletionService.GetChatMessageContentAsync(question);
    //Save Question 1 and Response 
   // await SaveChat(question, chatHistoryDataService, questionEmbedding, chatHistory, messageContent);
}
private static async Task SaveChat(string question, IChatHistoryDataService chatHistoryDataService, ReadOnlyMemory<float> questionEmbedding, ChatHistory chatHistory, ChatMessageContent messageContent)
{
    ChatDocument chatDocument = new()
    {
        Id = Guid.NewGuid().ToString(),
        UserId = userId,
        Content = messageContent.Content,
        IpAddress = "127.0.0.1",
        SessionId = sessionId,
        Timestamp = DateTime.UtcNow,
        QuestionVector = questionEmbedding,
        Question = question,
        Role = AuthorRole.User.Label
    };
    chatHistory.Add(messageContent);
    //Save the conversation to the UserStore 
    (List<LogMessage> messages, bool success) response = await chatHistoryDataService.AddDocumentAsync(chatDocument);
    Assert.IsNotNull(response);
}


Interface Methods

AddDocumentAsync

Adds a document to the data source.

Method Signature:

Task<(List<LogMessage> messages, bool success)> AddDocumentAsync(ChatDocument chatDocument);

Example:

var chatDocument = new ChatDocument { UserId = "user123", Content = "Hello, world!" };
var (messages, success) = await chatHistoryDataService.AddDocumentAsync(chatDocument);
if (success) Console.WriteLine("Document added successfully");

CreateDataSourceIfNotExistAsync

Creates the DataSource

Method Signature

Task<(List<LogMessage> messages, bool success)> CreateDataSourceIfNotExistAsync();

Example:

var (messages, success) = await chatHistoryDataService.CreateDataSourceIfNotExistAsync();
if (success) Console.WriteLine("Data source created successfully");

DeleteIfDataSourceExistsAsync

Delete the DataSource if it exists

Method Signature

Task<(List<LogMessage> messages, bool success)> DeleteIfDataSourceExistsAsync();

Example:

var (messages, success) = await chatHistoryDataService.DeleteIfDataSourceExistsAsync();
if (success) Console.WriteLine("Data source deleted successfully");

DataSourceExistsAsync

Check if Data Source Exists

Method Signature

Task<bool> DataSourceExistsAsync();

Example:

var exists = await chatHistoryDataService.DataSourceExistsAsync();
Console.WriteLine($"Data source exists: {exists}");

FindAsync

Find Records by predicate

Method Signature


Task<ChatDocument> FindAsync(Expression<Func<ChatDocument, bool>> predicate);

Example:

var chatDocument = await chatHistoryDataService.FindAsync(doc => doc.UserId == "user123");
Console.WriteLine($"Found document: {chatDocument?.Content}");

 Expression<Func<ChatDocument, bool>> expr = x => x.Question == question;
 var document = await chatHistoryDataService.FindAsync(expr);
Console.WriteLine($"Found document: {document?.Content}");

FindAllAsync

Find all related records based on predicate

Method signature

Task<List<ChatDocument>> FindAllAsync(Expression<Func<ChatDocument, bool>> predicate);

Example:

var chatDocuments = await chatHistoryDataService.FindAllAsync(doc => doc.UserId == "user123");
foreach (var doc in chatDocuments)
{
    Console.WriteLine($"Found document: {doc.Content}");
}

GetChatHistoryContextAsync

Get Chat History Context which can be added to IChatCompletion

Method Signature

Task<HistoryContext> GetChatHistoryContextAsync(Expression<Func<ChatDocument, bool>> predicate);

Example:

var historyContext = await chatHistoryDataService.GetChatHistoryContextAsync(doc => doc.UserId == "user123");
Console.WriteLine($"History context: {historyContext}");
Task<HistoryContext> GetChatHistoryContextAsync(string query, ReadOnlyMemory<float>? queryEmbeddings, int size, string userId, double rerankerScoreThreshold);
var query = "What is the capital of France?";
var queryEmbeddings = new ReadOnlyMemory<float>(/* Embedding values */);
int size = 10;
string userId = "user123";
double rerankerScoreThreshold = 3.5;

var historyContext = await chatHistoryDataService.GetChatHistoryContextAsync(query, queryEmbeddings, size, userId, rerankerScoreThreshold);
Console.WriteLine($"History context: {historyContext}");

//Add to ChatCompletion 
 IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
 if (historyContext != null)
 {
     chatHistory.AddMessage(AuthorRole.Assistant, historyContext.ToString());
 }

GetDocumentsByQueryAsync

Get documents by Query for the user

Method Signature

Task<List<ChatDocument>> GetDocumentsByQueryAsync(string query, ReadOnlyMemory<float>? queryEmbeddings, int size, string userId, double rerankerScoreThreshold = 3.5);

Example:

 
var query = "What is the capital of France?";
var queryEmbeddings = new ReadOnlyMemory<float>(/* Embedding values */);
int size = 10;
string userId = "user123";
double rerankerScoreThreshold = 3.5;

var chatDocuments = await chatHistoryDataService.GetDocumentsByQueryAsync(query, queryEmbeddings, size, userId, rerankerScoreThreshold);
foreach (var doc in chatDocuments)
{
    Console.WriteLine($"Question: {doc.Question}, Answer: {doc.Content}");
}

GetDocumentsByUserIdAsync

Get documents by UserId

Method Signature

Task<List<ChatDocument>> GetDocumentsByUserIdAsync(string userId);

Example:

 
var chatDocuments = await chatHistoryDataService.GetDocumentsByUserIdAsync("user123");
foreach (var doc in chatDocuments)
{
    Console.WriteLine($"Question: {doc.Question}, Answer: {doc.Content}");
}


Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.5 248 10/11/2024
1.0.4 87 10/10/2024

Added implmentation for Cosmos and Azure Ai Search. Cosoms is still on previous