SemanticKernel/C#:检索增强生成(RAG)简易实践
检索增强生成(RAG)是什么?
RAG是“Reference-based Generative model with Attention”的缩写,也可以被称为“Retrieval-Augmented Generation”,是一种结合了检索技术和生成模型的方法,主要用于自然语言处理任务,如文本生成、对话系统、机器翻译等。RAG模型通过从外部知识库中检索相关信息,并将其与输入文本结合,以生成更准确、更丰富的输出。这种方法可以提高模型的准确性和可解释性,因为它可以明确地指出生成的文本与哪些外部知识相关。RAG模型在处理需要大量背景知识的任务时特别有用,如专业领域的问答系统或对话代理。
本示例实现的效果
在使用大语言模型的过程中,会发现大语言模型在通用知识上很强,但是如果你问的是跟私有数据有关的事情,它就不知道了。比如有一段私有文本数据如下所示:
小X于2000年创建了一家名为“小X的世界”的公司,公司总部在湖北武汉,员工有300人。小X最喜欢的编程语言是C#,小X最喜欢的书是《平凡的世界》。
这只是个简单的例子,所以文本先取的很短,实际上可以换成是你的一些私有文档,然后让大语言模型根据你的私有文档进行回答,现在你如果问大语言模型,“小X创建的公司叫什么?”、”小X最喜欢的编程语言是什么?“等等一些根据私有文档才能回答的问题,大语言模型是不知道的,但是通过RAG就可以让大语言模型回答诸如此类的需要根据私有文档才能回答的问题。
实现的思路是通过嵌入模型将文本转化为向量,将向量存入数据库,检索时基于输入查询的向量表示,从知识库中检索出最相关的文档或片段。将获取的相关片段,嵌入到Prompt中,让大语言模型根据获取到的片段进行回答。
开始实践
安装所需的nuget包:
首先先初始化一个Kernel,这里我使用的大语言模型是硅基流动平台提供的开源的Qwen/Qwen2-7B-Instruct。
private readonly Kernel _kernel;
public SemanticKernelService()
{
var handler = new OpenAIHttpClientHandler();
var builder = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "Qwen/Qwen2-7B-Instruct",
apiKey: "api key",
httpClient: new HttpClient(handler));
var kernel = builder.Build();
_kernel = kernel;
}
由于硅基流动平台已经提供了与OpenAI兼容的格式,只需要在传入一个HttpClient将请求转发到硅基流动平台的api即可,OpenAIHttpClientHandler类如下所示:
public class OpenAIHttpClientHandler : HttpClientHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
UriBuilder uriBuilder;
switch (request.RequestUri?.LocalPath)
{
case "/v1/chat/completions":
uriBuilder = new UriBuilder(request.RequestUri)
{
// 这里是你要修改的 URL
Scheme = "https",
Host = "api.siliconflow.cn",
Path = "v1/chat/completions",
};
request.RequestUri = uriBuilder.Uri;
break;
case "/v1/embeddings":
uriBuilder = new UriBuilder(request.RequestUri)
{
// 这里是你要修改的 URL
Scheme = "https",
Host = "api.siliconflow.cn",
Path = "v1/embeddings",
};
request.RequestUri = uriBuilder.Uri;
break;
}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return response;
}
}
现在需要将文本转化为向量,需要先构建一个ISemanticTextMemory:
现在先来看看如何构建一个ISemanticTextMemory:
public async Task<ISemanticTextMemory> GetTextMemory2()
{
var memoryBuilder = new MemoryBuilder();
memoryBuilder.WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", "api key");
IMemoryStore memoryStore = await SqliteMemoryStore.ConnectAsync("memstore.db");
memoryBuilder.WithMemoryStore(memoryStore);
var textMemory = memoryBuilder.Build();
return textMemory;
}
首先需要有一个嵌入模型,这里使用的是OpenAI的text-embedding-ada-002模型,也尝试过使用硅基流动平台提供的嵌入模型,生成向量是没有问题的,但是在搜索的时候会报错,还没有解决。
使用SQLite来存储生成的向量。
var lines = TextChunker.SplitPlainTextLines(input, 100);
var paragraphs = TextChunker.SplitPlainTextParagraphs(lines, 1000);
foreach (var para in paragraphs)
{
await textMemory.SaveInformationAsync(index, id: Guid.NewGuid().ToString(), text: para, cancellationToken: default);
}
将文本分段,本示例文本内容很少,只有一段。
查看数据库:
已经将向量数据存入数据库了。
现在根据问题,搜索最相关的片段:
以“小X最喜欢的编程语言是什么?”这个问题为例。
将问题转化为向量并利用余弦相似度进行检索搜索最相关的片段:
将获取到的最相关的文本与问题嵌入到Prompt中,让大语言模型回答:
大语言模型的回答结果:
以上就基于SemanticKernel实现了一个简单的RAG应用。
下一步探索方向
虽然说我的电脑本地运行大语言模型不太行,但是在本地运行大语言模型还是有很多需求场景的,下一步探索如何在SemanticKernel中使用本地的大语言模型与嵌入模型。如果大语言模型运行不太行的话,再换成国内的平台,嵌入模型我试过,本地运行也还可以的。
本地运行使用的是Ollama,官方也有计划发布一个Ollama Connector:
网上查了一些资料,有些大佬已经实现了在SemanticKernel中使用Ollama中的对话模型与嵌入模型。可以等官方支持,也可以根据大佬们的分享,自己去实践一下。
Using local LLM with Ollama and Semantic Kernel - Learnings in IT (sachinsu.github.io)
Use Custom and Local AI Models with the Semantic Kernel SDK for .NET | Microsoft Learn
参考
2、https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/09-memory-with-chroma.ipynb