History message

csharp
using System;
using System.Collections.Generic;
using System.Linq;
// Note: Requires NuGet package: Microsoft.ML.Tokenizers
using Microsoft.ML.Tokenizers;

namespace MultiProviderChat
{
    // --- 1. UNIFIED DATA MODELS ---
    public enum MessageRole { System, User, Assistant }

    public class ChatMessage
    {
        public MessageRole Role { get; set; }
        public string Content { get; set; }
    }

    // --- 2. PROVIDER INTERFACE ---
    public interface IProviderAdapter
    {
        object MapToProvider(IEnumerable<ChatMessage> history);
        int CountHistoryTokens(IEnumerable<ChatMessage> history);
    }

    // --- 3. TOKEN UTILITY ---
    public static class TokenUtility
    {
        // OpenAI models primarily use 'cl100k_base' or 'o200k_base'.
        private static readonly Tokenizer _tokenizer = TiktokenTokenizer.CreateForModel("gpt-4o");

        public static int CountTokens(string text) =>
            string.IsNullOrEmpty(text) ? 0 : _tokenizer.CountTokens(text);
    }

    // --- 4. CONCRETE ADAPTERS ---

    // OpenAI & Ollama: Both use the standard { role, content } format.
    public class OpenAIAdapter : IProviderAdapter
    {
        public object MapToProvider(IEnumerable<ChatMessage> history) =>
            history.Select(m => new { role = m.Role.ToString().ToLower(), content = m.Content }).ToList();

        public int CountHistoryTokens(IEnumerable<ChatMessage> history)
        {
            int tokens = 3; // Base overhead
            foreach (var msg in history)
            {
                tokens += 4 + TokenUtility.CountTokens(msg.Content);
            }
            return tokens;
        }
    }

    // Google Gemini: Uses "contents" with "role" (user/model) and "parts".
    // Note: 'Assistant' is mapped to 'model'.
    public class GeminiAdapter : IProviderAdapter
    {
        public object MapToProvider(IEnumerable<ChatMessage> history)
        {
            return history
                .Where(m => m.Role != MessageRole.System) // System instructions are separate in Gemini
                .Select(m => new
                {
                    role = m.Role == MessageRole.User ? "user" : "model",
                    parts = new[] { new { text = m.Content } }
                }).ToList();
        }

        public string ExtractSystemInstruction(IEnumerable<ChatMessage> history) =>
            history.FirstOrDefault(m => m.Role == MessageRole.System)?.Content ?? "";

        public int CountHistoryTokens(IEnumerable<ChatMessage> history) =>
            history.Sum(m => TokenUtility.CountTokens(m.Content)) + (history.Count() * 2);
    }

    // Anthropic: Uses 'user' and 'assistant'. System prompts are separate.
    public class AnthropicAdapter : IProviderAdapter
    {
        public object MapToProvider(IEnumerable<ChatMessage> history) =>
            history.Where(m => m.Role != MessageRole.System)
                   .Select(m => new { role = m.Role == MessageRole.User ? "user" : "assistant", content = m.Content })
                   .ToList();

        public int CountHistoryTokens(IEnumerable<ChatMessage> history) =>
            history.Sum(m => TokenUtility.CountTokens(m.Content)) + (history.Count() * 3);
    }

    // --- 5. THE FACTORY ---
    public static class ProviderFactory
    {
        public static IProviderAdapter GetAdapter(string providerName) =>
            providerName?.ToLower() switch
            {
                "openai" or "ollama" => new OpenAIAdapter(),
                "gemini" => new GeminiAdapter(),
                "anthropic" => new AnthropicAdapter(),
                _ => throw new NotSupportedException($"Provider '{providerName}' is not supported.")
            };
    }

    // --- 6. EXECUTION EXAMPLE ---
    public class Program
    {
        public static void Main()
        {
            var history = new List<ChatMessage>
            {
                new ChatMessage { Role = MessageRole.System, Content = "You are a helpful assistant." },
                new ChatMessage { Role = MessageRole.User, Content = "Hello Gemini!" },
                new ChatMessage { Role = MessageRole.Assistant, Content = "Hi! I am the Gemini model." }
            };

            // Process for Gemini
            var geminiAdapter = (GeminiAdapter)ProviderFactory.GetAdapter("gemini");
            var geminiPayload = geminiAdapter.MapToProvider(history);
            var systemText = geminiAdapter.ExtractSystemInstruction(history);

            Console.WriteLine($"Gemini System Instruction: {systemText}");
            Console.WriteLine($"Gemini History Count (User/Model turns): {((dynamic)geminiPayload).Count}");
            Console.WriteLine($"Estimated Tokens: {geminiAdapter.CountHistoryTokens(history)}");
        }
    }
}



; // Requires NuGet: Microsoft.Data.Sqlite public class ChatRepository { private readonly string _connectionString; public ChatRepository(string folderName) { // Ensure folder exists Directory.CreateDirectory(folderName); string dbPath = Path.Combine(folderName, "chat_history.db"); _connectionString = $"Data Source={dbPath}"; InitializeDatabase(); } private void InitializeDatabase() { using var connection = new SqliteConnection(_connectionString); connection.Open(); var command = connection.CreateCommand(); command.CommandText = @" CREATE TABLE IF NOT EXISTS Messages ( Id INTEGER PRIMARY KEY AUTOINCREMENT, ConversationId TEXT, Role TEXT, Content TEXT, Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP );"; command.ExecuteNonQuery(); } public void SaveMessage(string conversationId, ChatMessage message) { using var connection = new SqliteConnection(_connectionString); connection.Open(); var command = connection.CreateCommand(); command.CommandText = @" INSERT INTO Messages (ConversationId, Role, Content) VALUES ($convId, $role, $content);"; command.Parameters.AddWithValue("$convId", conversationId); command.Parameters.AddWithValue("$role", message.Role.ToString()); command.Parameters.AddWithValue("$content", message.Content); command.ExecuteNonQuery(); } public List<ChatMessage> GetHistory(string conversationId) { var history = new List<ChatMessage>(); using var connection = new SqliteConnection(_connectionString); connection.Open(); var command = connection.CreateCommand(); command.CommandText = "SELECT Role, Content FROM Messages WHERE ConversationId = $convId ORDER BY Timestamp ASC"; command.Parameters.AddWithValue("$convId", conversationId); using var reader = command.ExecuteReader(); while (reader.Read()) { history.Add(new ChatMessage { Role = Enum.Parse<MessageRole>(reader.GetString(0)), Content = reader.GetString(1) }); } return history; } }



csharp
public static void Main()
{
    var repo = new ChatRepository("MyAppData/ChatLogs");
    string sessionId = "session_001";

    // 1. Save a new message
    var userMsg = new ChatMessage { Role = MessageRole.User, Content = "Hello!" };
    repo.SaveMessage(sessionId, userMsg);

    // 2. Read history back
    var history = repo.GetHistory(sessionId);

    // 3. Convert for a provider
    var adapter = ProviderFactory.GetAdapter("openai");
    var payload = adapter.MapToProvider(history);
}

Comments

Popular posts from this blog

যমুনার বৃত্তান্ত: মর্মরের কারাগার

লেখকের নিয়তি

লীলা মজুমদার - আনন্দের জগৎ