基於 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,需實現以下方法:
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);
}
- 實現IKevinAIChatMessageStore
Task AddMessagesAsync(List<ChatHistoryItemDto> chatHistoryItems, CancellationToken cancellationToken);
Task<List<ChatHistoryItemDto>> GetMessagesAsync(string threadId, CancellationToken cancellationToken);
- 實現注入到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; }
}
實現數據持久化
- 寫入示例 使用 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();
}
- 讀取示例
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();
}
性能優化建議
- 為高頻查詢字段(如
UserId和ChannelId)添加索引 - 實現數據分片策略應對大規模歷史記錄
- 考慮使用 Redis 緩存熱點對話數據
安全注意事項
- 加密存儲敏感對話內容
- 實現數據保留策略定期清理舊記錄
- 遵守 GDPR 等數據隱私法規
測試驗證方法
- 編寫單元測試驗證存儲接口實現
- 使用 Bot Framework Emulator 進行端到端測試
- 進行負載測試驗證性能表現
擴展可能性
- 添加全文檢索支持(如 Azure Cognitive Search)
- 實現跨渠道對話歷史同步
- 開發分析模塊生成對話洞察報告
這種實現方式允許完全控制數據存儲位置和格式,同時保持與 Bot Framework 的兼容性。根據具體需求可選擇 SQL Server、Cosmos DB 或其他數據庫解決方案。