2024年8月

在上次的文章中,我们已经详细介绍了GraphRag的基本功能和使用方式。如果你还不熟悉,建议先阅读前面的文章

通过前两篇文章,相信你已经了解到
GraphRag.Net
目前只支持OpenAI规范的接口,但许多小伙伴在社区中提议,希望能增加对本地模型(例如:ollama等)的支持。所以这次,我们将探讨如何在GraphRag.Net中使用自定义模型和本地模型。

为什么选择GraphRag.Net?

GraphRag.Net
采用了
Semantic Kernel
作为基础,让我们能够非常简洁地抽象出会话与向量接口。因此,用户可以非常方便地实现自己定制的解决方案。接下来,我们会通过一个具体的例子,展示如何将本地模型和国产模型集成到
GraphRag.Net
中。

默认配置方法

首先,我们来看看如何进行默认配置:

// OpenAI配置
builder.Configuration.GetSection("OpenAI").Get<OpenAIOption>();
// 文档切片配置
builder.Configuration.GetSection("TextChunker").Get<TextChunkerOption>();
// 配置数据库连接
builder.Configuration.GetSection("GraphDBConnection").Get<GraphDBConnectionOption>();

// 注意,需要先注入配置文件,然后再注入GraphRag.Net
builder.Services.AddGraphRagNet();

这里,我们将在默认配置中注入OpenAI的配置、文本切片的配置和数据库连接的配置。然后,依次注入这些配置文件和
GraphRag.Net
的服务。

自定义配置方法

如果需要自定义模型或本地模型,可能需要实现一些额外的服务接口,下面是自定义配置的示例:

var kernelBuild = Kernel.CreateBuilder();
kernelBuild.Services.AddKeyedSingleton<ITextGenerationService>("mock-text", new MockTextCompletion());
kernelBuild.Services.AddKeyedSingleton<IChatCompletionService>("mock-chat", new MockChatCompletion());
kernelBuild.Services.AddSingleton<ITextEmbeddingGenerationService>(new MockTextEmbeddingGeneratorService());
kernelBuild.Services.AddKeyedSingleton("mock-embedding", new MockTextEmbeddingGeneratorService());

builder.Services.AddGraphRagNet(kernelBuild.Build());

在这个自定义配置示例中,我们引入了三个自定义服务接口:
ITextGenerationService

IChatCompletionService

ITextEmbeddingGenerationService

实现自定义服务接口

接下来,我们需要为每个服务接口提供具体的实现。以下是三个接口的具体实现:

实现
IChatCompletionService

  public class MockChatCompletion : IChatCompletionService
  {
      private readonly Dictionary<string, object?> _attributes = new();
      private string _chatId;


      private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
      {
          NumberHandling = JsonNumberHandling.AllowReadingFromString,
          Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
      };

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

      public MockChatCompletion()
      {

      }

      public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
      {
          StringBuilder sb = new();
          string result = $"这是一条Mock数据,便于聊天测试,你的消息是:{chatHistory.LastOrDefault().ToString()}";
          return [new(AuthorRole.Assistant, result.ToString())];
      }

      public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
      {
          StringBuilder sb = new();
          string result = $"这是一条Mock数据,便于聊天测试,你的消息是:{chatHistory.LastOrDefault().ToString()}";
          foreach (var c in result)
          {
              yield return new StreamingChatMessageContent(AuthorRole.Assistant, c.ToString());
          }
      }
  }

实现
ITextGenerationService

 public class MockTextCompletion : ITextGenerationService, IAIService
 {
     private readonly Dictionary<string, object?> _attributes = new();
     private string _chatId;

     private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
     {
         NumberHandling = JsonNumberHandling.AllowReadingFromString,
         Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
     };

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

     public MockTextCompletion()
     {

     }

     public async Task<IReadOnlyList<TextContent>> GetTextContentsAsync(string prompt, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
     {
         StringBuilder sb = new();
         string result = $"这是一条Mock数据,便于聊天测试,你的消息是:{prompt}";
         return [new(result.ToString())];
     }

     public async IAsyncEnumerable<StreamingTextContent> GetStreamingTextContentsAsync(string prompt, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
     {
         StringBuilder sb = new();
         string result = $"这是一条Mock数据,便于聊天测试,你的消息是:{prompt}";
         foreach (var c in result)
         {
             var streamingTextContent = new StreamingTextContent(c.ToString(), modelId: "mock");

             yield return streamingTextContent;
         }
     }
 }

实现
ITextEmbeddingGenerationService

  public sealed class MockTextEmbeddingGeneratorService : ITextEmbeddingGenerationService
  {
      private Dictionary<string, object?> AttributesInternal { get; } = [];
      public IReadOnlyDictionary<string, object?> Attributes => this.AttributesInternal;
      public MockTextEmbeddingGeneratorService()
      {

      }
      public async Task<IList<ReadOnlyMemory<float>>> GenerateEmbeddingsAsync(
        IList<string> data,
        Kernel? kernel = null,
        CancellationToken cancellationToken = default)
      {
          IList<ReadOnlyMemory<float>> results = new List<ReadOnlyMemory<float>>();

          float[] array1 = { 1.0f, 2.0f, 3.0f };
          float[] array2 = { 4.0f, 5.0f, 6.0f };
          float[] array3 = { 7.0f, 8.0f, 9.0f };

          // 将数组包装为ReadOnlyMemory<float>并添加到列表中
          results.Add(new ReadOnlyMemory<float>(array1));
          results.Add(new ReadOnlyMemory<float>(array2));
          results.Add(new ReadOnlyMemory<float>(array3));

          return results;
      }

      public void Dispose()
      {

      }
  }

看到这里,你可能已经发现,集成自定义模型和本地模型非常简单。只需按照上述步骤,实现相应的接口并注入配置,你就可以在
GraphRag.Net
中使用这些自定义的功能。

结语

通过本文的介绍,我们了解了如何在
GraphRag.Net
中集成国产模型和本地模型。希望大家能够根据这些示例,开发出更多适合自己需求的功能。更多精彩内容,欢迎关注我的公众号,并发送进群加入我们的
GraphRag.Net
交流群,与社区小伙伴们一起交流学习!

感谢阅读,我们下期再见!

大家好,我是码农先森。

经常听到身边写 Java、Go 的朋友提到程序异步、非阻塞、线程、协程,让系统性能提高到百万、千万并发,使我甚是惊讶属实羡慕。对于常年写 PHP 的我来说,最初听到这几个词时,脑袋一直处于蒙圈状态,回过头来看着自己手上同步阻塞的 PHP 代码,心想着「异步、非阻塞、线程、协程」到底是个什么东东,这么厉害嘛。其实 PHP 中也有线程、协程,但在日常的编程中几乎不会使用,原因是 PHP-FPM 多进程模式下并不支持线程、协程,使用 PHP 编程的程序员绝大多数都离不开 PHP-FPM 。这也就导致了 PHP 程序员对那些概念没有接触,那就更别提理解了,因此为了广大的 PHP 程序员同胞们能够和 Java、Go 的程序员对上话,特地对「同步、阻塞、异步、非阻塞」这几个概念进行了深度的分析,争取把 PHP 程序员的腰杆挺直溜。

按照惯例先上八股文这道菜:

  • 同步阻塞:当一个操作被调用时,调用者将被阻塞,直到这个操作完成并返回结果。在此期间,调用者无法进行其他任务。
  • 异步阻塞:当一个操作被调用时,调用者不会被阻塞,而是可以继续执行其他任务。然而,它仍然需要等待被调用的操作完成,并在操作完成后处理其结果。这个等待过程可能是阻塞的。
  • 同步非阻塞:调用者发起一个操作后,不会被阻塞并可以继续执行其他任务。虽然调用者可以立即获得控制权,但它仍然需要等待操作完成才能处理结果。在等待的过程中,调用者可以主动轮询或者不断尝试获取操作结果,以避免长时间的阻塞。
  • 异步非阻塞:调用者发起一个操作后,不会被阻塞并可以继续执行其他任务。同时,调用者也不需要等待操作完成来处理结果。相反,调用者可以注册一个回调函数或者使用类似事件驱动的机制,当操作完成后被自动触发回调函数来处理结果。

基础知识扎实的朋友看这个八股文就足以解惑了,不过看得懂八股文的毕竟是少数英俊帅气人,你说气不气人集颜值与才华于一体,别看说的就是各位看官「哈哈」。言归正传,那看不懂八股文的怎么搞?别急,且听我结合生活中的例子娓娓道来。

你每天上班匆匆路过的早餐店,今天额外的多人,你凑近一看原来是来了位身材高挑楚楚动人的美女服务员,结果你按耐不住心中的激动,今天高低得买两个馒头外加一杯豆浆,由于买的人太多,蒸好的馒头早已卖完,这时你只能等正在蒸的,期间你什么也干不了只能眼勾勾的干等着,那么这时的你是同步阻塞的。

由于来买早餐的人越来越多,离上班的时间也越来越近,你开始了骚动,每隔几分钟就问美女服务员馒头蒸好了没?此时的你不再干等,而是开始刷刷抖音看看工作群,因为你已经付钱了所以还是得等馒头,由于美女服务员太忙了没空主动告诉你,需要你自己不断地问,那么这时的你是同步非阻塞的。

过了高峰期人变少了,视野更广阔了,你看美女服务员更清楚了,结果你又开始眼勾勾的干等着,抖音也不刷了工作群的消息也不顾了。由于美女服务员不忙了,开始主动叫那位身穿格子衫背双肩包帅哥,馒头蒸好了,这时的你甩了甩头上的刘海,接过了美女服务员手中的馒头会心一笑,顺便还加了对方的微信,那么此时的你是异步阻塞的。

隔天你为了再睹芳容,又来到了这家早餐店,一向抠门的你甩手就点了两个肉包。这时美女服务员迎面笑脸告知你肉包还需耐心等待哦,蒸好了会微信通知你。在炎炎的夏日里你路上走的太匆忙,此时的你口渴难耐,就去隔壁小卖部买了瓶82年的可乐,还坐着吹了会空调。随着微信的一声叮咚,你起身去早餐店,接过了美女服务员手中的肉包,那么此时的你是异步非阻塞的。

有了美女服务员的投喂,你工作的干劲都十足了,同时应该也把「同步、阻塞、异步、非阻塞」这几个概念搞懂了吧。其实这里的同步异步和阻塞非阻塞,容易搞混淆就像你看美女服务员容易丢魂一样,在这个例子中同步异步需要关注的是「美女服务员是否会主动的通知你」,主动通知你那么对你来说就是异步的,需要你去询问那么对你来说就是同步的。阻塞非阻塞需要关注的点是「你是否是眼勾勾的干等着」,如果你只能干等那就是阻塞的,如果你还能干点其他的事情比如刷抖音、买82年的可乐,那么就是非阻塞的。

美女也看了道理也懂了,有的朋友们又要产生新的疑问了,那在程序中怎么体现、怎么用「同步、阻塞、异步、非阻塞」呢?那我们就开始上代码,毕竟看美女服务员的目的也是为了能够深入交往嘛,也就等同于实践上手了,你细品是不是这个理。

开整!

我们先来看
同步阻塞
的例子,使用 socket_create、socket_bind、socket_listen 函数创建绑定并监听了 8080 端口,然后一直阻塞在 socket_accept 函数上,直到有客户端连接的到来。传统的 PHP-FPM 就是同步阻塞的模式,不过 PHP-FPM 多进程模型,在接收到客户端连接
$client
后就交给由子进程进行后续的处理了,在这个例子只举例了单进程的模式。

<?php

// 同步阻塞模式

// 创建一个监听 Socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

// 绑定 8080 端口
socket_bind($socket, '0.0.0.0', 8080);

// 开始监听
socket_listen($socket);

while(true){
    // 会阻塞在这里,一直等着客户端来连接
    // 结合刚刚的例子可以理解为,你一直在这里眼勾勾的干等馒头,啥也干不了
    $client = socket_accept($socket);
    if($client){
        echo "客官来了" . PHP_EOL;
    }
}

再来看看
同步非阻塞
的例子,同样也是监听了 8080 端口,不同的是将套接字
$socket
设置成了非阻塞模式。那么这种情况下将不会一直阻塞在 socket_accept 函数上,会继续往下执行,如果没有写其他的逻辑,就会出现放空炮的现象。这种模式在实际的编程中基本上不会采用,会把系统榨干,这一点值得注意一下,谁写了这样的代码就要拉出去罚站了。

<?php

// 同步非阻塞模式

// 创建一个监听 Socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

// 绑定 8080 端口
socket_bind($socket, '0.0.0.0', 8080);

// 开始监听
socket_listen($socket);

// 这里设置成非阻塞
socket_set_nonblock($socket);

while(true){
    // 不会阻塞在这里
    $client = socket_accept($socket);
    if($client){
        echo "客官来了" . PHP_EOL;
    }

    // 会继续往下执行
    // 结合刚刚的例子,可以在这里刷刷抖音、看看工作群消息等
    // ...

    // 如果你上面没有写任何的逻辑,这些最好 sleep 一下
    // 不然 CPU 就会被榨干了,也就是说不要一直眼勾勾的盯着美女服务员会被吸干
    // 要适当的休息一下
    sleep(5);
}

继续接着看看
异步阻塞
的例子,还好有 Swoole 不然这种模式的例子都没有地方找了,这里感谢一下 Swoole 为 PHP 程序员做的贡献,让我们硬气了一回。构造一个 HTTP 服务并监听了 9501 端口,然后设置了针对 Request 的异步回调函数,但如果在回调函数里面使用了类似 sleep、PDO 等的 PHP 原生函数,就会阻塞整个进程,导致无法处理其他的 Requset 请求。这种情况下的程序性能直接和同步阻塞等同了,所以异步阻塞模式在实际的编程实践中也不常用,还不如使用同步阻塞模式了。这里提醒一点,在新版的 Swoole 中已经可以通过 HOOK 的方式支持 PHP 原生函数协程化了,这一点也值得庆幸。

<?php

// 异步阻塞模式

// 创建一个 Swoole 的异步 HTTP 服务器
$http = new Swoole\Http\Server('127.0.0.1', 9501);

// 设置异步回调函数
$http->on('request', function ($request, $response) {
    // 阻塞了整个进程,使用 PHP 原生的 PDO、Redis 等都会阻塞当前进程
    // 结合刚刚的例子,只能干等着,这里你啥也干不了
    sleep(5);

    $response->end("OK");
});

// 启动服务器
$http->start();

最后来看看
异步非阻塞
的例子,这种模式是目前在实践中性能最好的,和上面例子唯一不同的是在 Request 回调函数中使用了协程类,便不会阻塞整个进程,能够释放出 CPU 的控制权去处理其他的请求。当然在新版的 Swoole 中也不一定需要使用协程类,使用原生的函数同样不会阻塞进程了,这一点大大减低了 PHP 程序员编程的心智负担。

<?php

// 异步非阻塞模式

// 创建一个 Swoole 的异步 HTTP 服务器
$http = new Swoole\Http\Server('127.0.0.1', 9501);

// 设置异步回调函数
$http->on('request', function ($request, $response) {
    // 不会阻塞整个进程,这里还可以使用类似其他的协程客户端
    // swoole\Coroutine\MySQL
    // swoole\Coroutine\Redis
    // 结合刚刚的例子,这里你可以去刷抖音、买82年的可乐
    // 也是说你有空去处理其他的请求了,不用这里干等
    // 等5秒过后,又可以回来继续向下执行,接过肉包之后你就可以上班去了,虽然你有百般不舍。
    Co::sleep(5);

    $response->end("OK");
});

// 启动服务器
$http->start();

虽然你依然忘不了早餐店美女服务员的容颜,但空空的口袋催促着你赶紧去上班了。看到这里你既欣赏了美女的容颜,同时又把「同步、阻塞、异步、非阻塞」也搞懂了,简直两全其美,了解了这些概念对以后学习 Go 语言也大有裨益。但是大家都知道这么一个道理,看懂了并不等于真的懂了,很多人一看就会一做就废,因此最好自己上手实践一下,在知中行,在行中知,做到知行合一,就像看美女服务员不是目的而是想要更深入一步交流,就此打住哈哈。在市面上绝大多数的高性能程序都是异步非阻塞的模式,比如 Nginx、Redis 等,如果大家想写出高性能的程序最好是优先考虑这种模式,因为借鉴才是最快的学习方法。本次分享的内容到就此结束了,希望对大家能有所帮助。

感谢大家阅读,个人观点仅供参考,欢迎在评论区发表不同观点。


欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

相关:

ubuntu desktop改用无桌面方式启动



在使用Linux做异构计算等科学计算的时候一个常见的问题就是:

NVIDIA显卡关闭图形显示 —— 彻底禁用NVIDIA GPU 的显示输出接口 —— ubuntu无桌面方式启动 —— NVIDIA显卡模式切换(显示模式切换为计算模式)


网上关于这个问题的解决方法很多,比如:

  1. 修改BIOS,更新BIOS,在BIOS中进行设置;

  2. 在系统中使用NVIDIA的管理命令nvidia的smi-setting进行修改和模式切换;


可以说网上的方法特别多,也特别复杂,其实这个操作可以不用那么复杂,本文就以Ubuntu系统为例,我们只需要关闭系统的桌面显示功能也就是关闭了图形化显示功能,那么自然就达到目的了,总之,在Ubuntu系统中我们通过关闭系统的桌面模式就可以达成这个目的。


缺点:

本文的这个方法十分方便,但是缺点也很明显,那就是需要重启系统,不过网上给出的其他方法也都需要重启系统,这么一看好像也就不算是缺点了。



关闭图形界面:

sudo systemctl set-default multi-user.target
sudo reboot


打开图形界面:

sudo systemctl set-default graphical.target
sudo reboot



转载请注明出处:

一、概述

内存交换空间(Swap Space)是计算机内存的一种补充,位于硬盘驱动器上。当物理内存(RAM)不足时,系统会将不活跃的页面(或称为内存页)移到交换空间中,以释放物理内存给更需要的进程。这种方式虽然比直接从物理内存中读取数据要慢,但能有效避免系统因内存不足而崩溃。

二、配置

1. 配置原则
  • 交换空间的总大小一般推荐为物理内存的两倍和32MB中的较大者,但不超过2GB(不同来源可能有不同建议,具体根据实际需求和环境调整)。
  • 专用交换分区是推荐的方法,因为它性能更优且管理方便。
2. 配置方法(以Linux为例)

创建交换文件

  1. 关闭现有的交换空间(如果已存在)。
  2. 使用
    dd
    命令创建交换文件。例如,创建一个10GB的交换文件:
sudo dd if=/dev/zero of=/swapfile bs=1G count=10

3. 设置交换文件的权限,仅允许root用户读写:

sudo chmod 600 /swapfile

4. 将文件设置为交换空间:

sudo mkswap /swapfile

5. 激活交换空间:

sudo swapon /swapfile

配置交换分区

  1. 分区:使用
    fdisk

    parted
    等工具在磁盘上分出一个分区用于交换。
  2. 格式化:使用
    mkswap
    命令格式化该分区为交换分区。
  3. 激活:使用
    swapon
    命令激活交换分区。

三、查看

在Linux中,可以使用多种命令查看交换空间的使用情况:

  1. free命令:显示系统的内存使用情况,包括物理内存、交换空间等。

free -m

-m
选项表示以MB为单位显示。

2. swapon命令:查看当前正在使用的交换文件或交换分区。

swapon --show

3. cat /proc/swaps命令:查看当前正在使用的交换空间及其相关信息。

cat /proc/swaps

4. top或htop命令:这些交互式系统监视工具可以在终端中实时显示系统资源使用情况,包括交换空间。

四、作用

  1. 内存扩展:当物理内存不足时,交换空间提供额外的存储空间,确保系统可以继续运行。
  2. 性能优化:通过将不常用的内存页移动到交换空间,可以为当前需要执行的程序腾出物理内存空间,从而提高系统性能。
  3. 系统稳定性:防止因内存不足而导致的系统崩溃或性能下降。

五、示例

1. 创建交换文件:

sudo dd if=/dev/zero of=/swapfile bs=1G count=8# 创建一个8GB的交换文件  
sudo chmod
600 /swapfile
sudo mkswap
/swapfile
sudo swapon
/swapfile

2. 验证配置:
使用
free -m
命令查看内存和交换空间的使用情况,确认交换空间已正确配置并激活。

通过以上步骤,Linux系统成功配置了交换空间,提高了系统的稳定性和性能。

前言

今天推荐一款用 .NET 和 Vue3 实现的开源权限管理系统。它的界面清爽干净,功能强大,还具备灵活的角色权限分配功能,能够满足不同规模企业的管理需求。无论你是开发新手还是大神,都能轻松上手,快速搭建起自己的权限管理体系。别再犹豫了,赶快来试试吧!

项目简介

Malus是海棠的意思,顾名思义,海棠后台管理系统,读音与【马卢斯】相近,也可称作为马卢斯后台管理系统。

海棠后台管理系统,基于Net7+Vue3+Soybean 开发的前后端分离式权限管理系统,采用最原生最简洁的方式来实现, 前端清新优雅高颜值,后端 结构清晰,优雅易懂,功能强大,提供快速开发的解决方案。

项目特点

前后端分离,使用 JWT 认证。

后端:基于 .NET 7 和
sqlsugar
,集成常用组件,从0到1搭建。

前端:基于
Soybean Admin
做适配,主技术栈:Vue3、
NaiveUI 版本

极简的项目依赖,简洁清爽的目录结构,代码注释方便上手

数据库文件见【doc/dbsql/20240429.sql】

项目预览

MalusAdmin 提供了以下演示和文档资源:

项目演示

直接手机端上完美的运行,一套代码三端俱全。

1、系统登录

2、系统首页

3、系统页面

4、插件示例

5、移动端

项目地址

https://gitee.com/Pridejoy/MalusAdmin

总结

这款权限管理系统是用最新的.NET Core / .NET 7/8、SqlSugar、Vue 3、Vite 4、TypeScript 和 NaiveUI 技术组合打造的。前端界面设计得很漂亮,操作起来也很顺手;后端代码结构清晰,容易上手。

系统功能强大,可以快速搭建起来,非常适合需要快速开发权限管理系统的开发者和企业使用。

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号
[DotNet技术匠]
社区,与其他热爱技术的同行一起交流心得,共同成长!