2024年3月

处理大量数据时,经常需要对数据进行分组和汇总,
groupby
为我们提供了一种简洁、高效的方式来实现这些操作,从而简化了数据分析的流程。

1. 分组聚合是什么

分组
是指根据一个或多个列的值将数据分成多个组,每个组包含具有相同键值(这里的键值即用来分组的列值)的数据行。

聚合
或者
汇总
则是指,在分组后,可以对每个组应用聚合函数(如求和、平均值、计数等),从而得到每个组的汇总信息。

2. 准备数据

下面的示例中使用的数据采集自A股2024年1月和2月的真实交易数据。
数据下载地址:
https://databook.top/

导入数据:

import pandas as pd

fp = r'D:\data\2024\历史行情数据-不复权-2024.csv'

df = pd.read_csv(fp)
df = df.loc[:, ["股票代码", "日期", "开盘", "收盘", "最高", "最低", "成交量"]]
df

image.png

3. groupby 使用示例

下面通过具体的示例演示
groupby
常用的使用方法。

3.1. 单列分组再聚合

单列聚合是指针对某一列汇总计算,比如:
针对
“股票代码”
聚合,看看不同股票的
开盘价

收盘价
的平均值。

# 只保留需要的列
data = df.loc[:, ["股票代码", "开盘", "收盘"]]

# 根据股票代码聚合平均值
data.groupby(by=["股票代码"]).mean()

image.png
一共
5352
支股票,聚合之后,红色框内的是每支股票
开盘价

收盘价
的平均值。

3.2. 多列分组再聚合

多列分组聚合时,按照
groupby

by
参数的顺序,依次进行分组,然后再聚合。
本次的使用的数据包含2024年1月和2月的数据,
我们先按照
“股票代码”
分组,再按
“月份”
分组,最后汇总信息。

聚合之前,先把日期的格式转换成月的形式:

data = df.loc[:, ["股票代码", "日期", "开盘", "收盘"]]
data["日期"] = data["日期"].str.slice(0, 7)
data

image.png

根据
“股票代码”

“日期”
来聚合每支股票每个月的
开盘价

收盘价
的最大值:

data.groupby(by=["股票代码", "日期"]).max()

image.png
聚合之后的
DataFrame
,有
2

Index
(索引)。

3.3. 一次分组多次聚合

聚合汇总信息时,可以一次汇总多个信息,这样分组一次就可以了,不用每次聚合都重复调用
groupby
去分组。
比如,下面的示例一次汇总出每支股票每个月
开盘价

收盘价

最大值

最小值

平均值

data.groupby(by=["股票代码", "日期"]).agg(["mean", "max", "min"])

image.png

3.4. 定制分组的聚合方式

更进一步,我们还可以针对不同的列采用不同的聚合方式。
比如,对
开盘价
汇总
最大值

平均值
,对
收盘价
汇总
最小值

平均值

data.groupby(by=["股票代码", "日期"]).agg(
    {
        "开盘": ["max", "mean"],
        "收盘": ["min", "mean"],
    }
)

image.png

3.5. 聚合后重置索引

从上面聚合后数据的截图中,可以发现,聚合之后,分组用的列(比如
["股票代码", "日期"]
)变为索引。
image.png
如上所示,聚合之后返回的
DataFrame
,红色框内的是索引(
index
),蓝色框内的是列(
columns
)。

如果,我们希望分组聚合统计之后,分组的列(比如
["股票代码", "日期"]
)仍然作为
DataFrame
的列,
可以在
groupby
分组时使用
as_index=False
参数。

data.groupby(by=["股票代码", "日期"], as_index=False).agg(
    {
        "开盘": ["max", "mean"],
        "收盘": ["min", "mean"],
    }
)

image.png
这样的话,分组的列(比如
["股票代码", "日期"]
)就不会成为索引。

4. 总结

总的来说,
groupby
函数是
pandas
库中一个非常常用的工具,它大大简化了数据处理和分析的过程,
使得用户能够更高效地洞察和理解数据。

功能简介

角色 功能
生产端(或发起端) 发布消息、定时消息(或叫延时)、顺序消息、可过期消息、事务消息。发送消息(rpc)
支持 Qos0、Qos1
消费端(或接收端) 订阅、取消订阅。消费-ACK(自动、手动)。监听(rpc)
服务端 发布-Confirm、订阅-Confirm、取消订阅-Confirm、派发-Retry、派发-Delayed
服务端 单线程、内存运行、快照持久化(自动、停机、手动)、Broker 模式集群、集群热扩展

中间件特点

  • 高吞吐量、低延迟

集群模式每秒能处理百万消息,最低延迟不到1毫秒。

  • 可扩展性

集群模式
支持服务节点热扩展
。流量高时随时加,流量低时可减。视频:

消息被快照持久化(类似于 redis)到本地磁盘,并且支持数据备份防止数据丢失

  • 可集群、高可用

可单机,可集群。集群内任何节点坏掉
只要还有“一个”同类节点
,仍可提供服务。视频:

本次更新

  • 新增 javascript 语言客户端实现
  • 调整 docker 基础镜像改为:adoptopenjdk/openjdk11-openj9 (内存可省一半)
  • 调整 folkmq-broker 更名为:folkmq-server-broker (相互兼容,体验不变)
  • 添加 folkmq-server 对 ws 输传协议的支持
  • 添加 rpc 异常传导机制
  • 完善 顺序消息的消费逻辑(改为串行消费),更适合数据库有序同步之类的场景
  • 完善 许可证本地处理机制(基于rsa签名机制)

启动服务:

docker run -p 18602:18602 -p 8602:8602 noearorg/folkmq-server:1.3.1

新功能示例(for Js or Node.js Demo):

const {FolkMQ} = require("@noear/folkmq");

async function main() {
    //创建客户端,并连接
    const client = await FolkMQ.createClient("folkmq:ws://127.0.0.1:18602")
                            .nameAs("demoapp")
                            .connect();

    //订阅主题,并指定加入的消费者分组
    client.subscribe("demo", null, true, message => {
        console.log(message);
    });

    //发布消息
    client.publish("demo", FolkMQ.newMqMessage("helloworld!"));
}

main();

代码仓库

官网

测试环境

WIN10,IIS 10,IISExpress 10,Chrome 120,Microsoft Edge 114

网站A 端口7001

只有1个Default.aspx,无前端代码。逻辑很简单,SetCookie用来把客户端传过来的值写入到cookie中,GetCookie用来将客户端传过来的cookie值再返回给客户端。


1 protected void Page_Load(objectsender, EventArgs e)2 {3     string Type = Request["Type"];4     if(Type=="SetCookie")5 {6         Response.Cookies.Add(new HttpCookie("CK",Request["Value"]));7         Response.Write("SetCookie OK");8 }9     else if (Type == "GetCookie")10 {11         var ck = Request.Cookies["CK"];12         if(ck==null)13 {14             Response.Write("无Cookie");15 }16         else
17 {18 Response.Write(ck.Value);19 }20 }21 
22     Response.Headers.Add("Access-Control-Allow-Credentials", "true");23     Response.Headers.Add("Access-Control-Allow-Origin", "http://localhost:7002");24     Response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,HEAD,OPTIONS");25     Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,api_key,Authorization,X-Requested-With");26     Response.Headers.Add("Access-Control-Allow-Private-Network", "*");27 }

Page_Load事件代码

网站B 端口7002

只有1个Default.aspx,只有前端代码。SetCookie按钮用ajax将随机数传给服务端,GetCookie按钮用来将cookie发给服务端再返回cookie值


1 <inputid="btnSetCookie"type="button"value="SetCookie"onclick="SetCookie()" />
2 <inputid="btnGetCookie"type="button"value="GetCookie"onclick="GetCookie()" />
3 
4 <scripttype="text/javascript">
5     functionSetCookie() {6 $.ajax({7 url:"http://localhost:7001/Default.aspx?Type=SetCookie&Value=" +parseInt(Math.random()* 100000),8 type:'GET',9 xhrFields: {10 withCredentials:true
11 }12 });13 }14 
15     functionGetCookie() {16 $.ajax({17 url:"http://localhost:7001/Default.aspx?Type=GetCookie",18 type:'GET',19 xhrFields: {20 withCredentials:true
21 },22 });23 }24 </script>

View Code

正常情况

正常情况下,先点击SetCookie按钮再点击GetCookie按钮,会是下图的情况,GetCookie获取到的值是SetCookie发送的值

异常情况

当两个网站都用IISExpress部署时,就会发生奇怪的事情

第一次操作时,SetCookie成功,但是GetCookie却是上一次的值,给人的感觉是浏览器设置cookie没成功。后面连续又操作了多次,却都是成功的。
我把IISExpress重新部署多次测试发现,上面的情况是偶发的,有时候一直正常,有时候第一次操作会出现设置cookie没成功的情况。
直接访问7001端口的网站,不跨域,就每次都是成功的。

我又换了多种不同的部署方式:

IISExpress部署7001网站A,IIS部署7002网站B,依然会偶发出现。
IIS部署7001网站A,IISExpress部署7002网站B,每次都成功。
都用IIS部署,每次都成功。

使用Chrome和Edge都是一样的效果,网上搜了下也没找到解决方案。

效果

使用gpt-3.5-turbo的效果

效果

什么是向量数据库?

向量数据库是一种专为处理高维向量数据而设计的数据库系统。与传统的关系型数据库不同,向量数据库专注于存储和查询向量数据,例如图像、音频、文本等。其核心思想是将数据表示为向量形式,并通过向量之间的相似度来进行查询和分析。

向量数据库的特点包括高效的相似度搜索和复杂的查询操作。通过利用向量之间的相似度,可以快速检索与查询向量相似的数据,适用于大规模数据集和高维度数据。此外,向量数据库还支持复杂的查询操作,如范围查询、聚类和聚合等。

在实际应用中,向量数据库被广泛用于图像搜索、推荐系统、自然语言处理等领域。例如,在图像搜索中,可以利用向量数据库来存储图像的特征向量,并通过计算相似度来实现快速的图像检索。在推荐系统中,可以利用用户的特征向量来实现个性化推荐。

总之,向量数据库是一种适用于处理高维向量数据的数据库系统,具有高效的相似度搜索和复杂的查询操作,广泛应用于图像搜索、推荐系统等领域。

什么是嵌入?

嵌入是一种在高维空间中将单词或其他数据表示为向量的方法。向量就像有方向和长度的箭头。高维意味着空间有很多维度,比我们能看到或想象的要多。这个想法是,相似的单词或数据将具有相似的向量,而不同的单词或数据将具有不同的向量。这有助于我们衡量它们的相关或不相关程度,并对它们进行操作,例如加、减、乘等。嵌入对 AI 模型很有用,因为它们可以以计算机可以理解和处理的方式捕获单词或数据的含义和上下文。

所以基本上你拿一个句子、段落或整页文本,然后生成相应的嵌入向量。当执行查询时,查询将转换为其嵌入表示形式,然后通过所有现有的嵌入向量执行搜索以找到最相似的嵌入向量。这类似于在必应上进行搜索查询时,它会为您提供与查询近似的多个结果。语义记忆不太可能给你一个完全匹配,但它总是会给你一组匹配,根据你的查询与其他文本的相似程度进行排名。

Chroma向量数据库

在本示例中,我使用的是Chroma向量数据库。

image-20240305103835056

在docker中运行chroma:

docker pull chromadb/chroma
docker run -p 8000:8000 chromadb/chroma

成功启动如下所示:

创建ISemanticTextMemory

因为向量数据库使用的是Chroma,因此需要下载这个包:

image-20240305142522438

#pragma warning disable SKEXP0003
            ISemanticTextMemory? memory;
#pragma warning disable SKEXP0003
            var memoryBuilder = new MemoryBuilder();
#pragma warning disable SKEXP0011
            memoryBuilder.WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", envVars["OpenAIAPIKey"]);

#pragma warning disable SKEXP0022
            var chromaMemoryStore = new ChromaMemoryStore("http://127.0.0.1:8000");

            memoryBuilder.WithMemoryStore(chromaMemoryStore);

            memory = memoryBuilder.Build();

ISemanticTextMemory

语义记忆的接口,用于创建和调用与文本相关的记忆。

WithOpenAITextEmbeddingGeneration

添加 OpenAI 文本嵌入服务。

ChromaMemoryStore

用于 Chroma 的 IMemoryStore 的实现。

本地需要开启Chroma服务。

保存自有数据

 // 创建 OpenFileDialog 对象
 OpenFileDialog openFileDialog = new OpenFileDialog();

 // 设置文件类型过滤器
 openFileDialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";

 // 显示文件选择对话框
 if (openFileDialog.ShowDialog() == true)
 {
     // 用户选择了一个文件,你可以通过 openFileDialog.FileName 获取文件的路径
     string filePath = openFileDialog.FileName;
     HandyControl.Controls.MessageBox.Show($"你选中的路径为{filePath}");

     var text = File.ReadAllText(filePath);

     const string MemoryCollectionName = "hello2";

     var id = Guid.NewGuid().ToString();

     await memory.SaveInformationAsync(MemoryCollectionName, id: id, text: text);
    
     HandyControl.Controls.MessageBox.Show($"完成");

这是以选择一个txt文件为例,核心就一行代码:

 await memory.SaveInformationAsync(MemoryCollectionName, id: id, text: text);

将一些信息保存到Semantic Memory中。

查看该方法的定义:

 Task<string> SaveInformationAsync(string collection, string text, string id, string? description = null, string? additionalMetadata = null, Kernel? kernel = null, CancellationToken cancellationToken = default(CancellationToken));

参数及含义:

参数名 类型 含义
collection string 保存数据的集合名
text string 要保存的数据
id string 唯一标识符
description string? 描述
additionalMetadata string? 额外的元数据
kernel Kernel? 包含服务、插件和其他状态的内核,供整个操作使用
cancellationToken CancellationToken 用于监视取消请求的 CancellationToken。默认值为 None

前面三项是必填的,其他项都有默认值。

 const string MemoryCollectionName = "hello2";

经过我的测试,集合名要是英文,中文会报错。

创建TextMemoryPlugin

代码如下所示:

/// <summary>
/// TextMemoryPlugin provides a plugin to save or recall information from the long or short term memory.
/// </summary>
[Experimental("SKEXP0003")]
public sealed class TextMemoryPlugin
{
    /// <summary>
    /// Name used to specify the input text.
    /// </summary>
    public const string InputParam = "input";
    /// <summary>
    /// Name used to specify which memory collection to use.
    /// </summary>
    public const string CollectionParam = "collection";

    /// <summary>
    /// Name used to specify memory search relevance score.
    /// </summary>
    public const string RelevanceParam = "relevance";

    /// <summary>
    /// Name used to specify a unique key associated with stored information.
    /// </summary>
    public const string KeyParam = "key";

    /// <summary>
    /// Name used to specify the number of memories to recall
    /// </summary>
    public const string LimitParam = "limit";

    private const string DefaultCollection = "generic";
    private const double DefaultRelevance = 0.0;
    private const int DefaultLimit = 1;

    private readonly ISemanticTextMemory _memory;
    private readonly ILogger _logger;

    /// <summary>
    /// Creates a new instance of the TextMemoryPlugin
    /// </summary>
    public TextMemoryPlugin(
        ISemanticTextMemory memory,
        ILoggerFactory? loggerFactory = null)
    {
        this._memory = memory;
        this._logger = loggerFactory?.CreateLogger(typeof(TextMemoryPlugin)) ?? NullLogger.Instance;
    }

    /// <summary>
    /// Key-based lookup for a specific memory
    /// </summary>
    /// <param name="key">The key associated with the memory to retrieve.</param>
    /// <param name="collection">Memories collection associated with the memory to retrieve</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    [KernelFunction, Description("Key-based lookup for a specific memory")]
    public async Task<string> RetrieveAsync(
        [Description("The key associated with the memory to retrieve")] string key,
        [Description("Memories collection associated with the memory to retrieve")] string? collection = DefaultCollection,
        CancellationToken cancellationToken = default)
    {
      
        if (this._logger.IsEnabled(LogLevel.Debug))
        {
            this._logger.LogDebug("Recalling memory with key '{0}' from collection '{1}'", key, collection);
        }

        var memory = await this._memory.GetAsync(collection, key, cancellationToken: cancellationToken).ConfigureAwait(false);

        return memory?.Metadata.Text ?? string.Empty;
    }

    /// <summary>
    /// Semantic search and return up to N memories related to the input text
    /// </summary>
    /// <param name="input">The input text to find related memories for.</param>
    /// <param name="collection">Memories collection to search.</param>
    /// <param name="relevance">The relevance score, from 0.0 to 1.0, where 1.0 means perfect match.</param>
    /// <param name="limit">The maximum number of relevant memories to recall.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    [KernelFunction, Description("Semantic search and return up to N memories related to the input text")]
    public async Task<string> RecallAsync(
        [Description("The input text to find related memories for")] string input,
        [Description("Memories collection to search")] string collection = DefaultCollection,
        [Description("The relevance score, from 0.0 to 1.0, where 1.0 means perfect match")] double? relevance = DefaultRelevance,
        [Description("The maximum number of relevant memories to recall")] int? limit = DefaultLimit,
        CancellationToken cancellationToken = default)
    {
      
        relevance ??= DefaultRelevance;
        limit ??= DefaultLimit;

        if (this._logger.IsEnabled(LogLevel.Debug))
        {
            this._logger.LogDebug("Searching memories in collection '{0}', relevance '{1}'", collection, relevance);
        }

        // Search memory
        List<MemoryQueryResult> memories = await this._memory
            .SearchAsync(collection, input, limit.Value, relevance.Value, cancellationToken: cancellationToken)
            .ToListAsync(cancellationToken)
            .ConfigureAwait(false);

        if (memories.Count == 0)
        {
            if (this._logger.IsEnabled(LogLevel.Warning))
            {
                this._logger.LogWarning("Memories not found in collection: {0}", collection);
            }
            return string.Empty;
        }

        return limit == 1 ? memories[0].Metadata.Text : JsonSerializer.Serialize(memories.Select(x => x.Metadata.Text));
    }

    /// <summary>
    /// Save information to semantic memory
    /// </summary>
    /// <param name="input">The information to save</param>
    /// <param name="key">The key associated with the information to save</param>
    /// <param name="collection">Memories collection associated with the information to save</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    [KernelFunction, Description("Save information to semantic memory")]
    public async Task SaveAsync(
        [Description("The information to save")] string input,
        [Description("The key associated with the information to save")] string key,
        [Description("Memories collection associated with the information to save")] string collection = DefaultCollection,
        CancellationToken cancellationToken = default)
    {          

        if (this._logger.IsEnabled(LogLevel.Debug))
        {
            this._logger.LogDebug("Saving memory to collection '{0}'", collection);
        }

        await this._memory.SaveInformationAsync(collection, text: input, id: key, cancellationToken: cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    /// Remove specific memory
    /// </summary>
    /// <param name="key">The key associated with the information to save</param>
    /// <param name="collection">Memories collection associated with the information to save</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    [KernelFunction, Description("Remove specific memory")]
    public async Task RemoveAsync(
        [Description("The key associated with the information to save")] string key,
        [Description("Memories collection associated with the information to save")] string collection = DefaultCollection,
        CancellationToken cancellationToken = default)
    {
      
        if (this._logger.IsEnabled(LogLevel.Debug))
        {
            this._logger.LogDebug("Removing memory from collection '{0}'", collection);
        }

        await this._memory.RemoveAsync(collection, key, cancellationToken: cancellationToken).ConfigureAwait(false);
    }
}

比较长,可以以后用到了什么函数再慢慢看,等一会我们就要接触到的函数如下:

/// <summary>
/// Semantic search and return up to N memories related to the input text
/// </summary>
/// <param name="input">The input text to find related memories for.</param>
/// <param name="collection">Memories collection to search.</param>
/// <param name="relevance">The relevance score, from 0.0 to 1.0, where 1.0 means perfect match.</param>
/// <param name="limit">The maximum number of relevant memories to recall.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
[KernelFunction, Description("Semantic search and return up to N memories related to the input text")]
public async Task<string> RecallAsync(
    [Description("The input text to find related memories for")] string input,
    [Description("Memories collection to search")] string collection = DefaultCollection,
    [Description("The relevance score, from 0.0 to 1.0, where 1.0 means perfect match")] double? relevance = DefaultRelevance,
    [Description("The maximum number of relevant memories to recall")] int? limit = DefaultLimit,
    CancellationToken cancellationToken = default)
{
  
    relevance ??= DefaultRelevance;
    limit ??= DefaultLimit;

    if (this._logger.IsEnabled(LogLevel.Debug))
    {
        this._logger.LogDebug("Searching memories in collection '{0}', relevance '{1}'", collection, relevance);
    }

    // Search memory
    List<MemoryQueryResult> memories = await this._memory
        .SearchAsync(collection, input, limit.Value, relevance.Value, cancellationToken: cancellationToken)
        .ToListAsync(cancellationToken)
        .ConfigureAwait(false);

    if (memories.Count == 0)
    {
        if (this._logger.IsEnabled(LogLevel.Warning))
        {
            this._logger.LogWarning("Memories not found in collection: {0}", collection);
        }
        return string.Empty;
    }

    return limit == 1 ? memories[0].Metadata.Text : JsonSerializer.Serialize(memories.Select(x => x.Metadata.Text));
}

一步一步来看:

[KernelFunction, Description("Semantic search and return up to N memories related to the input text")]

KernelFunction
是一个特性,可能是用于标记这个方法作为某种内核函数的一部分。具体的含义取决于这个特性是如何在代码中被使用的。

Description
是.NET框架中的一个标准特性,它提供了一个人类可读的描述,这个描述可以在运行时通过反射来访问。在这个例子中,它提供了对 RecallAsync 方法的简单描述:"Semantic search and return up to N memories related to the input text"。

public async Task<string> RecallAsync(
    [Description("The input text to find related memories for")] string input,
    [Description("Memories collection to search")] string collection = DefaultCollection,
    [Description("The relevance score, from 0.0 to 1.0, where 1.0 means perfect match")] double? relevance = DefaultRelevance,
    [Description("The maximum number of relevant memories to recall")] int? limit = DefaultLimit,
    CancellationToken cancellationToken = default)

RecallAsync
方法有input、collection、relevance、limit、cancellationToken参数,它们的含义如下:

参数名 含义
input 用于查看向量数据库中是否有相关数据的文本
collection 向量数据库中的集合名
relevance 相关性,0最低,1最高
limit 相关数据的最大返回数量
cancellationToken .NET中用于协调取消长时间运行的操作的结构

在向量数据库中查找相关数据:

 // Search memory
 List<MemoryQueryResult> memories = await this._memory
     .SearchAsync(collection, input, limit.Value, relevance.Value, cancellationToken: cancellationToken)
     .ToListAsync(cancellationToken)
     .ConfigureAwait(false);

向kernel导入插件:

// TextMemoryPlugin provides the "recall" function
kernel.ImportPluginFromObject(new TextMemoryPlugin(memory));

开始测试

用于测试的本地数据:

image-20240305150743859

这些数据大语言模型肯定不会知道的。

导入文件之后,开始测试:

            const string skPrompt = @"
ChatBot can have a conversation with you about any topic.
It can give explicit instructions or say 'I don't know' if it does not have an answer.

Information about me, from previous conversations:
- {{$fact1}} {{recall $fact1}}
- {{$fact2}} {{recall $fact2}}

Chat:
{{$history}}
User: {{$userInput}}
ChatBot: ";

            var chatFunction = kernel.CreateFunctionFromPrompt(skPrompt, new OpenAIPromptExecutionSettings { MaxTokens = 200, Temperature = 0.8 });

#pragma warning disable SKEXP0052

            var arguments = new KernelArguments();

            arguments["fact1"] = "我的名字是什么?";
            arguments["fact2"] = "我喜欢什么编程语言?";
            
            
            arguments[TextMemoryPlugin.CollectionParam] = "hello2";
            arguments[TextMemoryPlugin.LimitParam] = "2";
            arguments[TextMemoryPlugin.RelevanceParam] = "0.8";

            arguments["userInput"] = "我的名字叫什么?";

            // Process the user message and get an answer
            var answer = await chatFunction.InvokeAsync(kernel, arguments);

            Debug.WriteLine(answer);

在查找“我的名字是什么?”时,并没有查找到相关内容:

image-20240305152156869

在查看“我喜欢什么编程语言?”时,找到了相关内容:

查看查找到的相关内容:

image-20240305152641678

大语言模型可以根据获取到的数据进行回答:

MemoryChat

经过测试,我们发现是可行的,就可以写成问答的形式:

            loadingMemory2.Visibility = Visibility.Visible;

            string question = textBoxMemory1.Text;
            
            // Get user input
            history.AddUserMessage(question);

            const string skPrompt = @"
ChatBot can have a conversation with you about any topic.
It can give explicit instructions or say 'I don't know' if it does not have an answer.

Information about me, from previous conversations:
- {{$fact1}} {{recall $fact1}}

Chat:
{{$history}}
User: {{$userInput}}
ChatBot: ";

            var chatFunction = kernel.CreateFunctionFromPrompt(skPrompt, new OpenAIPromptExecutionSettings { MaxTokens = 200, Temperature = 0.8 });

#pragma warning disable SKEXP0052

            var arguments = new KernelArguments();

            arguments["fact1"] = question;       


            arguments[TextMemoryPlugin.CollectionParam] = "hello2";
            arguments[TextMemoryPlugin.LimitParam] = "2";
            arguments[TextMemoryPlugin.RelevanceParam] = "0.6";

            arguments["userInput"] = question;

            // Process the user message and get an answer
            var answer = await chatFunction.InvokeAsync(kernel, arguments);

            Debug.WriteLine(answer);


         
            // Print the results           
            richTextBoxMemory.AppendText(answer + "\r\n");
           
            // Add the message from the agent to the chat history
            history.AddMessage(Microsoft.SemanticKernel.ChatCompletion.AuthorRole.System, answer.ToString());

            loadingMemory2.Visibility = Visibility.Hidden;

使用gpt-3.5-turbo的效果

效果

使用gemini-pro的效果

只对了一个最喜欢的编程语言问题。

使用讯飞星火认知大模型的效果

一个都没答对。

参考

1、
Understanding AI plugins in Semantic Kernel and beyond | Microsoft Learn

2、
Memories in Semantic Kernel | Microsoft Learn

3、
LLM AI Embeddings | Microsoft Learn

4、
Vector Database | Microsoft Learn

5、
semantic-kernel/dotnet/notebooks/09-memory-with-chroma.ipynb at main · microsoft/semantic-kernel (github.com)

前言

经过前面的铺垫,终于继续更新了,这个大概率是本系列近期的最后一篇了。

同时之前有些内容更新,我也补充到这一篇里面。

关于 scoop 管理器的补充

scoop 常用命令

scoop help # 查看帮助
scoop help < 某个命令 > # 具体查看某个命令的帮助

scoop info <app>      # 查看 APP 信息
scoop install <app>   # 安装 APP
scoop uinstall <app>  # 卸载 APP
scoop reset [app]@[version] # 切换版本

scoop list  # 列出已安装的 APP
scoop search # 搜索 APP
scoop status # 检查哪些软件有更新

scoop update # 更新 Scoop 自身
scoop update appName1 appName2 # 更新某些 app
scoop update *  # 更新所有 app (前提是需要在 apps 目录下操作)

scoop bucket known # 通过此命令列出已知所有 bucket(软件源)
scoop bucket add bucketName # 添加某个 bucket

scoop cache rm <app>  # 移除某个 app 的缓存
scoop cache rm *      # 移除所有缓存
scoop cleanup <app>   # 删除旧版本

scoop 常见问题

安装软件时出现 hash 检查错误

  • 多次安装后仍出现错误,找到其 JSON 文件,下载到本地更改其 hash 值,上传到自己的 bucket 中,进行更新。
  • 使用
    scoop install -s
    选项以忽略其 hash 检查,具体使用方法请用
    scoop help install

更新软件时如何删除旧版本软件

使用
scoop cleanup
命令,具体用法查看
scoop help cleanup

终端

OK,来到 Windows 开发体验的重灾区了

命令行一直以来都是 Windows 的弱势,现在有了一些 Rust 工具链,可以慢慢改善这一现状。

Nerd Fonts

想要美化终端界面,请先安装一个 nerd 字体

这时候包管理器的作用再次体现,使用 scoop 就能安装。

先添加个 bucket

scoop bucket add nerd-fonts

项目地址:
https://github.com/matthewjberger/scoop-nerd-fonts

然后选择一个喜欢的字体进行安装

scoop install FiraCode-NF

另外,这个 bucket 里面的字体很多,现在已经不仅仅是 nerd font 了,也可以安装一些普通的字体,比如

scoop install SarasaGothic-SC # 安装更纱黑体(简体中文)
scoop install Wenquanyi-Microhei # 安装文泉驿微米黑
scoop install Wenquanyi-Zenhei # 安装文泉驿正黑

参考资料

内置 Powershell

先说结论:内置的 PowerShell 是垃圾

不推荐使用 oh-my-posh ,启动速度太慢了,本身 PowerShell 的启动速度就慢,加上 oh-my-posh 就更慢了。

终端美化使用 Starship 就行了,接下来会介绍。

编辑配置的方法:
nano $PROFILE

这是我的配置

# oh-my-posh init pwsh --config "$env:POSH_THEMES_PATH/amro.omp.json" | Invoke-Expression

Import-Module gsudoModule

set-alias -name ll -value ls
set-alias -name ef -value dotnet-ef

# starship
Invoke-Expression (&starship init powershell)
$ENV:STARSHIP_CONFIG = "$HOME\.config\starship\plain-text-symbols.toml"

Powershell 7

这是微软新出的开源跨平台终端,比内置的 PowerShell 的功能强了一丢丢,性能更好。
(从项目仓库来看,是使用 C# 开发的,所以跨平台做得不错)

项目地址:
https://github.com/PowerShell/PowerShell

下载个安装包安装就完事了

我的
$PROFILE
配置

Set-PSReadLineOption -PredictionSource HistoryAndPlugin
Set-PSReadLineKeyHandler -Chord Tab -Function MenuComplete
Set-PSReadLineKeyHandler -Key UpArrow -ScriptBlock {
	[Microsoft.PowerShell.PSConsoleReadLine]::HistorySearchBackward()
  [Microsoft.PowerShell.PSConsoleReadLine]::EndOfLine()
}
Set-PSReadLineKeyHandler -Key DownArrow -ScriptBlock {
	[Microsoft.PowerShell.PSConsoleReadLine]::HistorySearchForward()
  [Microsoft.PowerShell.PSConsoleReadLine]::EndOfLine()
}
Set-PSReadLineOption -PredictionViewStyle ListView
set-alias -name ll -value ls

# starship
Invoke-Expression (&starship init powershell)
$ENV:STARSHIP_CONFIG = "$HOME\.config\starship\gruvbox-rainbow.toml"

Starship

NuShell

https://www.nushell.sh/zh-CN/book/

参考资料

命令行工具

WinSW

进程管理工具,类似 Linux 下的 supervisor ,可以将要运行的程序安装成服务,设置自动运行。

安装

scoop install winsw

使用方式

与 supervisor 类似,写配置文件,然后安装服务,可以启动和停止啥的。

我的目录结构

这里我设置了两个服务,vnc 和 frpc

 WinSW
 ├─ config
 │  ├─ vnc.xml
 │  ├─ vnc.wrapper.log
 │  ├─ vnc.out.log.old
 │  ├─ vnc.out.log
 │  ├─ vnc.err.log.old
 │  ├─ vnc.err.log
 │  ├─ frpc.xml
 │  ├─ frpc.wrapper.log
 │  ├─ frpc.out.log.old
 │  ├─ frpc.out.log
 │  ├─ frpc.err.log.old
 │  └─ frpc.err.log
 └─ WinSW.exe

配置文件内容


frpc.xml
为例

<service>
  <id>frpc</id>
  <name>frpc</name>
  <description>frp自动启动</description>
  <executable>D:\softwares\frp\frpc-a.exe</executable>
  <arguments>-c D:\softwares\frp\frpc.ini</arguments>
  <log mode="roll"></log>
  <startmode>Automatic</startmode>
</service>

安装服务和启动

敲命令

# 安装服务
winsw install ./config/frpc.xml
# 启动
winsw start ./config/frpc.xml

就这样

在找到更好的工具之前,暂时先用这个。

已知问题

我在使用这个工具的时候,经常遇到开机不会自启的问题,即使查看服务管理,确认已经安装的服务是自动启动并且启用的状态,开机后还是不会自启。

不知道是啥问题,我正在寻找这个 WinSW 的代替品。

由于 scoop 是使用 PowerShell 脚本写的,当添加了比较多的 bucket 之后,search 速度会比较慢。

有大佬写了个新的搜索工具,比原版的快多了,使用的语言是我没听过的 Zig 语言。

项目地址:
https://github.com/shilangyu/scoop-search

安装方式

scoop install scoop-search

如果想将原来的
search
替换为
scoop-search
,可以在
$profile
中添加配置

Invoke-Expression (&scoop-search --hook)

和原版的对比

> scoop search curl
Results from local buckets...

Name    Version Source Binaries
----    ------- ------ --------
curl    8.6.0_2 main
curlie  1.7.2   main
gnupg1  1.4.23  main   gpgkeys_curl.exe
gow     0.8.0   main   curl.exe
grpcurl 1.8.9   main

scoop-search

x scoop-search curl
'main' bucket:
    curl (8.6.0_2)
    curlie (1.7.2)
    gnupg1 (1.4.23) --> includes 'gpgkeys_curl.exe'
    gow (0.8.0) --> includes 'curl.exe'
    grpcurl (1.8.9)

gow

在 Windows 补齐 Unix 工具链

项目地址:
https://github.com/bmatzelle/gow

Gow (Gnu On Windows) is the lightweight alternative to Cygwin. It uses a convenient Windows installer that installs about 130 extremely useful open source UNIX applications compiled as native win32 binaries. It is designed to be as small as possible, about 10 MB, as opposed to Cygwin which can run well over 100 MB depending upon options.

里面有一百多个 Unix 系的小工具,比如我常用的 nano

具体的工具列表在这:
https://github.com/bmatzelle/gow/wiki/executables_list

安装很简单,使用 scoop

scoop install gow

gsudo

Windows 下的 sudo 工具

在 Linux 下要以管理员权限执行命令只需要在最前面加上
sudo
即可,Windows 却很麻烦,需要专门打开一个管理员模式的 PowerShell 窗口。

这个小工具可以解决这个问题,使用方法和 Linux 的 sudo 命令一样。

PS:好消息是微软打算在 Windows 11 里加入原生的
sudo
命令

安装

scoop install gsudo

proxychains

命令行代理工具

也是 Linux 下常用的,没想到 Windows 也能用,
(但似乎有点奇怪的问题)

安装

scoop install proxychains

erdtree

使用 rust 开发的文件树展示工具

写文章的时候用来展示目录结构很方便,没必要目录结构都截图。

安装

scoop install erdtree

我常用的参数

erd -L 3 --dir-order last -y inverted --suppress-size -.

参数说明:

  • -L 3
    : 遍历3级目录
  • --dir-order last
    : 目录显示在前面
  • -y inverted
    :
    -y
    是设置 layout ,可选项为
    regular, inverted, flat, iflat
  • --suppress-size
    : 隐藏文件大小
  • -.
    : 显示隐藏文件

blessed-contrib

这是一个命令行的仪表盘组件库,基于 Nodejs ,实际作用似乎没有,不过就是好玩,众所周知 Linux 下有很多好玩的命令行工具,但 Windows 这边就少了很多,这个是用 Nodejs 开发的,所以是跨平台的,Windows 也可以用。

项目地址:
https://github.com/yaronn/blessed-contrib

使用方法

git clone https://github.com/yaronn/blessed-contrib.git
cd blessed-contrib
npm install
node ./examples/dashboard.js

需要安装 Nodejs 环境,稍微麻烦一点,如果有用 go 或者 rust 开发的类似工具就好了。

加入到
$profile
方便后续使用,打开配置文件

nano $profile

添加一个 PowerShell 函数

function dashboard {
	node "C:\path\to\blessed-contrib\examples\dashboard.js"
}

后续只要输入
dashboard
命令就可以了