前言

本文通过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。

 

SemanticKernel/C#:实现接口,接入本地嵌入模型的更多相关文章

  1. 循序渐进BootstrapVue,开发公司门户网站(5)--- 使用实际数据接口代替本地Mock数据

    在我们开发一些门户网站功能的时候,有时候我们需要快速的创建数据模型来进行数据展示,因为数据结构可能处于不断的修正变化之中,因此服务端的接口我们可以暂时不开发,当我们基本完成数据结构和界面展示的时候,就 ...

  2. 微信SDK开发——接口接入

    园子里面很多关于微信接口开发的文章,Github也一堆的开源代码. 官方文档地址:http://mp.weixin.qq.com/wiki/home/index.html 接下来主要以代码为主,接口说 ...

  3. ACM MM | 中山大学等提出HSE:基于层次语义嵌入模型的精细化物体分类

    细粒度识别一般需要模型识别非常精细的子类别,它基本上就是同时使用图像全局信息和局部信息的分类任务.在本论文中,研究者们提出了一种新型层次语义框架,其自顶向下地由全局图像关注局部特征或更具判别性的区域. ...

  4. webpack正式、测试环境接口地址本地运行及打包命令配置

    声明:本文由w3h5原创,转载请注明出处:<webpack正式.测试环境接口地址本地运行及打包命令配置> https://www.w3h5.com/post/521.html 为了方便开发 ...

  5. 配置交换机Trunk接口流量本地优先转发(集群/堆叠)

    组网图形 Eth-Trunk接口流量本地优先转发简介 在设备集群/堆叠情况下,为了保证流量的可靠传输,流量的出接口设置为Eth-Trunk接口.那么Eth-Trunk接口中必定存在跨框成员口.当集群/ ...

  6. 华为S5300交换机配置基于接口的本地端口镜像

    配置思路 1.  将Ethernet0/0/20接口配置为观察端口(监控端口) 2.  将Ethernet0/0/1----Ethernet0/0/10接口配置为镜像端口 配置步骤 1.  配置观察端 ...

  7. xddpay.com 个人支付接口接入流程

    作为一个独立开发者产品需要支付接口是挺麻烦的,支付宝微信都不对个人开放,注册公司维护成本太高,市面上各种收款工具要么手续费太高,要么到账很慢,体验很不好. 看到 「小叮当支付」 这个收款工具,挺有意思 ...

  8. php短信验证码接口接入流程及代码示例

    对于绝大部分企业来说,所使用的短信验证码接口都是第三方短信服务商所提供,目前市场上短信服务商有很多,在此向大家推荐一家动力思维乐信,运营13年,值得信赖! 就拿动力思维乐信短信验证码接口为例,详细介绍 ...

  9. BufPay.com 个人收款接口 接入步骤

    作为独立开发者产品需要收款是非常麻烦的,注册公司维护成本太高,市面上各种收款工具要么手续费太高,要么到账很慢,体验很不好. 看到 「BufPay.com 个人收款」 这个收款工具,挺有意思的.原理是监 ...

  10. 用PHP调用证件识别接口识别本地图片

    前置条件 在开始前,请作如下准备:1.学会用PHP输出“Hello World” 2.去聚合数据申请证件识别专用的KEY:https://www.juhe.cn/docs/api/id/153 操作步 ...

随机推荐

  1. Elasticsearch之Nested Query nestedQuery查询数组

    es是通过符合条件的json记录找出来,本身并不是将数据中的记录filter过滤.es nestedQuery不是过滤的结果,是匹配的这条es记录,所以数组中的其他的记录也会查询出来1.方法1:可以在 ...

  2. php+sql后台实现从主表迁出至副表(数据超万条)

    上万条甚至上百万数据进行迁出做备份或者进行不妨碍原系统数据的操作,现在很多企业都会用到,目前就需要将上百万条数据进行迁出到副表保存并操作,直接再后台写一个按钮进行操作,既方便操作也不会很慢.毕竟是客户 ...

  3. 免费且离线的同声翻译利器「GitHub 热点速览」

    开源的翻译软件众多,但大多数依赖于翻译 API 服务,因此就需要联网.有次数限制.并非完全免费.然后,本周上榜的是一款可以离线使用的 Android 翻译软件:RTranslator,它创建于 4 年 ...

  4. mysql子查询不支持limit问题解决

    如果sql语句中的子查询包含limit 例如: select * from table where id in (select id from table limit 3) 会报错: This ver ...

  5. Kubernetes(三)实战入门

    实战入门 本章介绍如何在kubernetes集群中部署一个nginx服务,并能够对其进行访问. 1. Namespace Namespace主要作用是实现多套环境的资源隔离或者多租户的资源隔离. 默认 ...

  6. 半夜被慢查询告警吵醒,limit深度分页的坑

    分享是最有效的学习方式. 博客:https://blog.ktdaddy.com/ 故事 梅雨季,闷热的夜,令人窒息,窗外一道道闪电划破漆黑的夜幕,小猫塞着耳机听着恐怖小说,辗转反侧,终于睡意来了,然 ...

  7. HCIA基本命令

    ACLDHCPNATSTP-RSTP-MSTPVLANVLAN间路由VRRP链路聚合路由

  8. 千万别忽视基础!十张图带你一步步理解Java内存结构!

    作为一个Java程序员,在日常的开发中,不必像C/C++程序员那样,为每一个内存的分配而操心,JVM会替我们进行自动的内存分配和回收,方便我们开发.但是一旦发生内存泄漏或者内存溢出,如果对Java内存 ...

  9. python跟踪脚本运行过程(类似bash shell -x)

    #详细追踪 python -m trace --trace pyscript.py #显示调用了哪些函数 python -m trace --trackcalls pyscript.py

  10. Java开发环境配置(IDEA系列)

    一.IDEA安装和破解,JDK1.8 以上或JDK 16 下一步下一步,安装jdk配置环境变量: 二.Maven安装 和IDEA集成Maven IDEA 配置 Maven 环境 1.选择 IDEA中 ...