SemanticKernel/C#:实现接口,接入本地嵌入模型

简介: SemanticKernel/C#:实现接口,接入本地嵌入模型

前言

本文通过Codeblaze.SemanticKernel这个项目,学习如何实现ITextEmbeddingGenerationService接口,接入本地嵌入模型。

项目地址:https://github.com/BLaZeKiLL/Codeblaze.SemanticKernel

实践

SemanticKernel初看以为只支持OpenAI的各种模型,但其实也提供了强大的抽象能力,可以通过自己实现接口,来实现接入不兼容OpenAI格式的模型。

Codeblaze.SemanticKernel这个项目实现了ITextGenerationService、IChatCompletionService与ITextEmbeddingGenerationService接口,由于现在Ollama的对话已经支持了OpenAI格式,因此可以不用实现ITextGenerationService和IChatCompletionService来接入Ollama中的模型了,但目前Ollama的嵌入还没有兼容OpenAI的格式,因此可以通过实现ITextEmbeddingGenerationService接口,接入Ollama中的嵌入模型。

查看ITextEmbeddingGenerationService接口:

代表了一种生成浮点类型文本嵌入的生成器。

再看看IEmbeddingGenerationService<string, float>接口:

[Experimental("SKEXP0001")]
public interface IEmbeddingGenerationService<TValue, TEmbedding> : IAIService where TEmbedding : unmanaged
{
      Task<IList<ReadOnlyMemory<TEmbedding>>> GenerateEmbeddingsAsync(IList<TValue> data, Kernel? kernel = null, CancellationToken cancellationToken = default(CancellationToken));
}

再看看IAIService接口:

说明我们只要实现了

Task<IList<ReadOnlyMemory<TEmbedding>>> GenerateEmbeddingsAsync(IList<TValue> data, Kernel? kernel = null, CancellationToken cancellationToken = default(CancellationToken));
IReadOnlyDictionary<string, object?> Attributes { get; }

这个方法和属性就行。

学习Codeblaze.SemanticKernel中是怎么做的。

添加OllamaBase类:

public interface IOllamaBase
 {
     Task PingOllamaAsync(CancellationToken cancellationToken = new());
 }
 public abstract class OllamaBase<T> : IOllamaBase where T : OllamaBase<T>
 {
     public IReadOnlyDictionary<string, object?> Attributes => _attributes;
     private readonly Dictionary<string, object?> _attributes = new();
     protected readonly HttpClient Http;
     protected readonly ILogger<T> Logger;
     protected OllamaBase(string modelId, string baseUrl, HttpClient http, ILoggerFactory? loggerFactory)
     {
         _attributes.Add("model_id", modelId);
         _attributes.Add("base_url", baseUrl);
         Http = http;
         Logger = loggerFactory is not null ? loggerFactory.CreateLogger<T>() : NullLogger<T>.Instance;
     }
     /// <summary>
     /// Ping Ollama instance to check if the required llm model is available at the instance
     /// </summary>
     /// <param name="cancellationToken"></param>
     public async Task PingOllamaAsync(CancellationToken cancellationToken = new())
     {
         var data = new
         {
             name = Attributes["model_id"]
         };
         var response = await Http.PostAsJsonAsync($"{Attributes["base_url"]}/api/show", data, cancellationToken).ConfigureAwait(false);
         ValidateOllamaResponse(response);
         Logger.LogInformation("Connected to Ollama at {url} with model {model}", Attributes["base_url"], Attributes["model_id"]);
     }
     protected void ValidateOllamaResponse(HttpResponseMessage? response)
     {
         try
         {
             response.EnsureSuccessStatusCode();
         }
         catch (HttpRequestException)
         {
             Logger.LogError("Unable to connect to ollama at {url} with model {model}", Attributes["base_url"], Attributes["model_id"]);
         }
     }
 }

注意这个

public IReadOnlyDictionary<string, object?> Attributes => _attributes;

实现了接口中的属性。

添加OllamaTextEmbeddingGeneration类:

#pragma warning disable SKEXP0001
    public class OllamaTextEmbeddingGeneration(string modelId, string baseUrl, HttpClient http, ILoggerFactory? loggerFactory)
        : OllamaBase<OllamaTextEmbeddingGeneration>(modelId, baseUrl, http, loggerFactory),
            ITextEmbeddingGenerationService
    {
        public async Task<IList<ReadOnlyMemory<float>>> GenerateEmbeddingsAsync(IList<string> data, Kernel? kernel = null,
            CancellationToken cancellationToken = new())
        {
            var result = new List<ReadOnlyMemory<float>>(data.Count);
            foreach (var text in data)
            {
                var request = new
                {
                    model = Attributes["model_id"],
                    prompt = text
                };
                var response = await Http.PostAsJsonAsync($"{Attributes["base_url"]}/api/embeddings", request, cancellationToken).ConfigureAwait(false);
                ValidateOllamaResponse(response);
                var json = JsonSerializer.Deserialize<JsonNode>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
                var embedding = new ReadOnlyMemory<float>(json!["embedding"]?.AsArray().GetValues<float>().ToArray());
                result.Add(embedding);
            }
            return result;
        }
    }

注意实现了GenerateEmbeddingsAsync方法。实现的思路就是向Ollama中的嵌入接口发送请求,获得embedding数组。

为了在MemoryBuilder中能用还需要添加扩展方法:

#pragma warning disable SKEXP0001
    public static class OllamaMemoryBuilderExtensions
    {
        /// <summary>
        /// Adds Ollama as the text embedding generation backend for semantic memory
        /// </summary>
        /// <param name="builder">kernel builder</param>
        /// <param name="modelId">Ollama model ID to use</param>
        /// <param name="baseUrl">Ollama base url</param>
        /// <returns></returns>
        public static MemoryBuilder WithOllamaTextEmbeddingGeneration(
            this MemoryBuilder builder,
            string modelId,
            string baseUrl
        )
        {
            builder.WithTextEmbeddingGeneration((logger, http) => new OllamaTextEmbeddingGeneration(
                modelId,
                baseUrl,
                http,
                logger
            ));
            return builder;
        }       
    }

开始使用
 public async Task<ISemanticTextMemory> GetTextMemory3()
 {
     var builder = new MemoryBuilder();
     var embeddingEndpoint = "http://localhost:11434";
     var cancellationTokenSource = new System.Threading.CancellationTokenSource();
     var cancellationToken = cancellationTokenSource.Token;
     builder.WithHttpClient(new HttpClient());
     builder.WithOllamaTextEmbeddingGeneration("mxbai-embed-large:335m", embeddingEndpoint);
     IMemoryStore memoryStore = await SqliteMemoryStore.ConnectAsync("memstore.db");
     builder.WithMemoryStore(memoryStore);
     var textMemory = builder.Build();
     return textMemory;
 }
  builder.WithOllamaTextEmbeddingGeneration("mxbai-embed-large:335m", embeddingEndpoint);

实现了WithOllamaTextEmbeddingGeneration这个扩展方法,因此可以这么写,使用的是mxbai-embed-large:335m这个向量模型。

我使用WPF简单做了个界面,来试试效果。

找了一个新闻嵌入:

文本向量化存入数据库中:

现在测试RAG效果:

回答的效果也还可以。

大模型使用的是在线api的Qwen/Qwen2-72B-Instruct,嵌入模型使用的是本地Ollama中的mxbai-embed-large:335m。

目录
相关文章
|
4月前
|
达摩院 Linux API
阿里达摩院MindOpt求解器V1.1新增C#接口
阿里达摩院MindOpt求解器发布最新版本V1.1,增加了C#相关API和文档。优化求解器产品是求解优化问题的专业计算软件,可广泛各个行业。阿里达摩院从2019年投入自研MindOpt优化求解器,截止目前经历27个版本的迭代,取得了多项国内和国际第一的成绩。就在上个月,2023年12月,在工信部产业发展促进中心等单位主办的首届能源电子产业创新大赛上,MindOpt获得电力用国产求解器第一名。本文将为C#开发者讲述如何下载安装MindOpt和C#案例源代码。
183 3
阿里达摩院MindOpt求解器V1.1新增C#接口
|
4月前
|
IDE C# 开发工具
C#系列之接口介绍
C#系列之接口介绍
|
4月前
|
编译器 C# 开发者
C# 11.0中的新特性:覆盖默认接口方法
C# 11.0进一步增强了接口的灵活性,引入了覆盖默认接口方法的能力。这一新特性允许类在实现接口时,不仅可以提供接口中未实现的方法的具体实现,还可以覆盖接口中定义的默认方法实现。本文将详细介绍C# 11.0中接口默认方法覆盖的工作原理、使用场景及其对现有代码的影响,帮助开发者更好地理解和应用这一新功能。
|
18天前
|
C# 索引
C# 一分钟浅谈:接口与抽象类的区别及使用
【9月更文挑战第2天】本文详细对比了面向对象编程中接口与抽象类的概念及区别。接口定义了行为规范,强制实现类提供具体实现;抽象类则既能定义抽象方法也能提供具体实现。文章通过具体示例介绍了如何使用接口和抽象类,并探讨了其实现方式、继承限制及实例化差异。最后总结了选择接口或抽象类应基于具体设计需求。掌握这两者有助于编写高质量的面向对象程序。
42 5
|
1月前
|
C#
C# 面向对象编程(三)——接口/枚举类型/泛型
C# 面向对象编程(三)——接口/枚举类型/泛型
24 0
|
1月前
|
C#
C# WPF 将第三方DLL嵌入 exe
C# WPF 将第三方DLL嵌入 exe
31 0
|
1月前
|
API C#
SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景
SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景
63 0
|
1月前
|
存储 自然语言处理 C#
SemanticKernel/C#:检索增强生成(RAG)简易实践
SemanticKernel/C#:检索增强生成(RAG)简易实践
57 0
|
4月前
|
前端开发 API C#
C# 接口
C# 接口
38 1
|
4月前
|
C# 开发者 索引
C# 11.0中的所需成员:强化接口与抽象类的约束
【1月更文挑战第24天】C# 11.0引入了所需成员(Required members)的概念,这一新特性允许在接口和抽象类中定义必须被实现的成员,包括方法、属性、索引器和事件。通过所需成员,C# 强化了对接口实现和抽象类继承的约束,提高了代码的一致性和可维护性。本文将详细探讨C# 11.0中所需成员的工作原理、使用场景及其对现有编程模式的影响。