技术速递|利用 Redis 使 AI 驱动的 .NET 应用程序更加一致和智能

作者:Catherine Wang

排版:Alan Wang

Redis 是一种流行的内存数据存储,可用于解决构建和扩展智能应用程序的关键挑战。在本文中,你将了解如何使用 Redis 的 Azure 缓存来提高使用 Azure OpenAI 的应用程序的效率。

Redis 的 Azure 缓存不受最近的 Redis 许可证更新的影响:

"我们将持续的合作以确保 Azure 客户能够无缝地利用 Azure Cache for Redis 的所有层级。Azure Cache

for Redis、Azure Cache for Redis Enterprise 和 Enterprise Flash

服务不会中断,客户将收到及时的更新和错误修复,以保持最佳性能。" -- Julia Liuson,开发部总裁

本博客包括两个示例应用程序:

第一个是基于 .NET 揭秘检索增强生成的演示聊天 Semantic Kernel 应用程序。我添加了使用 Redis 保存额外知识并启用聊天历史记录的功能。完整示例位于 Chat App with Redis

第二个是一个演示应用程序,它在 .NET 8 中使用 Redis OM dotnet 进行 Redis 输出缓存,以提高生成式 AI 的一致性和弹性。完整示例位于 OpenAI 的输出缓存中。

Redis 为 OpenAI 模型提供了额外的知识

像 GPT 这样的 OpenAI 模型在大多数情况下都经过训练且知识渊博,但它们无法了解您公司的内部文档或最近的博客文章。这就是为什么您需要 Redis 作为附加知识的语义内存存储。

语义内存存储有两个基本要求:

  1. 智能应用程序无法直接读取文本 blob、图像、视频等非结构化数据。语义内存存储需要支持向量嵌入的有效保存。
  2. 智能应用程序需要执行总结、比较、异常检测等任务。语义内存存储需要支持搜索功能。这意味着用于查找相关数据的索引、距离算法和搜索查询。

Redis Enterprise 提供了 RediSearch 模块来满足这些需求。您可以使用内置的 FLAT 和 HNSW 索引算法、余弦等距离算法以及 KNN 搜索查询在 Redis 中保存向量嵌入。

Semantic Kernel 为 Redis 语义内存存储提供了一个 连接器。在语义内核中使用 Redis 作为语义内存存储的代码可能如下所示(来自 ChatAppRedis):

csharp 复制代码
//初始化Redis连接
ConnectionMultiplexer connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(redisConnection);
IDatabase database = connectionMultiplexer.GetDatabase();

//创建和使用Redis语义内存存储
RedisMemoryStore memoryStore = new RedisMemoryStore(database, vectorSize: 1536);
var memory = new SemanticTextMemory(
    memoryStore,
    new AzureOpenAITextEmbeddingGenerationService(aoaiEmbeddingModel, aoaiEndpoint, aoaiApiKey)
    );

//将文本字符串保存到 Redis 语义存储中的代码
await memory.SaveInformationAsync(collectionName, $"{your_text_blob}", $"{an_arbitrary_key}");

Redis 保留聊天记录以启用 AI 记忆

像 GPT 这样的 OpenAI 模型不会记住聊天记录。Semantic Kernel 提供聊天记录,以回答基于先前上下文的问题。例如,您可以要求聊天应用程序讲一个笑话。然后问为什么这个笑话好笑。第二个问题的答案将与第一个问题相关,这就是聊天记录所启用的功能。

Chat History 对象存储在内存中。客户要求将其保存到外部存储,以获得以下好处:

  • 资源效率------内存是应用服务器中的稀缺资源。
  • 应用程序弹性------在服务器故障转移期间,我们希望避免内存中的数据丢失和出现故障。

Redis 是保存聊天记录的理想选择,因为:

  • 数据过期支持 - 应用程序可以设置聊天记录的过期时间,以保持其记忆犹新。
  • 数据结构------Redis 支持 Hash 等内置数据结构,可以轻松查询相关消息。
  • 弹性 -- 如果会话由于服务器故障转移而中断,聊天可以继续。

下面是一个对话示例。如果没有在 Redis 中保留聊天记录,我就无法根据之前的上下文提出问题。

借助 Redis 中的聊天记录,我可以在开始新会话时继续之前的对话。

从 Redis 获取用户消息并转到 ChatHistory 对象的代码可能如下所示:

csharp 复制代码
RedisValue[] userMsgList = await _redisConnection.BasicRetryAsync(
    async(db) =>(await db.HashValuesAsync(_userName + ":" + userMessageSet)));

if (userMsgList.Any()) {
  foreach (var userMsg in userMsgList) {
    chat.AddUserMessage(userMsg.ToString());
  }
}

将用户消息保存到 Redis 的代码可能如下所示:

csharp 复制代码
chat.AddUserMessage(question);

await _redisConnection.BasicRetryAsync(
    async(_db) => _db.HashSetAsync($"{_userName}:{userMessageSet}", [
      new HashEntry(new RedisValue(Utility.GetTimestamp()), question)
    ]));

Redis Hash 用于每个用户的用户消息和辅助消息。Redis Insight 提供 UI 来查看和管理保存的聊天记录数据。

我们可以进一步利用这种聊天记录体验,将其转换为向量嵌入,以提高回答类似问题的一致性和相关性。好处是:

  • 对略有不同的问题给出一致的答案
  • 通过减少对 OpenAI 的 API 调用来节省成本

带有 Redis 的聊天应用程序作为参考,将以前的聊天记录保存在 Redis 语义内存存储中的代码可能如下所示:

csharp 复制代码
//将用户和助理消息作为向量嵌入存储在 Redis 中。 仅保存前一个会话。
if (_historyContent.Length > 0)
{
    await memory.SaveInformationAsync(_userName+"_chathistory", _historyContent, "lastsession");
}

用于搜索以前的聊天记录的代码可能如下所示:

csharp 复制代码
await foreach (var result in memory.SearchAsync(_userName+"_chathistory", question, limit: 1))
        stbuilder.Append(result.Metadata.Text);

我收到了关于类似问题的一致答复。即"法国首都在哪里?" 和"哪里是法国首都?"

我的实验代码有局限性:

  • 它只保存最后一次聊天会话的历史记录
  • 它不会根据逻辑分组将大型历史对象划分为块
  • 代码很乱

这就是我们在语义内核中添加对此体验的官方支持的原因,请参阅 microsoft/semantic-kernel #5436。请分享您对此问题的反馈,以帮助我们设计出色的体验。

Redis 提高 Web 应用程序性能

.NET 提供了多种缓存抽象来提高 Web 应用程序性能。这些仍然适用于您的整体智能应用。 此外,缓存抽象与语义缓存相辅相成,以提供高性能且一致的 Web 响应。

网页输出缓存

使用相同参数的重复 Web 请求会引入不必要的服务器利用率和依赖项调用。在 .NET 8 中,我们引入了 Redis 输出缓存来改进以下领域的 Web 应用程序:

  • 一致性------输出缓存确保相同的请求得到一致的响应。
  • 性能 -- 输出缓存避免了对数据存储或 API 的重复依赖调用,从而加快了整体 Web 响应时间。
  • 资源效率 -- 输出缓存可降低渲染网页时的 CPU 利用率。

下面是前面提到的示例应用程序,它使用 Redis 输出缓存来提高调用 DALL-E 以根据提示生成图像的性能。使用 OpenAI 图像生成进行输出缓存。使用输出缓存所需最少的编码。

使用 .NET 8 Redis 输出缓存的代码片段可能如下所示:

csharp 复制代码
app.MapGet("/cached/{prompt}", async (HttpContext context, string prompt, IConfiguration config) => 
    { await GenerateImage.GenerateImageAsync(context, prompt, config); 
    }).CacheOutput();

添加语义缓存以确保类似的提示收到一致的响应

Redis OM for dotnet 刚刚发布了语义缓存功能。它支持使用 Azure OpenAI 嵌入来生成向量。 以下代码片段显示了示例用法。完整的代码示例可以在 OutputCacheOpenAI 存储库中的 GenerateImageSC.cs 中找到。

使用 Redis 作为语义缓存的代码片段可能如下所示:

csharp 复制代码
_provider = new RedisConnectionProvider(_config["SemanticCacheAzureProvider"]);
var cache = _provider.AzureOpenAISemanticCache(
    _config["apiKey"], _config["AOAIResourceName"],
    _config["AOAIEmbeddingDeploymentName"], 1536);

if (cache.GetSimilar(_prompt).Length > 0) {
  imageURL = cache.GetSimilar(_prompt)[0];
  await context.Response.WriteAsync(
      "<!DOCTYPE html><html><body> " +
      $"<img src=\"{imageURL}\" alt=\"AI Generated Picture {_prompt}\" width=\"460\" height=\"345\">" +
      " </body> </html>");
}

这样,我就可以确保来自不同用户的类似提示会产生相同的图像,从而提高一致性并减少 API 调用,从而减少对 DALL-E 的调用并提高性能。下面的屏幕截图演示了类似提示重复使用的相同图片。

这是从提示"a french garden in monet style""返回的图像。

这是从提示"a monet style french garden"返回的图像。它与上面相同,因为先前的条目已被语义缓存:

这是 Redis 语义缓存中的条目:

Redis 语义缓存是 Redis 输出缓存的补充,因为:

  • 语义缓存进一步减少了 API 依赖调用,从而提高性能和降低成本。
  • 输出缓存可降低渲染网页时的 CPU 利用率。

总之,Redis 可以成为高性能、一致且低成本的智能 Web 应用程序解决方案和设计的关键部分。

下一步计划

最近发布的 Enterprise E5 SKU 对于试验 RediSearch 模块来说具有成本效益。请查看用于 Redis 的 Azure 缓存

今天就可以在您的智能应用程序中试用 Redis!通过在博文中发表评论,分享您对这些场景的想法和反馈 - 我们很乐意听取您的意见!

相关推荐
NAGNIP8 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab9 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab9 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP13 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年13 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼14 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS14 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区15 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈15 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang15 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx