基於 Microsoft Agent Framework 實現第三方聊天曆史存儲

理解 Microsoft Agent Framework

Microsoft Agent Framework 是一個用於構建對話式 AI 應用的框架,支持自然語言處理和上下文管理。其核心組件包括 Bot Framework SDK 和 Azure Bot Service,但默認聊天曆史通常存儲在 Azure 服務中。

具體實現可參考NetCoreKevin的kevin.AI.AgentFramework和KevinAIChatMessageStore服務模塊

基於NET構建的現代化AI智能體Saas企業級架構:

.Net——AI智能體開發基於 Microsoft Agent Framework 實現第三方聊天曆史存儲_AgentFramework

.Net——AI智能體開發基於 Microsoft Agent Framework 實現第三方聊天曆史存儲_AI_02

項目地址:github:https://github.com/junkai-li/NetCoreKevin Gitee: https://gitee.com/netkevin-li/NetCoreKevin

配置自定義存儲提供程序
  1. **實現 ChatMessageStore ** 創建自定義存儲類繼承 Microsoft.Bot.Builder.IStorage,需實現以下方法:
public sealed class KevinChatMessageStore : ChatMessageStore
 {
     private IKevinAIChatMessageStore _chatMessageStore;
     public string ThreadDbKey { get; private set; }
     public KevinChatMessageStore(
         IKevinAIChatMessageStore vectorStore,
         JsonElement serializedStoreState,
         string AIChatsId,
         JsonSerializerOptions? jsonSerializerOptions = null)
     {
         this._chatMessageStore = vectorStore ?? throw new ArgumentNullException(nameof(vectorStore));
         this.ThreadDbKey = AIChatsId; 
     }



     public override async Task AddMessagesAsync(
         IEnumerable<ChatMessage> messages,
         CancellationToken cancellationToken)
     {
         await _chatMessageStore.AddMessagesAsync(messages.Select(x => new ChatHistoryItemDto()
         {
             Key = this.ThreadDbKey + x.MessageId,
             Timestamp = DateTimeOffset.UtcNow,
             ThreadId = this.ThreadDbKey,
             MessageId = x.MessageId,
             Role = x.Role.Value,
             SerializedMessage = JsonSerializer.Serialize(x),
             MessageText = x.Text
         }).ToList(), cancellationToken);
         // 設置前景色為紅色
         // 保存原始顏色,以便之後恢復
         ConsoleColor originalColor = Console.ForegroundColor;
         Console.ForegroundColor = ConsoleColor.Red;
         Console.WriteLine("聊天消息記錄:", Color.Red);
         messages.Select(x => x.Text).ToList().ForEach(t => Console.WriteLine(t));
         // 設置前景色為紅色
         Console.ForegroundColor = ConsoleColor.Red;
         Console.WriteLine("聊天消息記錄添加完成", Color.Red);
         // 恢復原始顏色
         Console.ForegroundColor = originalColor;
     }

     public override async Task<IEnumerable<ChatMessage>> GetMessagesAsync(
         CancellationToken cancellationToken)
     {
         var data = await _chatMessageStore.GetMessagesAsync(this.ThreadDbKey, cancellationToken);
         var messages = data.ConvertAll(x => JsonSerializer.Deserialize<ChatMessage>(x.SerializedMessage!)!);
         messages.Reverse();
         ConsoleColor originalColor = Console.ForegroundColor;
         Console.ForegroundColor = ConsoleColor.Red;
         Console.WriteLine("所有聊天消息記錄開始:", Color.Red);
         messages.Select(x => x.Text).ToList().ForEach(t => Console.WriteLine(t));
         Console.WriteLine("所有聊天消息記錄結束:", Color.Red);
         // 恢復原始顏色
         Console.ForegroundColor = originalColor;
         return messages;
     }

     public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) =>
         // We have to serialize the thread id, so that on deserialization you can retrieve the messages using the same thread id.
         JsonSerializer.SerializeToElement(this.ThreadDbKey);
 }
  1. 實現IKevinAIChatMessageStore
Task AddMessagesAsync(List<ChatHistoryItemDto> chatHistoryItems, CancellationToken cancellationToken);

     Task<List<ChatHistoryItemDto>> GetMessagesAsync(string threadId, CancellationToken cancellationToken);
  1. 實現注入到AI中間件中 1.定義AI服務:
/// <summary>
 /// AI服務
 /// </summary>
 public class AIAgentService : IAIAgentService
 {
     public AIAgentService()
     {
     }
     public async Task<AIAgent> CreateOpenAIAgent(string name, string prompt, string description, string url, string model, string keySecret,
         IList<AITool>? tools = null, ChatResponseFormat? chatResponseFormat = null, Func<IChatClient, IChatClient>? clientFactory = null, ILoggerFactory? loggerFactory = null, IServiceProvider? services = null)
     {
         OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions();
         openAIClientOptions.Endpoint = new Uri(url);
         var ai = new OpenAIClient(new ApiKeyCredential(keySecret), openAIClientOptions);
         if (chatResponseFormat != default)
         {
             ChatOptions chatOptions = new()
             {
                 ResponseFormat = chatResponseFormat
             };
             return ai.GetChatClient(model).CreateAIAgent(new ChatClientAgentOptions()
             {
                 Name = name,
                 Instructions = prompt,
                 ChatOptions = chatOptions,
                 Description = description
             });
         }
         return ai.GetChatClient(model).CreateAIAgent(instructions: prompt, name: name, prompt, tools, clientFactory, loggerFactory, services);
     }

     public async Task<AIAgent> CreateOpenAIAgent(string msg, string url, string model, string keySecret, ChatClientAgentOptions chatClientAgentOptions)
     {
         OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions();
         openAIClientOptions.Endpoint = new Uri(url);
         var ai = new OpenAIClient(new ApiKeyCredential(keySecret), openAIClientOptions); 
         return ai.GetChatClient(model).CreateAIAgent(chatClientAgentOptions);
     }


     /// <summary>
     /// 智能體轉換為McpServerTool
     /// </summary>
     /// <param name="aIAgent">智能體</param>
     /// <returns></returns>
     /// <exception cref="NotImplementedException"></exception>
     public McpServerTool AIAgentAsMcpServerTool(AIAgent aIAgent)
     {
         return McpServerTool.Create(aIAgent.AsAIFunction());
     }

     /// <summary>
     /// 獲取代理
     /// </summary>
     /// <returns></returns>
     public IChatClient GetChatClient(string url, string model, string keySecret)
     {
         OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions();
         openAIClientOptions.Endpoint = new Uri(url);
         var ai = new OpenAIClient(new ApiKeyCredential(model), openAIClientOptions);
         return ai.GetChatClient(keySecret).AsIChatClient();
     }

     public async Task<(AIAgent, AgentRunResponse)> CreateOpenAIAgentAndSendMSG(string msg, string name, string prompt, string description, string url, string model, string keySecret,
         IList<AITool>? tools = null, ChatResponseFormat? chatResponseFormat = null, Func<IChatClient, IChatClient>? clientFactory = null, ILoggerFactory? loggerFactory = null, IServiceProvider? services = null)
     {
         OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions();
         openAIClientOptions.Endpoint = new Uri(url);
         var ai = new OpenAIClient(new ApiKeyCredential(keySecret), openAIClientOptions);
         var aiAgent = ai.GetChatClient(model).CreateAIAgent(instructions: prompt, name: name, prompt, tools, clientFactory, loggerFactory, services);
         if (chatResponseFormat != default)
         {
             ChatOptions chatOptions = new()
             {
                 ResponseFormat = chatResponseFormat
             };
             aiAgent = ai.GetChatClient(model).CreateAIAgent(new ChatClientAgentOptions()
             {
                 Name = name,
                 Instructions = prompt,
                 ChatOptions = chatOptions,
                 Description = description
             });
         }
         var reslut = await aiAgent.RunAsync(msg);
         return (aiAgent, reslut);
     }

     public async Task<(AIAgent, AgentRunResponse)> CreateOpenAIAgentAndSendMSG(string msg, string url, string model, string keySecret, ChatClientAgentOptions chatClientAgentOptions)
     {
         OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions();
         openAIClientOptions.Endpoint = new Uri(url);
         var ai = new OpenAIClient(new ApiKeyCredential(keySecret), openAIClientOptions); 
         var aiAgent = ai.GetChatClient(model).CreateAIAgent(chatClientAgentOptions); 
         var reslut = await aiAgent.RunAsync(msg);
         return (aiAgent, reslut);
     }
 }

2.使用AI服務

addAi.Content = (await aIAgentService.CreateOpenAIAgentAndSendMSG(add.Content, aIModels.EndPoint, aIModels.ModelName, aIModels.ModelKey,new ChatClientAgentOptions
    {
        Name= aiapp.Name, 
        Instructions = aIPrompts.Prompt,
        Description = aIPrompts.Description ?? "你是一個智能體,請根據你的提示詞進行相關回答",
        ChatMessageStoreFactory = ctx =>
        {
            // Create a new chat message store for this agent that stores the messages in a vector store.
            return new KevinChatMessageStore(
               kevinAIChatMessageStore,
               ctx.SerializedState,
              par.AIChatsId.ToString(),
               ctx.JsonSerializerOptions);
        }
    })).Item2.Text;
數據庫設計建議

對於關係型數據庫(如 SQL Server),建議的表結構:

/// <summary>
 /// 專門用於存儲AI聊天記錄的表
 /// </summary>
 [Table("TAIChatMessageStore")]
 [Description("專門用於存儲AI聊天記錄的表")]
 [Index(nameof(Key))]
 [Index(nameof(ThreadId))]
 [Index(nameof(Role))]
 [Index(nameof(MessageId))]
 public class TAIChatMessageStore : CUD_User
 {
     [MaxLength(200)]
     public string Key { get; set; }

     [MaxLength(100)]

     public string ThreadId { get; set; }

     [Description("消息時間stamp")]
     public DateTimeOffset? Timestamp { get; set; }

     /// <summary>
     /// 角色標識
     /// </summary>
     [MaxLength(50)]
     public string Role { get; set; }
     public string SerializedMessage { get; set; }
     /// <summary>
     /// 消息內容
     /// </summary>

     public string? MessageText { get; set; }
     /// <summary>
     /// 消息id
     /// </summary>
     [Description("消息id")]
     [MaxLength(100)] 
     public string? MessageId { get; set; }
 }
實現數據持久化
  1. 寫入示例 使用 Entity Framework Core 保存數據:
public async Task AddMessagesAsync(List<ChatHistoryItemDto> chatHistoryItems, CancellationToken cancellationToken)
     {
         var adddata = chatHistoryItems.Select(t => new TAIChatMessageStore
         {
             Id = SnowflakeIdService.GetNextId(),
             CreateTime = DateTime.Now,
             CreateUserId = CurrentUser.UserId,
             IsDelete = false,
             TenantId = CurrentUser.TenantId,
             ThreadId = t.ThreadId,
             Timestamp = t.Timestamp,
             Role = t.Role,
             Key = t.Key,
             SerializedMessage = t.SerializedMessage,
             MessageText = t.MessageText,
             MessageId = t.MessageId
         }).ToList();
         aIChatMessageStoreRp.AddRange(adddata);
         await aIChatMessageStoreRp.SaveChangesAsync();
     }
  1. 讀取示例
public Task<List<ChatHistoryItemDto>> GetMessagesAsync(string threadId, CancellationToken cancellationToken)
 {
     return aIChatMessageStoreRp.Query().Where(t => t.ThreadId == threadId && t.IsDelete == false).Select(t => new ChatHistoryItemDto
     {
         Key = t.Key,
         ThreadId = t.ThreadId,
         Timestamp = t.Timestamp,
         SerializedMessage = t.SerializedMessage,
         MessageText = t.MessageText,
         Role = t.Role,
         MessageId = t.MessageId
     }).ToListAsync();
 }
性能優化建議
  • 為高頻查詢字段(如 UserIdChannelId)添加索引
  • 實現數據分片策略應對大規模歷史記錄
  • 考慮使用 Redis 緩存熱點對話數據
安全注意事項
  • 加密存儲敏感對話內容
  • 實現數據保留策略定期清理舊記錄
  • 遵守 GDPR 等數據隱私法規
測試驗證方法
  1. 編寫單元測試驗證存儲接口實現
  2. 使用 Bot Framework Emulator 進行端到端測試
  3. 進行負載測試驗證性能表現
擴展可能性
  1. 添加全文檢索支持(如 Azure Cognitive Search)
  2. 實現跨渠道對話歷史同步
  3. 開發分析模塊生成對話洞察報告

這種實現方式允許完全控制數據存儲位置和格式,同時保持與 Bot Framework 的兼容性。根據具體需求可選擇 SQL Server、Cosmos DB 或其他數據庫解決方案。