基于 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企业级架构:
项目地址:github:https://github.com/junkai-li/NetCoreKevin
Gitee: https://gitee.com/netkevin-li/NetCoreKevin
配置自定义存储提供程序
**实现
ChatMessageStore**
创建自定义存储类继承Microsoft.Bot.Builder.IStorage,需实现以下方法:publicsealedclassKevinChatMessageStore:ChatMessageStore{privateIKevinAIChatMessageStore_chatMessageStore;publicstringThreadDbKey{get;privateset;}publicKevinChatMessageStore(IKevinAIChatMessageStorevectorStore,JsonElementserializedStoreState,stringAIChatsId,JsonSerializerOptions?jsonSerializerOptions=null){this._chatMessageStore=vectorStore??thrownewArgumentNullException(nameof(vectorStore));this.ThreadDbKey=AIChatsId;}publicoverrideasyncTaskAddMessagesAsync(IEnumerable<ChatMessage>messages,CancellationTokencancellationToken){await_chatMessageStore.AddMessagesAsync(messages.Select(x=>newChatHistoryItemDto(){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);// 设置前景色为红色// 保存原始颜色,以便之后恢复ConsoleColororiginalColor=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;}publicoverrideasyncTask<IEnumerable<ChatMessage>>GetMessagesAsync(CancellationTokencancellationToken){vardata=await_chatMessageStore.GetMessagesAsync(this.ThreadDbKey,cancellationToken);varmessages=data.ConvertAll(x=>JsonSerializer.Deserialize<ChatMessage>(x.SerializedMessage!)!);messages.Reverse();ConsoleColororiginalColor=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;returnmessages;}publicoverrideJsonElementSerialize(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);}实现IKevinAIChatMessageStore
TaskAddMessagesAsync(List<ChatHistoryItemDto>chatHistoryItems,CancellationTokencancellationToken);Task<List<ChatHistoryItemDto>>GetMessagesAsync(stringthreadId,CancellationTokencancellationToken);实现注入到AI中间件中
1.定义AI服务:/// <summary>/// AI服务/// </summary>publicclassAIAgentService:IAIAgentService{publicAIAgentService(){}publicasyncTask<AIAgent>CreateOpenAIAgent(stringname,stringprompt,stringdescription,stringurl,stringmodel,stringkeySecret,IList<AITool>?tools=null,ChatResponseFormat?chatResponseFormat=null,Func<IChatClient,IChatClient>?clientFactory=null,ILoggerFactory?loggerFactory=null,IServiceProvider?services=null){OpenAIClientOptionsopenAIClientOptions=newOpenAIClientOptions();openAIClientOptions.Endpoint=newUri(url);varai=newOpenAIClient(newApiKeyCredential(keySecret),openAIClientOptions);if(chatResponseFormat!=default){ChatOptionschatOptions=new(){ResponseFormat=chatResponseFormat};returnai.GetChatClient(model).CreateAIAgent(newChatClientAgentOptions(){Name=name,Instructions=prompt,ChatOptions=chatOptions,Description=description});}returnai.GetChatClient(model).CreateAIAgent(instructions:prompt,name:name,prompt,tools,clientFactory,loggerFactory,services);}publicasyncTask<AIAgent>CreateOpenAIAgent(stringmsg,stringurl,stringmodel,stringkeySecret,ChatClientAgentOptionschatClientAgentOptions){OpenAIClientOptionsopenAIClientOptions=newOpenAIClientOptions();openAIClientOptions.Endpoint=newUri(url);varai=newOpenAIClient(newApiKeyCredential(keySecret),openAIClientOptions);returnai.GetChatClient(model).CreateAIAgent(chatClientAgentOptions);}/// <summary>/// 智能体转换为McpServerTool/// </summary>/// <param name="aIAgent">智能体</param>/// <returns></returns>/// <exception cref="NotImplementedException"></exception>publicMcpServerToolAIAgentAsMcpServerTool(AIAgentaIAgent){returnMcpServerTool.Create(aIAgent.AsAIFunction());}/// <summary>/// 获取代理/// </summary>/// <returns></returns>publicIChatClientGetChatClient(stringurl,stringmodel,stringkeySecret){OpenAIClientOptionsopenAIClientOptions=newOpenAIClientOptions();openAIClientOptions.Endpoint=newUri(url);varai=newOpenAIClient(newApiKeyCredential(model),openAIClientOptions);returnai.GetChatClient(keySecret).AsIChatClient();}publicasyncTask<(AIAgent,AgentRunResponse)>CreateOpenAIAgentAndSendMSG(stringmsg,stringname,stringprompt,stringdescription,stringurl,stringmodel,stringkeySecret,IList<AITool>?tools=null,ChatResponseFormat?chatResponseFormat=null,Func<IChatClient,IChatClient>?clientFactory=null,ILoggerFactory?loggerFactory=null,IServiceProvider?services=null){OpenAIClientOptionsopenAIClientOptions=newOpenAIClientOptions();openAIClientOptions.Endpoint=newUri(url);varai=newOpenAIClient(newApiKeyCredential(keySecret),openAIClientOptions);varaiAgent=ai.GetChatClient(model).CreateAIAgent(instructions:prompt,name:name,prompt,tools,clientFactory,loggerFactory,services);if(chatResponseFormat!=default){ChatOptionschatOptions=new(){ResponseFormat=chatResponseFormat};aiAgent=ai.GetChatClient(model).CreateAIAgent(newChatClientAgentOptions(){Name=name,Instructions=prompt,ChatOptions=chatOptions,Description=description});}varreslut=awaitaiAgent.RunAsync(msg);return(aiAgent,reslut);}publicasyncTask<(AIAgent,AgentRunResponse)>CreateOpenAIAgentAndSendMSG(stringmsg,stringurl,stringmodel,stringkeySecret,ChatClientAgentOptionschatClientAgentOptions){OpenAIClientOptionsopenAIClientOptions=newOpenAIClientOptions();openAIClientOptions.Endpoint=newUri(url);varai=newOpenAIClient(newApiKeyCredential(keySecret),openAIClientOptions);varaiAgent=ai.GetChatClient(model).CreateAIAgent(chatClientAgentOptions);varreslut=awaitaiAgent.RunAsync(msg);return(aiAgent,reslut);}}
2.使用AI服务
addAi.Content=(awaitaIAgentService.CreateOpenAIAgentAndSendMSG(add.Content,aIModels.EndPoint,aIModels.ModelName,aIModels.ModelKey,newChatClientAgentOptions{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.returnnewKevinChatMessageStore(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))]publicclass TAIChatMessageStore : CUD_User {[MaxLength(200)]publicstringKey{ get;set;}[MaxLength(100)]publicstring ThreadId { get;set;}[Description("消息时间stamp")]publicDateTimeOffset?Timestamp{ get;set;}/// <summary>/// 角色标识/// </summary>[MaxLength(50)]publicstring Role { get;set;}publicstring SerializedMessage { get;set;}/// <summary>/// 消息内容/// </summary>publicstring? MessageText { get;set;}/// <summary>/// 消息id/// </summary>[Description("消息id")][MaxLength(100)]publicstring? MessageId { get;set;} }实现数据持久化
写入示例
使用 Entity Framework Core 保存数据:publicasyncTaskAddMessagesAsync(List<ChatHistoryItemDto>chatHistoryItems,CancellationTokencancellationToken){varadddata=chatHistoryItems.Select(t=>newTAIChatMessageStore{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);awaitaIChatMessageStoreRp.SaveChangesAsync();}读取示例
publicTask<List<ChatHistoryItemDto>>GetMessagesAsync(stringthreadId,CancellationTokencancellationToken){returnaIChatMessageStoreRp.Query().Where(t=>t.ThreadId==threadId&&t.IsDelete==false).Select(t=>newChatHistoryItemDto{Key=t.Key,ThreadId=t.ThreadId,Timestamp=t.Timestamp,SerializedMessage=t.SerializedMessage,MessageText=t.MessageText,Role=t.Role,MessageId=t.MessageId}).ToListAsync();}
性能优化建议
- 为高频查询字段(如
UserId和ChannelId)添加索引 - 实现数据分片策略应对大规模历史记录
- 考虑使用 Redis 缓存热点对话数据
安全注意事项
- 加密存储敏感对话内容
- 实现数据保留策略定期清理旧记录
- 遵守 GDPR 等数据隐私法规
测试验证方法
- 编写单元测试验证存储接口实现
- 使用 Bot Framework Emulator 进行端到端测试
- 进行负载测试验证性能表现
扩展可能性
- 添加全文检索支持(如 Azure Cognitive Search)
- 实现跨渠道对话历史同步
- 开发分析模块生成对话洞察报告
这种实现方式允许完全控制数据存储位置和格式,同时保持与 Bot Framework 的兼容性。根据具体需求可选择 SQL Server、Cosmos DB 或其他数据库解决方案。