- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
玩过大语言模型(LLM)的都知道OpenAI,然后微软Azure也提供了OpenAI的服务:Azure OpenAI,只需要申请到API Key,就可以使用这些AI服务。使用方式可以是通过在线Web页面直接与AI聊天,也可以调用AI的API服务,将AI的能力集成到自己的应用程序中。不过这些服务都是在线提供的,都会需要根据token计费,所以不仅需要依赖互联网,而且在使用上会有一定成本。于是,就出现了像Ollama这样的本地大语言模型服务,只要你的电脑足够强悍,应用场景允许的情况下,使用本地大语言模型也是一个不错的选择.
既然有这么多AI服务可以选择,那如果在我的应用程序中需要能够很方便地对接不同的AI服务,应该怎么做呢?这就是Semantic Kernel的基本功能,它是一个基于大语言模型开发应用程序的框架,可以让你的应用程序更加方便地集成大语言模型。Semantic Kernel可用于轻松生成 AI 代理并将最新的 AI 模型集成到 C#、Python 或 Java 代码库中。因此,它虽然在.NET AI生态中扮演着非常重要的角色,但它是支持多编程语言跨平台的应用开发套件.
Semantic Kernel主要包含以下这些核心概念:
有关Semantic Kernel的具体介绍可以参考微软官方文档.
话不多说,直接实操。这个演练的目的,就是使用部署在Azure上的gpt-4o大语言模型来实现一个简单的问答系统.
微软于2024年10月21日终止面向个人用户的Azure OpenAI服务,企业用户仍能继续使用。参考:https://finance.sina.com.cn/roll/2024-10-18/doc-incsymyx4982064.shtml 。
登录Azure Portal,新建一个Azure AI service,然后点击Go to Azure OpenAI Studio,进入OpenAI Studio:
进入后,在左侧侧边栏的共享资源部分,选择部署标签页,然后在模型部署页面,点击部署模型按钮,在下拉的菜单中,选择部署基本模型:
在选择模型对话框中,选择gpt-4o,然后点击确认按钮:
在弹出的对话框部署模型 gpt-4o中,给模型取个名字,然后直接点击部署按钮,如果希望对模型版本、安全性等做一些设置,也可以点击自定义按钮展开选项.
部署成功后,就可以在模型部署页面的列表中看到已部署模型的版本以及状态:
点击新部署的模型的名称,进入模型详细信息页面,在页面的终结点部分,把目标URI和密钥复制下来,待会要用。目标URI只需要复制主机名部分即可,比如https://qingy-m2e0gbl3-eastus.openai.azure.com这样:
首先创建一个控制台应用程序,然后添加Microsoft.SemanticKernel NuGet包的引用:
$ dotnet new console --name ChatApp
$ dotnet add package Microsoft.SemanticKernel
然后编辑Program.cs文件,加入下面的代码:
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using System.Text;
var apikey = Environment.GetEnvironmentVariable("azureopenaiapikey")!;
// 初始化Semantic Kernel
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
"gpt-4",
"https://qingy-m2e0gbl3-eastus.openai.azure.com",
apikey)
.Build();
// 创建一个对话完成服务以及对话历史对象,用来保存对话历史,以便后续为大模型
// 提供对话上下文信息。
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var chat = new ChatHistory("你是一个AI助手,帮助人们查找信息和回答问题");
StringBuilder chatResponse = new();
while (true)
{
Console.Write("请输入问题>> ");
// 将用户输入的问题添加到对话中
chat.AddUserMessage(Console.ReadLine()!);
chatResponse.Clear();
// 获取大语言模型的反馈,并将结果逐字输出
await foreach (var message in
chatCompletionService.GetStreamingChatMessageContentsAsync(chat))
{
// 输出当前获取的结果字符串
Console.Write(message);
// 将输出内容添加到临时变量中
chatResponse.Append(message.Content);
}
Console.WriteLine();
// 在进入下一次问答之前,将当前回答结果添加到对话历史中,为大语言模型提供问答上下文
chat.AddAssistantMessage(chatResponse.ToString());
Console.WriteLine();
}
在上面的代码中,需要将你的API Key和终结点URI配置进去,为了安全性,这里我使用环境变量保存API Key,然后由程序读入。为了让大语言模型能够了解在一次对话中,我和它之间都讨论了什么内容,在代码中,使用一个StringBuilder临时保存了当前对话的应答结果,然后将这个结果又通过Semantic Kernel的AddAssistantMessage方法加回到对话中,以便在下一次对话中,大语言模型能够知道我们在聊什么话题.
比如下面的例子中,在第二次提问时我问到“有那几次迁徙?”,AI能知道我是在说人类历史上的大迁徙,然后将我想要的答案列举出来:
到这里,一个简单的基于gpt-4o的问答应用就完成了,它的工作流程大致如下:
由于这里使用的gpt-4o大语言模型是在今年5月份发布的,而大语言模型都是基于现有数据经过训练得到的,所以,它应该不会知道5月份以后的事情,遇到这样的问题,AI只能回答不知道,或者给出一个比较离谱的答案:
你或许会想,那我将这些知识或者新闻文章下载下来,然后基于上面的代码,将这些信息先添加到对话历史中,让大语言模型能够了解上下文,这样回答问题的时候准确率不是提高了吗?这个思路是对的,可以在进行问答之前,将新闻的文本信息添加到对话历史中:
chat.AddUserMessage("这是一些额外的信息:" + await File.ReadAllTextAsync("input.txt"));
但是这样做,会造成下面的异常信息:
这个问题其实就跟大语言模型的Context Window有关。当今所有的大语言模型在一次数据处理上都有一定的限制,这个限制就是Context Window,在这个例子中,我们的模型一次最多处理12万8千个token(token是大语言模型的数据处理单元,它可以是一个词组,一个单词或者是一个字符),而我们却输入了147,845个token,于是就报错了。很明显,我们应该减少传入的数据量,但这样又没办法把完整的新闻文章信息发送给大语言模型。此时就要用到“检索增强生成(RAG)”.
其实,并不一定非要把整篇新闻文章发给大语言模型,可以换个思路:只需要在新闻文章中摘出跟提问相关的内容发送给大语言模型就可以了,这样就可以大大减小需要发送到大语言模型的token数量。所以,这里就出现了额外的一些步骤:
流程大致如下:
虚线灰色框中就是检索增强生成(RAG)相关流程,这里就不针对每个标号一一说明了,能够理解上面所述的4个大的步骤,就很好理解这张图中的整体流程。下面我们直接使用Semantic Kernel,通过RAG来增强模型应答.
首先,在Azure OpenAI Studio中,按照上文的步骤,部署一个text-embedding-3-small的模型,同样将终结点URI和API Key记录下来,然后,在项目中添加Microsoft.SemanticKernel.Plugins.Memory NuGet包的引用,因为我们打算先使用基于内存的文本向量数据库来运行我们的代码。Semantic Kernel支持多种向量数据库,比如Sqlite,Azure AI Search,Chroma,Milvus,Pinecone,Qdrant,Weaviate等等。在添加引用的时候,需要使用--prerelease参数,因为Microsoft.SemanticKernel.Plugins.Memory包目前还处于alpha阶段.
将上面的代码改成下面的形式:
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using System.Text;
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Text;
#pragma warning disable SKEXP0010, SKEXP0001, SKEXP0050
const string CollectionName = "LatestNews";
var apikey = Environment.GetEnvironmentVariable("azureopenaiapikey")!;
// 初始化Semantic Kernel
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
"gpt-4",
"https://qingy-m2e0gbl3-eastus.openai.azure.com",
apikey)
.Build();
// 创建文本向量生成服务
var textEmbeddingGenerationService = new AzureOpenAITextEmbeddingGenerationService(
"text-embedding-3-small",
"https://qingy-m2e0gbl3-eastus.openai.azure.com",
apikey);
// 创建用于保存文本向量的内存向量数据库
var memory = new MemoryBuilder()
.WithMemoryStore(new VolatileMemoryStore())
.WithTextEmbeddingGeneration(textEmbeddingGenerationService)
.Build();
// 从外部文件以Markdown格式读入内容,然后根据语义产生多个段落
var markdownContent = await File.ReadAllTextAsync(@"input.md");
var paragraphs =
TextChunker.SplitMarkdownParagraphs(
TextChunker.SplitMarkDownLines(markdownContent.Replace("\r\n", " "), 128),
64);
// 将各个段落进行量化并保存到向量数据库
for (var i = 0; i < paragraphs.Count; i++)
{
await memory.SaveInformationAsync(CollectionName, paragraphs[i], $"paragraph{i}");
}
// 创建一个对话完成服务以及对话历史对象,用来保存对话历史,以便后续为大模型
// 提供对话上下文信息。
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var chat = new ChatHistory("你是一个AI助手,帮助人们查找信息和回答问题");
StringBuilder additionalInfo = new();
StringBuilder chatResponse = new();
while (true)
{
Console.Write("请输入问题>> ");
var question = Console.ReadLine()!;
additionalInfo.Clear();
// 从向量数据库中找到跟提问最为相近的3条信息,将其添加到对话历史中
await foreach (var hit in memory.SearchAsync(CollectionName, question, limit: 3))
{
additionalInfo.AppendLine(hit.Metadata.Text);
}
var contextLinesToRemove = -1;
if (additionalInfo.Length != 0)
{
additionalInfo.Insert(0, "以下是一些附加信息:");
contextLinesToRemove = chat.Count;
chat.AddUserMessage(additionalInfo.ToString());
}
// 将用户输入的问题添加到对话中
chat.AddUserMessage(question);
chatResponse.Clear();
// 获取大语言模型的反馈,并将结果逐字输出
await foreach (var message in
chatCompletionService.GetStreamingChatMessageContentsAsync(chat))
{
// 输出当前获取的结果字符串
Console.Write(message);
// 将输出内容添加到临时变量中
chatResponse.Append(message.Content);
}
Console.WriteLine();
// 在进入下一次问答之前,将当前回答结果添加到对话历史中,为大语言模型提供问答上下文
chat.AddAssistantMessage(chatResponse.ToString());
// 将当次问题相关的内容从对话历史中移除
if (contextLinesToRemove >= 0) chat.RemoveAt(contextLinesToRemove);
Console.WriteLine();
}
重新运行程序,然后提出同样的问题,可以看到,现在的答案就正确了:
现在看看向量数据库中到底有什么。新添加一个对Microsoft.SemanticKernel.Connectors.Sqlite NuGet包的引用,然后,将上面代码的:
.WithMemoryStore(new VolatileMemoryStore())
改为:
.WithMemoryStore(await SqliteMemoryStore.ConnectAsync("vectors.db"))
重新运行程序,执行成功后,在bin\Debug\net8.0目录下,可以找到vectors.db文件,用Sqlite查看工具(我用的是SQLiteStudio)打开数据库文件,可以看到下面的表和数据:
Metadata字段保存的就是每个段落的原始数据信息,而Embedding字段则是文本向量,其实它就是一系列的浮点值,代表着文本之间在语义上的距离.
Semantic Kernel现在已经可以支持Ollama本地大语言模型了,虽然它目前也还是预览版。可以在项目中通过添加Microsoft.SemanticKernel.Connectors.Ollama NuGet包来体验。建议安装最新版本的Ollama,然后,下载两个大语言模型,一个是Chat Completion类型的,另一个是Text Embedding类型的。我选择了llama3.2:3b和mxbai-embed-large这两个模型:
代码上只需要将Azure OpenAI替换为Ollama即可:
// 初始化Semantic Kernel
var kernel = Kernel.CreateBuilder()
.AddOllamaChatCompletion(
"llama3.2:3b",
new Uri("http://localhost:11434"))
.Build();
// 创建文本向量生成服务
var textEmbeddingGenerationService = new OllamaTextEmbeddingGenerationService(
"mxbai-embed-large:latest",
new Uri("http://localhost:11434"));
通过本文的介绍,应该可以对Semantic Kernel、RAG以及在C#中的应用有一定的了解,虽然没有涉及原理性的内容,但基本已经可以在应用层面上提供一定的参考价值。Semantic Kernel虽然有些Plugins还处于预览阶段,但通过本文的介绍,我们已经可以看到它的强大功能,比如,允许我们很方便地接入各种流行的向量数据库,也允许我们很方便地切换到不同的AI大语言模型服务,在AI的应用集成上,Semantic Kernel发挥着重要的作用.
本文部分内容参考了微软官方文档《Demystifying Retrieval Augmented Generation with .NET》,代码也部分参考了文中内容。文章介绍得更为详细,建议有兴趣的读者移步阅读.
最后此篇关于在C#中基于SemanticKernel的检索增强生成(RAG)实践的文章就讲到这里了,如果你想了解更多关于在C#中基于SemanticKernel的检索增强生成(RAG)实践的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试使用增强的 for 循环遍历 Iterable,但我无法确定何时处理最后一个值。 public void apply(Tuple key,
我正在使用以下代码在 Sheet2 的 A:H 范围内查找和替换 Sheet1 中存在的单词列表(ColA 用于 FIND 单词,ColB 用于 REPLACE 单词)。它执行这项工作,但非常缓慢。可
我正在使用 Hibernate (JPA2) hibernate.hbm2ddl.auto=update用于测试和 hibernate.hbm2ddl.auto=validate用于生产。 我想要做的
基本问题: 为什么我只能用 Scala 编写: println(10) 为什么我不需要写: Console println(10) 后续问题: 如何引入一个新方法“foo”,它像“println”一样
我正在尝试将 Maven 项目迁移到 Bazel,但在 Datanucleus 增强方面遇到了麻烦。 后 jar -file 已构建,Datanucleus 会查看其中的内部并执行一些字节码操作以增强
正在使用 css3 转换进行漂亮的导航。为此还编写了一些 javascript。 但不幸的是它看起来有点凌乱。你们能给我一些优化 javascript 代码的技巧吗? 笔--> http://code
我想将自定义任务绑定(bind)到默认构建器发布周期中。我想在项目编译、打包、标记和部署之后但在增加版本号并提交之前运行此代码。 我将如何融入发布周期的这一部分? 最佳答案 不幸的是,release
我使用ElasticSearch 6.6。我的应用程序通过从不同数据源提取数据来构建ES索引。搜索未指定数据源。它只是建立一个类似的查询: GET employerdata/_search { "
我正在使用此代码将“k1 = v1; k2 = v2; k3 = v3; kn = vn”字符串解析为映射。 qi::phrase_parse( begin,end,
我正在试图弄清楚作业的一部分,但我已经把头撞在墙上有一段时间了。我正在尝试将 DNA 序列转录为 RNA 序列。然而,我收到了 ArrayOutOfBoundsException。我不熟悉使用增强的
我需要对基于 python Google App Engine 的应用程序的警告进行分类。我从 GAE stackdriver 下载日志。我认为 GAE Stackdriver 错误报告位于 http
我有一个 django charField,通过 is_valid() 方法进行检查。用户应该在此字段中输入有效的逻辑表达式,因此我编写了一个解析方法,如果表达式不正确,该方法会引发异常。 如何增强
我编写了以下控制台应用程序,要求用户输入一天。 我需要一些帮助才能改进,以便他们为一周中的所有日子提供正确的答案。 如果用户输入除星期一以外的任何其他日期,则输出为“今天”、“昨天”、“明天”,并在这
我在使用带有 ES6 let 关键字的模块模式(扩充)时遇到错误。 这有效。 var Example = ( Example => { Example.name = ""; retur
我只是问是否线程安全可以使用 我明确指出“doSomething()”是线程安全的。 最佳答案 线程安全取决于您正在迭代的 Collection,而不是 enhanced for 的使用。如果 Col
我有一个非常符合 this Jquery demo 的要求,这是一个简单的购物车演示。基本上,我需要对该演示进行两项改进。 我需要文本输入以及可用的“产品”。因此,当我拖放其中一种产品时,文本字段应随
我正在三个表 messages、message_recipients 和 users 上运行查询。 messages表的表结构: id int pk message_id int message te
这个问题已经有答案了: In detail, how does the 'for each' loop work in Java? (29 个回答) 已关闭 4 年前。 由于增强的 for 循环是只读
我在 css 中制作了一个很酷的鼠标悬停,当父级鼠标悬停时它会显示动画 gif。 这是我的代码:http://codepen.io/clemeeent/pen/oggzMa 问题是我将有大约 40 天
目前,当使用 Knockout foreach 绑定(bind)时,您可以使用 $index 访问当前索引。我想让其他类似的功能可用于我的内部绑定(bind) - 例如: array(让我访问正在操作
我是一名优秀的程序员,十分优秀!