首页 > 其他分享 >【Semantic Kernel】4、记忆(Memory)

【Semantic Kernel】4、记忆(Memory)

时间:2023-07-08 18:23:39浏览次数:45  
标签:Kernel Semantic text await memoryCollectionName Memory var myKernel

为什么需要Memory

LLM对自然语言的理解和掌握在知识内容的解读和总结方面提供了强大的能力。
但是由于训练数据本身来自于公共领域,也就注定了无法在一些小众或者私有的领域能够足够的好的应答。
因此如何给LLM 提供足够多的信息上下文,就是如今的LLM AI应用可以充分发挥能力的地方了。

我们默认可以想到的是在提示词中提供足够的上下文信息,然而像OpenAI的模型总是有一个Max Tokens 的限制,也就意味着不可能一次性将所有的相关信息都可以放在提示词中,即便是最大的gpt-4-32k,目前也只有32,768 tokens,虽然看起来挺多的,但是相比较动则成百上千页的内部文档,或者专业资料,也不大可能将所有的内容都塞进prompt

即便说不远未来MaxTokens的上限提升到了可以轻轻松松塞下一本书了,还需要考虑的就是成本问题,以目前GPT4 的价格,0.06美元/1K tokens(32K context),光是把Prompt 塞满,不指望做出回复,一次调用成本就 1.97美元了。所以在Prompt中放置大量的信息怎么算都是不划算的。

通常情况下,我们回答一个问题,并不总是需要采用所有的信息的,例如讲某本书的某个知识点,基本不会说要一次性将全书翻一遍,然后才回答问题。除非已经将书中的内容记得滚瓜烂熟了,否则通常都是根据书中关于这个知识点相关的章节或者段落,就可以得到对应的答案了。

这种方法也常常应用于搜索领域,人们所需要的答案往往仅仅在问题所涉及的很小的范围之内。搜索引擎一直在做的事情就是找到最符合你预期的相关结果。对应的结果就是,我们总是能在搜索结果的前两页,甚至前两个条目中获得答案。

所以解决LLM有限Prompt下的上下文的基本方法也是这样,提前根据问题搜索找到相关的内容信息,再将内容信息和问题都是提供给LLM,让LLM做出对应的总结和回答。

如何找到有用的信息(Embedding)

借助于 Native Function的功能,我们可以通过一些简单的方法,例如关键词等,匹配到一些相关信息,也可以对接搜索引擎,获取一些的相关的讯息。
但是传统的方法还是传统的问题,就比如搜索引擎所采用的索引方法,也都是基于关键词,能匹配上的自然找得到,匹配不上的就很难说了。尤其是有些专用词汇无法描述清楚的时候,还有一些比较多的同义词的时候,都很难得到合适的答案。
这里就需要应用到LLM另外一个神器,Embedding
简单地说,Embedding可以将文本进行一些转化高维向量,作为向量就有了计算的可能性,就可以的进行相似性和差异性的判断。只需要计算一下两段文本之间的距离,就可以判断是否具有相似性,这种相似性是基于语义的,也就完全突破了字面上的相似性。如此以来,将所有的信息分段或者创建摘要进行转化,将问题和所有信息进行匹配,找到距离最近的或者符合距离需求的,就都是相关的信息了。这样就可以无须关心关键词是否匹配,不用煞费苦心的提取相关关键词了。
不过也有一个问题需要注意的,那就是这种向量的映射方式决定了相关内容查找的准确性是由LLM决定的,也并不是所有的时候都能找到最合适的内容。了解LLM的脾性也是使用它的重要一环。
Semantic Kernelembedding的功能封装到了Memory中,用来存储上下文信息,就好像电脑的内存一样,而LLM就像是CPU一样,我们所需要做的就是从内存中取出相关的信息交给CPU处理就好了。
了解了基本原理之后,后面就可以看看Semantic Kernel在这方面做了什么。

Memory配置

使用Memory需要注册 embedding模型,目前使用的就是 text-embedding-ada-002。同时需要为Kernel添加MemoryStore,用于存储更多的信息,这里Semantic Kernel提供了一个 VolatileMemoryStore,就是一个普通的内存存储的MemoryStore

var myKernel = Kernel.Builder
    .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", key)
    .WithOpenAITextCompletionService("text-davinci-003", key, serviceId: "LearnEnglish")
    .WithMemoryStorage(new VolatileMemoryStore())
    .Build();

信息存储

完成了基础信息的注册后,就可以往Memroy中存储信息了。

    const string MemoryCollectionName = "aboutMe";

    await myKernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
    await myKernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I currently work as a tourist operator");
    await myKernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I currently live in Seattle and have been living there since 2005");
    await myKernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
    await myKernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info5", text: "My family is from New York");

SaveInformationAsync 会将text的内容通过 embedding 模型转化为对应的文本向量,存放在的MemoryStore中。其中CollectionName如同数据库的表名,Id就是Id。

语义搜索

完成信息的存储之后,就可以用来语义搜索了。
直接使用Memory.SearchAsync方法,指定对应的Collection,同时提供相应的查询问题,查询问题也会被转化为embedding,再在MemoryStore中计算查找最相似的信息。

var questions = new[]
{
    "what is my name?",
    "where do I live?",
    "where is my family from?",
    "where have I travelled?",
    "what do I do for work?",
};
    foreach (var q in questions)
    {
        var results = myKernel.Memory.SearchAsync(MemoryCollectionName, q, 1);
        await foreach (var result in results)
        {
            Console.WriteLine(q + " " + result?.Metadata.Text);
        }
    }
 
// output
/*
what is my name? My name is Andrea
where do I live? I currently live in Seattle and have been living there since 2005
where is my family from? My family is from New York
where have I travelled? I visited France and Italy five times since 2015
what do I do for work? I currently work as a tourist operator
*/

引用存储

除了添加信息以外,还可以添加引用,像是非常有用的参考链接之类的。

const string memoryCollectionName = "SKGitHub";

    var githubFiles = new Dictionary<string, string>()
    {
        ["https://github.com/microsoft/semantic-kernel/blob/main/README.md"]
        = "README: Installation, getting started, and how to contribute",
        ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/2-running-prompts-from-file.ipynb"]
        = "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function",
        ["https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/Getting-Started-Notebook.ipynb"]
        = "Jupyter notebook describing how to get started with the Semantic Kernel",
        ["https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT"]
        = "Sample demonstrating how to create a chat skill interfacing with ChatGPT",
        ["https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs"]
        = "C# class that defines a volatile embedding store",
        ["https://github.com/microsoft/semantic-kernel/tree/main/samples/dotnet/KernelHttpServer/README.md"]
        = "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4",
        ["https://github.com/microsoft/semantic-kernel/tree/main/samples/apps/chat-summary-webapp-react/README.md"]
        = "README: README associated with a sample starter react-based chat summary webapp",
    };
    foreach (var entry in githubFiles)
    {
        await myKernel.Memory.SaveReferenceAsync(
            collection: memoryCollectionName,
            description: entry.Value,
            text: entry.Value,
            externalId: entry.Key,
            externalSourceName: "GitHub"
        );
    }

同样的,使用SearchAsync搜索就行。

string ask = "I love Jupyter notebooks, how should I get started?";
    Console.WriteLine("===========================\n" +
                        "Query: " + ask + "\n");

    var memories = myKernel.Memory.SearchAsync(memoryCollectionName, ask, limit: 5, minRelevanceScore: 0.77);
    var i = 0;
    await foreach (MemoryQueryResult memory in memories)
    {
        Console.WriteLine($"Result {++i}:");
        Console.WriteLine("  URL:     : " + memory.Metadata.Id);
        Console.WriteLine("  Title    : " + memory.Metadata.Description);
        Console.WriteLine("  ExternalSource: " + memory.Metadata.ExternalSourceName);
        Console.WriteLine("  Relevance: " + memory.Relevance);
        Console.WriteLine();
    }
//output
/*
===========================
Query: I love Jupyter notebooks, how should I get started?
 
Result 1:
  URL:     : https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/Getting-Started-Notebook.ipynb
  Title    : Jupyter notebook describing how to get started with the Semantic Kernel
  ExternalSource: GitHub
  Relevance: 0.8677381632778319
 
Result 2:
  URL:     : https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/2-running-prompts-from-file.ipynb
  Title    : Jupyter notebook describing how to pass prompts from a file to a semantic skill or function
  ExternalSource: GitHub
  Relevance: 0.8162989178955157
 
Result 3:
  URL:     : https://github.com/microsoft/semantic-kernel/blob/main/README.md
  Title    : README: Installation, getting started, and how to contribute
  ExternalSource: GitHub
  Relevance: 0.8083238591883483
*/

这里多使用了两个参数,一个是limit,用于限制返回信息的条数,只返回最相似的前几条数据,另外一个是minRelevanceScore,限制最小的相关度分数,这个取值范围在0.0 ~ 1.0 之间,1.0意味着完全匹配。

Memory持久化

VolatileMemoryStore本身也是易丢失的,往往使用到内存的场景,其中的信息都是有可能长期存储的,起码并不会即刻过期。那么将这些信息的 embedding 能够长期存储起来,也是比较划算的事情。毕竟每一次做 embedding的转化也是需要调接口,需要花钱的。Semantic Kernel库中包含了SQLite、Qdrant和CosmosDB的实现,自行扩展的话,也只需要实现 IMemoryStore 这个接口就可以了。使用sqllite持久化:
添加nuget:

	  <PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Sqlite" Version="0.17.230629.1-preview" />

替换VolatileMemoryStore:

var store = Directory.GetCurrentDirectory() + "/MemoryDB.sqlite";
    var myKernel = Kernel.Builder
    .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", key)
    .WithOpenAITextCompletionService("text-davinci-003", key, serviceId: "LearnEnglish")
    .WithMemoryStorage(await SqliteMemoryStore.ConnectAsync(store))
    .Build();

语义问答

将Memory的存储、搜索功能和语义技能相结合,就可以快速的打造一个实用的语义问答的应用了。
只需要将搜索到的相关信息内容填充到 prompt中,然后将内容和问题都抛给LLM,就可以等着得到一个满意的答案了。

var store = Directory.GetCurrentDirectory() + "/MemoryDB.sqlite";
    var myKernel = Kernel.Builder
    .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", key)
    .WithOpenAITextCompletionService("text-davinci-003", key, serviceId: "LearnEnglish")
    .WithMemoryStorage(await SqliteMemoryStore.ConnectAsync(store))
    .Build();
    const string memoryCollectionName = "aboutMe";
    var collections = await myKernel.Memory.GetCollectionsAsync();
    if (!collections.Contains(memoryCollectionName))
    {
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info1", text: "My name is Andrea");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info2", text: "I currently work as a tourist operator");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info3", text: "I currently live in Seattle and have been living there since 2005");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info5", text: "My family is from New York");
    }
    var prompt =
                    """
                    你是我的客服,你的回答应该来自我的信息,以下是关于我的信息:
                    {{ $fact }}
                    如果你不知道我的信息就回答:“我不知道”,以下是我们的聊天记录:
                    [done]
                    {{ $history }}
                    [done]
                    ++++++++
                    User: {{ $ask }}
                    ChatBot:
                    """;
    StringBuilder history = new StringBuilder();
    Console.WriteLine("请提问:", Color.YellowGreen);
    while (true)
    {
        string ask = Console.ReadLine();
        if (ask=="quit")
        {
            break;
        }

        var fact = myKernel.Memory.SearchAsync(memoryCollectionName, ask, 1).ToBlockingEnumerable().FirstOrDefault();
        var context = myKernel.CreateNewContext();
        context["history"] = history.ToString();
        context["fact"] = fact?.Metadata?.Text;
        context["ask"] = ask;
        var skill = myKernel.CreateSemanticFunction(prompt);
        var answer = skill.InvokeAsync(context).Result.ToString();
        
        history.AppendLine(ask);
        history.AppendLine(answer);
        Console.WriteLine(answer,Color.YellowGreen);
    }

优化搜索过程

由于这种场景太常见了,所以Semantic Kernel中直接提供了一个技能TextMemorySkill,通过Function调用的方式简化了搜索的过程。


    var store = Directory.GetCurrentDirectory() + "/MemoryDB.sqlite";
    var myKernel = Kernel.Builder
    .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", key)
    .WithOpenAITextCompletionService("text-davinci-003", key, serviceId: "LearnEnglish")
    .WithMemoryStorage(await SqliteMemoryStore.ConnectAsync(store))
    .Build();
    myKernel.ImportSkill(new TextMemorySkill());
    const string memoryCollectionName = "aboutMe";
    var collections = await myKernel.Memory.GetCollectionsAsync();
    if (!collections.Contains(memoryCollectionName))
    {
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info1", text: "My name is Andrea");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info2", text: "I currently work as a tourist operator");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info3", text: "I currently live in Seattle and have been living there since 2005");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
        await myKernel.Memory.SaveInformationAsync(memoryCollectionName, id: "info5", text: "My family is from New York");
    }
    var prompt =
                    """
                    你是我的客服,你的回答应该来自我的信息,以下是关于我的信息:
                    {{ recall $ask }}
                    如果你不知道我的信息就回答:“我不知道”,以下是我们的聊天记录:
                    [done]
                    {{ $history }}
                    [done]
                    ++++++++
                    User: {{ $ask }}
                    ChatBot:
                    """;
    StringBuilder history = new StringBuilder();
    Console.WriteLine("请提问:", Color.YellowGreen);
    while (true)
    {
        string ask = Console.ReadLine();
        if (ask == "quit")
        {
            break;
        }

        //var fact = myKernel.Memory.SearchAsync(memoryCollectionName, ask, 1).ToBlockingEnumerable().FirstOrDefault();
        var context = myKernel.CreateNewContext();
        context["history"] = history.ToString();
        //context["fact"] = fact?.Metadata?.Text;
        context["ask"] = ask;
        context[TextMemorySkill.CollectionParam] = memoryCollectionName;
        var skill = myKernel.CreateSemanticFunction(prompt);
        var answer = skill.InvokeAsync(context).Result.ToString();

        history.AppendLine(ask);
        history.AppendLine(answer);
        Console.WriteLine(answer, Color.YellowGreen);
    }

这里直接使用 recall 方法,将问题传给了 TextMemorySkill,搜索对应得到结果,免去了手动搜索注入得过程。

标签:Kernel,Semantic,text,await,memoryCollectionName,Memory,var,myKernel
From: https://www.cnblogs.com/fanfan-90/p/17536676.html

相关文章

  • 【Semantic Kernel】3、本机函数(Native Function)
    基础定义最基本的NativeFunction定义只需要在方法上添加SKFunction的特性即可。usingMicrosoft.SemanticKernel.SkillDefinition;usingMicrosoft.SemanticKernel.Orchestration;namespaceMySkillsDirectory;publicclassMyCSharpSkill{[SKFunction("Return......
  • linux系统报错:系统自己弹出诸如 kernel:NMI watchdog: BUG: soft lockup - CPU#2 stuc
    1、https://blog.csdn.net/weixin_41752389/article/details/120777145 内核软死锁(softlockup)Softlockup:这个bug没有让系统彻底死机,但是若干个进程(或者kernelthread)被锁死在了某个状态(一般在内核区域),很多情况下这个是由于内核锁的使用的问题。出现死锁原因1、CPU高负载时......
  • Multi-Modal Attention Network Learning for Semantic Source Code Retrieval 解读
    Multi-ModalAttentionNetworkLearningfor SemanticSourceCodeRetrieva Multi-ModalAttentionNetworkLearningfor SemanticSourceCodeRetrieval,题目意思是用于语义源代码检索的多模态注意网络学习,2019年发表于ASE的##研究什么东西Background:研究代码检索技......
  • kernel pwn入门
    LinuxKernel介绍Linux内核是Linux操作系统的核心组件,它提供了操作系统的基本功能和服务。它是一个开源软件,由LinusTorvalds在1991年开始开发,并得到了全球广泛的贡献和支持。Linux内核的主要功能包括进程管理、内存管理、文件系统、网络通信、设备驱动程序等。它负责管理......
  • linux Kernel
    ......
  • 当使用POI打开Excel文件遇到out of memory时该如何处理?
    摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。当我们开发处理Excel文件时,ApachePOI是许多人首选的工具。但是,随着需求的增加、工程复杂,在打开复杂的Excel文件的时候可能会出现一些异......
  • OutOfMemoryError: Java heap space/GC overhead limit exceeded 内存溢出问题排查
    一、背景我开发的给产线使用的工具时不时就无法登录,查看日志基本上都是内存溢出,查看实际内存基本上都占满了JVM设置的内存大小导致的现象就是SpringBoot项目无法登录,导致系统不可用。下面是我的java启动设置。javaw-Xmx6G-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPat......
  • 全志 Linux 系统启动优化 启动优化速度方式 优化启动流程 优化uboot 优化kernel等
    文章目录1概述2启动速度优化简介2.1启动流程2.2测量方法2.2.1printktime2.2.2initcall_debug2.2.3bootgraph.2.2.4bootchart2.2.5gpio+示波器.2.2.6grabserial.2.3优化方法2.3.1boot0启动优化2.3.1.1非安全启动.2.3.1.2安全启动2.3.2uboot启动优化2.3.2.1完全去......
  • 使用Redis时的vm.overcommit_memory内存分配控制
    最近在使用Redis的时候遇到了linux系统中的vm.overcommit_memory参数设置,对此不是很了解,于是研究了一下,有了本文。 ===================================== 一个尝试,如何在内存中申请空间:>>>100000*400000*8/1024/1024/1024298.0232238769531 实际代码:importnumpyas......
  • Linux memory读写约束readl、readl_relaxed、writel、writel_relaxed区别
    内存类型和属性|ARMMemorytypesandattributesflowchartLR1(ArmMemoryType)-->2(NormalMemory)1-->3(DeviceMemory)2-->4(Shareable)2-->6(Cacheabilityattributes)4-->7(InnerShareable)4-->8(OuterSh......