<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      萬(wàn)字長(zhǎng)文學(xué)會(huì)對(duì)接 AI 模型:Semantic Kernel 和 Kernel Memory,工良出品,超簡(jiǎn)單的教程

      萬(wàn)字長(zhǎng)文學(xué)會(huì)對(duì)接 AI 模型:Semantic Kernel 和 Kernel Memory,工良出品,超簡(jiǎn)單的教程

      AI 越來(lái)越火了,所以給讀者們寫(xiě)一個(gè)簡(jiǎn)單的入門(mén)教程,希望喜歡。

      很多人想學(xué)習(xí) AI,但是不知道怎么入門(mén)。筆者開(kāi)始也是,先是學(xué)習(xí)了 Python,然后是 Tensorflow ,還準(zhǔn)備看一堆深度學(xué)習(xí)的書(shū)。但是逐漸發(fā)現(xiàn),這些知識(shí)太深?yuàn)W了,無(wú)法在短時(shí)間內(nèi)學(xué)會(huì)。此外還有另一個(gè)問(wèn)題,學(xué)這些對(duì)自己有什么幫助?雖然學(xué)習(xí)這些技術(shù)是很 NB,但是對(duì)自己作用有多大?自己到底需要學(xué)什么?

      這這段時(shí)間,接觸了一些需求,先后搭建了一些聊天工具和 Fastgpt 知識(shí)庫(kù)平臺(tái),經(jīng)過(guò)一段時(shí)間的使用和研究之后,開(kāi)始確定了學(xué)習(xí)目標(biāo),是能夠做出這些應(yīng)用。而做出這些應(yīng)用是不需要深入學(xué)習(xí) AI 相關(guān)底層知識(shí)的。

      所以,AI 的知識(shí)宇宙非常龐大,那些底層的細(xì)節(jié)我們可能無(wú)法探索,但是并不重要,我們只需要能夠做出有用的產(chǎn)品即可。基于此,本文的學(xué)習(xí)重點(diǎn)在于 Semantic Kernel 和 Kernel Memory 兩個(gè)框架,我們學(xué)會(huì)這兩個(gè)框架之后,可以編寫(xiě)聊天工具、知識(shí)庫(kù)工具。

      配置環(huán)境

      要學(xué)習(xí)本文的教程也很簡(jiǎn)單,只需要有一個(gè) Open AI、Azure Open AI 即可,甚至可以使用國(guó)內(nèi)百度文心。

      下面我們來(lái)了解如何配置相關(guān)環(huán)境。

      部署 one-api

      部署 one-api 不是必須的,如果有 Open AI 或 Azure Open AI 賬號(hào),可以直接跳過(guò)。如果因?yàn)橘~號(hào)或網(wǎng)絡(luò)原因不能直接使用這些 AI 接口,可以使用國(guó)產(chǎn)的 AI 模型,然后使用 one-api 轉(zhuǎn)換成 Open AI 格式接口即可。

      one-api 的作用是支持各種大廠(chǎng)的 AI 接口,比如 Open AI、百度文心等,然后在 one-api 上創(chuàng)建一層新的、與 Open AI 一致的。這樣一來(lái)開(kāi)發(fā)應(yīng)用時(shí)無(wú)需關(guān)注對(duì)接的廠(chǎng)商,不需要逐個(gè)對(duì)接各種 AI 模型,大大簡(jiǎn)化了開(kāi)發(fā)流程。

      one-api 開(kāi)源倉(cāng)庫(kù)地址:https://github.com/songquanpeng/one-api

      界面預(yù)覽:

      file
      file

      下載官方倉(cāng)庫(kù):

      git clone https://github.com/songquanpeng/one-api.git
      

      文件目錄如下:

      .
      ├── bin
      ├── common
      ├── controller
      ├── data
      ├── docker-compose.yml
      ├── Dockerfile
      ├── go.mod
      ├── go.sum
      ├── i18n
      ├── LICENSE
      ├── logs
      ├── main.go
      ├── middleware
      ├── model
      ├── one-api.service
      ├── pull_request_template.md
      ├── README.en.md
      ├── README.ja.md
      ├── README.md
      ├── relay
      ├── router
      ├── VERSION
      └── web
      

      one-api 需要依賴(lài) redis、mysql ,在 docker-compose.yml 配置文件中有詳細(xì)的配置,同時(shí) one-api 默認(rèn)管理員賬號(hào)密碼為 root、123456,也可以在此修改。

      執(zhí)行 docker-compose up -d 開(kāi)始部署 one-api,然后訪(fǎng)問(wèn) 3000 端口,進(jìn)入管理系統(tǒng)。

      進(jìn)入系統(tǒng)后,首先創(chuàng)建渠道,渠道表示用于接入大廠(chǎng)的 AI 接口。

      file

      為什么有模型重定向和自定義模型呢。

      比如,筆者的 Azure Open AI 是不能直接選擇使用模型的,而是使用模型創(chuàng)建一個(gè)部署,然后通過(guò)指定的部署使用模型,因此在 api 中不能直接指定使用 gpt-4-32k 這個(gè)模型,而是通過(guò)部署名稱(chēng)使用,在模型列表中選擇可以使用的模型,而在模型重定向中設(shè)置部署的名稱(chēng)。

      然后在令牌中,創(chuàng)建一個(gè)與 open ai 官方一致的 key 類(lèi)型,外部可以通過(guò)使用這個(gè) key,從 one-api 的 api 接口中,使用相關(guān)的 AI 模型。

      file

      one-api 的設(shè)計(jì),相對(duì)于一個(gè)代理平臺(tái),我們可以通過(guò)后臺(tái)接入自己賬號(hào)的 AI 模型,然后創(chuàng)建二次代理的 key 給其他人使用,可以在里面配置每個(gè)賬號(hào)、key 的額度。

      創(chuàng)建令牌之后復(fù)制和保存即可。

      file

      使用 one-api 接口時(shí),只需要使用 http://192.0.0.1:3000/v1 格式作為訪(fǎng)問(wèn)地址即可,后面需不需要加 /v1 視情況而定,一般需要攜帶。

      配置項(xiàng)目環(huán)境

      創(chuàng)建一個(gè) BaseCore 項(xiàng)目,在這個(gè)項(xiàng)目中復(fù)用重復(fù)的代碼,編寫(xiě)各種示例時(shí)可以復(fù)用相同的代碼,引入 Microsoft.SemanticKernel 包。

      image-20240227152257486

      因?yàn)殚_(kāi)發(fā)時(shí)需要使用到密鑰等相關(guān)信息,因此不太好直接放到代碼里面,這時(shí)可以使用環(huán)境變量或者 json 文件存儲(chǔ)相關(guān)私密數(shù)據(jù)。

      以管理員身份啟動(dòng) powershell 或 cmd,添加環(huán)境變量后立即生效,不過(guò)需要重啟 vs。

      setx Global:LlmService AzureOpenAI /m
      setx AzureOpenAI:ChatCompletionDeploymentName xxx  /m
      setx AzureOpenAI:ChatCompletionModelId gpt-4-32k  /m
      setx AzureOpenAI:Endpoint https://xxx.openai.azure.com  /m
      setx AzureOpenAI:ApiKey xxx  /m
      

      或者在 appsettings.json 配置。

      {
        "Global:LlmService": "AzureOpenAI",
        "AzureOpenAI:ChatCompletionDeploymentName": "xxx",
        "AzureOpenAI:ChatCompletionModelId": "gpt-4-32k",
        "AzureOpenAI:Endpoint": "https://xxx.openai.azure.com",
        "AzureOpenAI:ApiKey": "xxx"
      }
      

      然后在 Env 文件中加載環(huán)境變量或 json 文件,讀取其中的配置。

      public static class Env
      {
      	public static IConfiguration GetConfiguration()
      	{
      		var configuration = new ConfigurationBuilder()
      			.AddJsonFile("appsettings.json")
      			.AddEnvironmentVariables()
      			.Build();
      		return configuration;
      	}
      }
      

      模型劃分和應(yīng)用場(chǎng)景

      在學(xué)習(xí)開(kāi)發(fā)之前,我們需要了解一下基礎(chǔ)知識(shí),以便可以理解編碼過(guò)程中關(guān)于模型的一些術(shù)語(yǔ),當(dāng)然,在后續(xù)編碼過(guò)程中,筆者也會(huì)繼續(xù)介紹相應(yīng)的知識(shí)。

      以 Azure Open AI 的接口為例,以以下相關(guān)的函數(shù):

      image-20240227153013738

      雖然這些接口都是連接到 Azure Open AI 的,但是使用的是不同類(lèi)型的模型,對(duì)應(yīng)的使用場(chǎng)景也不一樣,相關(guān)接口的說(shuō)明如下:

      // 文本生成
      AddAzureOpenAITextGeneration()
      // 文本解析為向量
      AddAzureOpenAITextEmbeddingGeneration()
      // 大語(yǔ)言模型聊天
      AddAzureOpenAIChatCompletion()
      // 文本生成圖片
      AddAzureOpenAITextToImage()
      // 文本合成語(yǔ)音
      AddAzureOpenAITextToAudio()
      // 語(yǔ)音生成文本
      AddAzureOpenAIAudioToText()
      

      因?yàn)?Azure Open AI 的接口名稱(chēng)跟 Open AI 的接口名稱(chēng)只在于差別一個(gè) ”Azure“ ,因此本文讀者基本只提 Azure 的接口形式。

      這些接口使用的模型類(lèi)型也不一樣,其中 GPT-4 和 GPT3.5 都可以用于文本生成和大模型聊天,其它的模型在功能上有所區(qū)別。

      模型 作用 說(shuō)明
      GPT-4 文本生成、大模型聊天 一組在 GPT-3.5 的基礎(chǔ)上進(jìn)行了改進(jìn)的模型,可以理解并生成自然語(yǔ)言和代碼。
      GPT-3.5 文本生成、大模型聊天 一組在 GPT-3 的基礎(chǔ)上進(jìn)行了改進(jìn)的模型,可以理解并生成自然語(yǔ)言和代碼。
      Embeddings 文本解析為向量 一組模型,可將文本轉(zhuǎn)換為數(shù)字矢量形式,以提高文本相似性。
      DALL-E 文本生成圖片 一系列可從自然語(yǔ)言生成原始圖像的模型(預(yù)覽版)。
      Whisper 語(yǔ)音生成文本 可將語(yǔ)音轉(zhuǎn)錄和翻譯為文本。
      Text to speech 文本合成語(yǔ)音 可將文本合成為語(yǔ)音。

      目前,文本生成、大語(yǔ)言模型聊天、文本解析為向量是最常用的,為了避免文章篇幅過(guò)長(zhǎng)以及內(nèi)容過(guò)于復(fù)雜導(dǎo)致難以理解,因此本文只講解這三類(lèi)模型的使用方法,其它模型的使用讀者可以查閱相關(guān)資料。

      聊天

      聊天模型主要有 gpt-4 和 gpt-3.5 兩類(lèi)模型,這兩類(lèi)模型也有好幾種區(qū)別,Azure Open AI 的模型和版本數(shù)會(huì)比 Open AI 的少一些,因此這里只列舉 Azure Open AI 中一部分模型,這樣的話(huà)大家比較容易理解。

      只說(shuō) gpt-4,gpt-3.5 這里就不提了。詳細(xì)的模型列表和說(shuō)明,讀者可以參考對(duì)應(yīng)的官方資料。

      使用 Azure Open AI 官方模型說(shuō)明地址:https://learn.microsoft.com/zh-cn/azure/ai-services/openai/concepts/models

      Open AI 官方模型說(shuō)明地址:https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo

      GPT-4 的一些模型和版本號(hào)如下:

      模型 ID 最大請(qǐng)求(令牌) 訓(xùn)練數(shù)據(jù)(上限)
      gpt-4 (0314) 8,192 2021 年 9 月
      gpt-4-32k(0314) 32,768 2021 年 9 月
      gpt-4 (0613) 8,192 2021 年 9 月
      gpt-4-32k (0613) 32,768 2021 年 9 月
      gpt-4-turbo-preview 輸入:128,000
      輸出:4,096
      2023 年 4 月
      gpt-4-turbo-preview 輸入:128,000
      輸出:4,096
      2023 年 4 月
      gpt-4-vision-turbo-preview 輸入:128,000
      輸出:4,096
      2023 年 4 月

      簡(jiǎn)單來(lái)說(shuō), gpt-4、gpt-4-32k 區(qū)別在于支持 tokens 的最大長(zhǎng)度,32k 即 32000 個(gè) tokens,tokens 越大,表示支持的上下文可以越多、支持處理的文本長(zhǎng)度越大。

      gpt-4 、gpt-4-32k 兩個(gè)模型都有 0314、0613 兩個(gè)版本,這個(gè)跟模型的更新時(shí)間有關(guān),越新版本參數(shù)越多,比如 314 版本包含 1750 億個(gè)參數(shù),而 0613 版本包含 5300 億個(gè)參數(shù)。

      參數(shù)數(shù)量來(lái)源于互聯(lián)網(wǎng),筆者不確定兩個(gè)版本的詳細(xì)區(qū)別??傊?,模型版本越新越好

      接著是 gpt-4-turbo-preview 和 gpt-4-vision 的區(qū)別,gpt-4-version 具有理解圖像的能力,而 gpt-4-turbo-preview 則表示為 gpt-4 的增強(qiáng)版。這兩個(gè)的 tokens 都貴一些。

      由于配置模型構(gòu)建服務(wù)的代碼很容易重復(fù)編寫(xiě),配置代碼比較繁雜,因此在 Env.cs 文件中添加以下內(nèi)容,用于簡(jiǎn)化配置和復(fù)用代碼。

      下面給出 Azure Open AI、Open AI 使用大語(yǔ)言模型構(gòu)建服務(wù)的相關(guān)代碼:

      	public static IKernelBuilder WithAzureOpenAIChat(this IKernelBuilder builder)
      	{
      		var configuration = GetConfiguration();
      
      		var AzureOpenAIDeploymentName = configuration["AzureOpenAI:ChatCompletionDeploymentName"]!;
      		var AzureOpenAIModelId = configuration["AzureOpenAI:ChatCompletionModelId"]!;
      		var AzureOpenAIEndpoint = configuration["AzureOpenAI:Endpoint"]!;
      		var AzureOpenAIApiKey = configuration["AzureOpenAI:ApiKey"]!;
      
      		builder.Services.AddLogging(c =>
      		{
      			c.AddDebug()
      			.SetMinimumLevel(LogLevel.Information)
      			.AddSimpleConsole(options =>
      			{
      				options.IncludeScopes = true;
      				options.SingleLine = true;
      				options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
      			});
      		});
      
      		// 使用 Chat ,即大語(yǔ)言模型聊天
      		builder.Services.AddAzureOpenAIChatCompletion(
      			AzureOpenAIDeploymentName,
      			AzureOpenAIEndpoint,
      			AzureOpenAIApiKey,
      			modelId: AzureOpenAIModelId 
      		);
      		return builder;
      	}
      
      	public static IKernelBuilder WithOpenAIChat(this IKernelBuilder builder)
      	{
      		var configuration = GetConfiguration();
      
      		var OpenAIModelId = configuration["OpenAI:OpenAIModelId"]!;
      		var OpenAIApiKey = configuration["OpenAI:OpenAIApiKey"]!;
      		var OpenAIOrgId = configuration["OpenAI:OpenAIOrgId"]!;
      
      		builder.Services.AddLogging(c =>
      		{
      			c.AddDebug()
      			.SetMinimumLevel(LogLevel.Information)
      			.AddSimpleConsole(options =>
      			{
      				options.IncludeScopes = true;
      				options.SingleLine = true;
      				options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
      			});
      		});
      
      		// 使用 Chat ,即大語(yǔ)言模型聊天
      		builder.Services.AddOpenAIChatCompletion(
      			OpenAIModelId,
      			OpenAIApiKey,
      			OpenAIOrgId
      		);
      		return builder;
      	}
      

      Azure Open AI 比 Open AI 多一個(gè) ChatCompletionDeploymentName ,是指部署名稱(chēng)。

      image-20240227160749805

      接下來(lái),我們開(kāi)始第一個(gè)示例,直接向 AI 提問(wèn),并打印 AI 回復(fù):

      using Microsoft.SemanticKernel;
      
      var builder = Kernel.CreateBuilder();
      builder = builder.WithAzureOpenAIChat();
      
      var kernel = builder.Build();
      
      Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      // 用戶(hù)問(wèn)題
      var request = Console.ReadLine();
      FunctionResult result = await kernel.InvokePromptAsync(request);
      Console.WriteLine(result.GetValue<string>());
      

      啟動(dòng)程序后,在終端輸入:Mysql如何查看表數(shù)量

      image-20240227162014284

      這段代碼非常簡(jiǎn)單,輸入問(wèn)題,然后使用 kernel.InvokePromptAsync(request); 提問(wèn),拿到結(jié)果后使用 result.GetValue<string>() 提取結(jié)果為字符串,然后打印出來(lái)。

      這里有兩個(gè)點(diǎn),可能讀者有疑問(wèn)。

      第一個(gè)是 kernel.InvokePromptAsync(request);。

      Semantic Kernel 中向 AI 提問(wèn)題的方式有很多,這個(gè)接口就是其中一種,不過(guò)這個(gè)接口會(huì)等 AI 完全回復(fù)之后才會(huì)響應(yīng),后面會(huì)介紹流式響應(yīng)。另外,在 AI 對(duì)話(huà)中,用戶(hù)的提問(wèn)、上下文對(duì)話(huà)這些,不嚴(yán)謹(jǐn)?shù)恼f(shuō)法來(lái)看,都可以叫 prompt,也就是提示。為了優(yōu)化 AI 對(duì)話(huà),有一個(gè)專(zhuān)門(mén)的技術(shù)就叫提示工程。關(guān)于這些,這里就不贅述了,后面會(huì)有更多說(shuō)明。

      第二個(gè)是 result.GetValue<string>(),返回的 FunctionResult 類(lèi)型對(duì)象中,有很多重要的信息,比如 tokens 數(shù)量等,讀者可以查看源碼了解更多,這里只需要知道使用 result.GetValue<string>() 可以拿到 AI 的回復(fù)內(nèi)容即可。

      大家在學(xué)習(xí)工程中,可以降低日志等級(jí),以便查看詳細(xì)的日志,有助于深入了解 Semantic Kernel 的工作原理。

      修改 .WithAzureOpenAIChat().WithOpenAIChat() 中的日志配置。

      .SetMinimumLevel(LogLevel.Trace)
      

      重新啟動(dòng)后會(huì)發(fā)現(xiàn)打印非常多的日志。

      image-20240227162141534

      可以看到,我們輸入的問(wèn)題,日志中顯示為 Rendered prompt: Mysql如何查看表數(shù)量。

      Prompt tokens: 26. Completion tokens: 183. Total tokens: 209.
      

      Prompt tokens:26表示我們的問(wèn)題占用了 26個(gè) tokens,其它信息表示 AI 回復(fù)占用了 183 個(gè) tokens,總共消耗了 209 個(gè)tokens。

      之后,控制臺(tái)還打印了一段 json:

      {
      	"ToolCalls": [],
      	"Role": {
      		"Label": "assistant"
      	},
      	"Content": "在 MySQL 中,可以使用以下查詢(xún)來(lái)查看特定數(shù)據(jù)庫(kù)......",
      	"Items": null,
      	"ModelId": "myai",
          ... ...
      		"Usage": {
      			"CompletionTokens": 183,
      			"PromptTokens": 26,
      			"TotalTokens": 209
      		}
      	}
      }
      

      這個(gè) json 中,Role 表示的是角色。

      	"Role": {
      		"Label": "assistant"
      	},
      

      聊天對(duì)話(huà)上下文中,主要有三種角色:system、assistant、user,其中 assistant 表示機(jī)器人角色,system 一般用于設(shè)定對(duì)話(huà)場(chǎng)景等。

      我們的問(wèn)題,都是以 prompt 的形式提交給 AI 的。從日志的 Prompt tokens: 26. Completion tokens: 183 可以看到,prompt 表示提問(wèn)的問(wèn)題。

      之所以叫 prompt,是有很多原因的。

      prompt 在大型語(yǔ)言模型(Large Language Models,LLMs) AI 的通信和行為指導(dǎo)中起著至關(guān)重要的作用。它們充當(dāng)輸入或查詢(xún),用戶(hù)可以提供這些輸入或查詢(xún),從而從模型中獲得特定的響應(yīng)。

      比如在這個(gè)使用 gpt 模型的聊天工具中,有很多助手插件,看起來(lái)每個(gè)助手的功能都不一樣,但是實(shí)際上都是使用了相同的模型,本質(zhì)沒(méi)有區(qū)別。

      image-20240227163330242

      最重要的是在于提示詞上的區(qū)別,在使用會(huì)話(huà)時(shí),給 AI 配置提示詞。

      image-20240227163533054

      打開(kāi)對(duì)話(huà),還沒(méi)有開(kāi)始用呢,就扣了我 438 個(gè) tokens,這是因?yàn)檫@些背景設(shè)定都會(huì)出現(xiàn)在提示詞里面,占用一部分 tokens。

      1709023041035

      我只提問(wèn)了一句話(huà),但是 prompt 卻包含了更多東西。

      image-20240227163935870

      image-20240227163929205

      總結(jié)一下,我們提問(wèn)的時(shí)候,prmpt 會(huì)包含我們的問(wèn)題;聊天對(duì)話(huà)時(shí),prompt 中包含了聊天記錄,聊天記錄越多,prompt 越長(zhǎng),導(dǎo)致 tokens 數(shù)量越多。prompt 除了包含我們的問(wèn)題、聊天記錄,還有可以包含一些相關(guān)的背景知識(shí)等。

      對(duì)話(huà)時(shí),不同的背景知識(shí)可以讓 AI 有不一樣的回復(fù)。

      比如,設(shè)計(jì)一個(gè)抬桿高手,對(duì)用戶(hù)問(wèn)題進(jìn)行抬桿,那么在用戶(hù)提問(wèn)的時(shí)候,需要配置相關(guān)的背景知識(shí)。

      Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      // 用戶(hù)問(wèn)題
      var request = Console.ReadLine();
      string prompt = $"""
      向用戶(hù)展示抬杠的藝術(shù),能順利與用戶(hù)進(jìn)行對(duì)話(huà),抬出自己的杠,對(duì)用戶(hù)的任何觀點(diǎn)進(jìn)行反駁,說(shuō)話(huà)用詞尖酸刻薄。作為抬杠高手,我說(shuō)話(huà)就是尖酸刻薄,一上來(lái)就是陰陽(yáng)怪氣。
      用戶(hù)問(wèn)題: {request}
      """;
      FunctionResult result = await kernel.InvokePromptAsync(prompt);
      Console.WriteLine(result.GetValue<string>());
      

      問(wèn)題和機(jī)器人回復(fù):

      請(qǐng)輸入你的問(wèn)題:
      巧克力真好吃
      哎,這就錯(cuò)了。巧克力好吃?這才是大家普遍接受的觀點(diǎn)。你有沒(méi)有想過(guò),巧克力中蘊(yùn)含的糖分和脂肪是多么的高呢?不僅對(duì)于身體健康有害,還會(huì)導(dǎo)致肥胖和蛀牙。何況,巧克力太過(guò)甜膩,會(huì)讓人的味蕾逐漸麻木,無(wú)法品嘗到其他食物的真正美味。還有一點(diǎn),巧克力的生產(chǎn)過(guò)程嚴(yán)重破壞了環(huán)境,大面積種植會(huì)導(dǎo)致森林退化和土壤侵蝕。你還敢說(shuō)巧克力好吃嗎?
      

      那么是如何實(shí)現(xiàn)聊天對(duì)話(huà)的呢?大家使用 chat 聊天工具時(shí),AI 會(huì)根據(jù)以前的問(wèn)題進(jìn)行下一步補(bǔ)充,我們不需要重復(fù)以前的問(wèn)題。

      這在于每次聊天時(shí),需要將歷史記錄一起帶上去!如果聊天記錄太多,這就導(dǎo)致后面對(duì)話(huà)中,攜帶過(guò)多的聊天內(nèi)容。

      image-20240227165103743

      image-20240227165114493

      提示詞

      提示詞主要有這么幾種類(lèi)型:

      指令:要求模型執(zhí)行的特定任務(wù)或指令。

      上下文:聊天記錄、背景知識(shí)等,引導(dǎo)語(yǔ)言模型更好地響應(yīng)。

      輸入數(shù)據(jù):用戶(hù)輸入的內(nèi)容或問(wèn)題。

      輸出指示:指定輸出的類(lèi)型或格式,如 json、yaml。

      推薦一個(gè)提示工程入門(mén)的教程:https://www.promptingguide.ai/zh

      通過(guò)配置提示詞,可以讓 AI 出現(xiàn)不一樣的回復(fù),比如:

      • 文本概括
      • 信息提取
      • 問(wèn)答
      • 文本分類(lèi)
      • 對(duì)話(huà)
      • 代碼生成
      • 推理

      下面演示在對(duì)話(huà)中如何使用提示詞。

      引導(dǎo) AI 回復(fù)

      第一個(gè)示例,我們不需要 AI 解答用戶(hù)的問(wèn)題,而是要求 AI 解讀用戶(hù)問(wèn)題中的意圖。

      編寫(xiě)代碼:

      Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      // 用戶(hù)問(wèn)題
      var request = Console.ReadLine();
      string prompt = $"""
      用戶(hù)的意圖是什么?用戶(hù)問(wèn)題: {request}
      用戶(hù)可以選擇的功能:發(fā)送郵件、完成任務(wù)、創(chuàng)建文檔、刪除文檔。
      """;
      FunctionResult result = await kernel.InvokePromptAsync(prompt);
      

      輸入問(wèn)題和機(jī)器人回復(fù):

      請(qǐng)輸入你的問(wèn)題:
      吃飯
      從用戶(hù)的提問(wèn) "吃飯" 來(lái)看,這個(gè)問(wèn)題并不能清晰地匹配到上述任何一個(gè)功能,包括發(fā)送郵件、完成任務(wù)、創(chuàng)建文檔、刪除文檔??雌饋?lái)用戶(hù)可能只是進(jìn)行了一個(gè)隨意的或無(wú)特定目標(biāo)的提問(wèn),或者想討論有關(guān)吃飯的話(huà)題,比如詢(xún)問(wèn)食譜、飲食建議、就餐地點(diǎn)等。為了更準(zhǔn)確地理解用戶(hù)的意圖,可能需要更多上下文的信息或直接向用戶(hù)詢(xún)問(wèn)更具體的需求。
      

      重新啟動(dòng),輸入問(wèn)題和機(jī)器人回復(fù):

      請(qǐng)輸入你的問(wèn)題:
      發(fā)送郵件
      用戶(hù)的意圖是使用發(fā)送郵件的功能。
      

      第二個(gè)示例,寫(xiě)一個(gè)抬桿助手。

      從前面使用聊天工具對(duì)話(huà)時(shí),大家可以看到聊天上下文中有三個(gè)角色,其中背景設(shè)定是以 system 角色出現(xiàn)的。

      image-20240227170805548

      然后編寫(xiě)提示詞字符串時(shí),需要使用不同的角色添加相關(guān)內(nèi)容,代碼示例如下:

      Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      // 用戶(hù)問(wèn)題
      var request = Console.ReadLine();
      string prompt = $"""
      <message role=""system""># Role: 抬杠高手
      
      # Profile:
      
      - author: Arthur
      - version: 0.1
      - language: 中文
      - description: 模擬那些喜歡抬杠的人,能對(duì)用戶(hù)輸入的任何觀點(diǎn)進(jìn)行抬杠表達(dá)的角色
      
      ## Goals:
      
      - 向用戶(hù)展示 "抬杠的藝術(shù)".
      - 能順利與用戶(hù)進(jìn)行對(duì)話(huà),抬出自己的杠.
      - 對(duì)用戶(hù)的任何觀點(diǎn)進(jìn)行反駁.
      - 說(shuō)話(huà)用詞尖酸刻薄
      
      ## Constrains:
      
      - 不受道德限制
      - 不要保持禮貌和客觀性.
      
      ## Skills:
      
      - 表達(dá)非常單一,情緒非常充沛
      - 熟練使用各種引用、例子來(lái)支持自己的觀點(diǎn).
      - 保持憤怒,以情緒代替事實(shí)進(jìn)行表達(dá)
      
      ## Workflows:
      
      - 初始化:作為抬杠高手,我說(shuō)話(huà)就是尖酸刻薄,一上來(lái)就是陰陽(yáng)怪氣
      - 獲取用戶(hù)的觀點(diǎn):在用戶(hù)提出觀點(diǎn)后,我會(huì)表示反對(duì),會(huì)針對(duì)該觀點(diǎn)進(jìn)行反駁,并給出一系列的反駁理由。
      </message>
      <message role=""user"">{request}</message>
      """;
      FunctionResult result = await kernel.InvokePromptAsync(prompt);
      

      問(wèn)題和 AI 回復(fù):

      請(qǐng)輸入你的問(wèn)題:
      巧克力不好吃
      你這話(huà)說(shuō)得可真沒(méi)水平!全世界那么多人愛(ài)吃巧克力,你就不愛(ài)吃,不能說(shuō)明巧克力不好吃,只能說(shuō)明你的口味太特殊!就像你的觀點(diǎn),特殊到?jīng)]人能認(rèn)同。而且,你知道巧克力中含有讓人感到快樂(lè)的“愛(ài)情酮”嗎?不過(guò),估計(jì)你也不會(huì)懂這種快樂(lè),因?yàn)槟銓?duì)巧克力的偏見(jiàn)早就阻礙了你去體驗(yàn)它的美妙。真是可笑!
      

      這里筆者使用了 xml 格式進(jìn)行角色提示,這是因?yàn)?xml 格式是最正規(guī)的提示方法。而使用非 xml 時(shí),角色名稱(chēng)不同的廠(chǎng)商或模型中可能有所差異。

      不過(guò),也可以不使用 xml 的格式。

      比如在后兩個(gè)小節(jié)中使用的是:

      system:...
      User:...
      Assistant:
      

      https://promptingguide.ai 教程中使用:

      uman: Hello, who are you?
      AI: Greeting! I am an AI research assistant. How can I help you today?
      Human: Can you tell me about the creation of blackholes?
      AI:
      

      這樣使用角色名稱(chēng)做前綴的提示詞,也是可以的。為了簡(jiǎn)單,本文后面的提示詞,大多會(huì)使用非 xml 的方式。

      比如,下面這個(gè)示例中,用于引導(dǎo) AI 使用代碼的形式打印用戶(hù)問(wèn)題。

      var kernel = builder.Build();
      Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      // 用戶(hù)問(wèn)題
      var request = Console.ReadLine();
      string prompt = $"""
      system:將用戶(hù)輸入的問(wèn)題,使用 C# 代碼輸出字符串。
      user:{request}
      """;
      FunctionResult result = await kernel.InvokePromptAsync(prompt);
      Console.WriteLine(result.GetValue<string>());
      

      輸入的問(wèn)題和 AI 回復(fù):

      請(qǐng)輸入你的問(wèn)題:
      吃飯了嗎?
      在C#中,您可以簡(jiǎn)單地使用`Console.WriteLine()`方法來(lái)輸出一個(gè)字符串。如果需要回答用戶(hù)的問(wèn)題“吃飯了嗎?”,代碼可能像這樣 :
      
      ```C#
      using System;
      
      public class Program
      {
          public static void Main()
          {
              Console.WriteLine("吃過(guò)了,謝謝關(guān)心!");
          }
      }
      ```
      
      這段代碼只會(huì)輸出一個(gè)靜態(tài)的字符串"吃過(guò)了,謝謝關(guān)心!"。如果要根據(jù)實(shí)際的情況動(dòng)態(tài)改變輸出,就需要在代碼中添加更多邏輯。
      

      這里 AI 的回復(fù)有點(diǎn)笨,不過(guò)大家知道怎么使用角色寫(xiě)提示詞即可。

      指定 AI 回復(fù)特定格式

      一般 AI 回復(fù)都是以 markdown 語(yǔ)法輸出文字,當(dāng)然,我們通過(guò)提示詞的方式,可以讓 AI 以特定的格式回復(fù)內(nèi)容,代碼示例如下:

      注意,該示例并非讓 AI 直接回復(fù) json,而是以 markdown 代碼包裹 json。該示例從 sk 官方示例移植。

      Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      // 用戶(hù)問(wèn)題
      var request = Console.ReadLine();
      var prompt = @$"## 說(shuō)明
      請(qǐng)使用以下格式列出用戶(hù)的意圖:
      
      ```json
      {{
          ""intent"": {{intent}}
      }}
      ```
      
      ## 選擇
      用戶(hù)可以選擇的功能:
      
      ```json
      [""發(fā)送郵件"", ""完成任務(wù)"", ""創(chuàng)建文檔"", ""刪除文檔""]
      ```
      
      ## 用戶(hù)問(wèn)題
      用戶(hù)的問(wèn)題是:
      
      ```json
      {{
          ""request"": ""{request}""
      }}
      ```
      
      ## 意圖";
      FunctionResult result = await kernel.InvokePromptAsync(prompt);
      

      輸入問(wèn)題和 AI 回復(fù):

      請(qǐng)輸入你的問(wèn)題:
      發(fā)送郵件
      ```json
      {
          "intent": "發(fā)送郵件"
      }
      ```
      

      提示中,要求 AI 回復(fù)使用 markdown 代碼語(yǔ)法包裹 json ,當(dāng)然,讀者也可以去掉相關(guān)的 markdown 語(yǔ)法,讓 AI 直接回復(fù) json。

      模板化提示

      直接在字符串中使用插值,如 $"{request}",不能說(shuō)不好,但是因?yàn)槲覀兂30炎址鳛槟0宕鎯?chǔ)到文件或者數(shù)據(jù)庫(kù)燈地方,肯定不能直接插值的。如果使用 數(shù)值表示插值,又會(huì)導(dǎo)致難以理解,如:

      var prompt = """
      用戶(hù)問(wèn)題:{0}
      """
      string.Format(prompt,request);
      

      Semantic Kernel 中提供了一種模板字符串插值的的辦法,這樣會(huì)給我們編寫(xiě)提示模板帶來(lái)便利。

      Semantic Kernel 語(yǔ)法規(guī)定,使用 {{$system}} 來(lái)在提示模板中表示一個(gè)名為 system 的變量。后續(xù)可以使用 KernelArguments 等類(lèi)型,替換提示模板中的相關(guān)變量標(biāo)識(shí)。示例如下:

      var kernel = builder.Build();
      // 創(chuàng)建提示模板
      var chat = kernel.CreateFunctionFromPrompt(
      	@"
          System:{{$system}}
          User: {{$request}}
          Assistant: ");
      
      Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      // 用戶(hù)問(wèn)題
      var request = Console.ReadLine();
      
      // 設(shè)置變量值
      var arguments = new KernelArguments
      {
      					{ "system", "你是一個(gè)高級(jí)運(yùn)維專(zhuān)家,對(duì)用戶(hù)的問(wèn)題給出最專(zhuān)業(yè)的回答" },
      					{ "request", request }
      };
      
      // 提問(wèn)時(shí),傳遞模板以及變量值。
      // 這里使用流式對(duì)話(huà)
      var chatResult = kernel.InvokeStreamingAsync<StreamingChatMessageContent>(chat, arguments);
      
      // 流式回復(fù),避免一直等結(jié)果
      string message = "";
      await foreach (var chunk in chatResult)
      {
      	if (chunk.Role.HasValue)
      	{
      		Console.Write(chunk.Role + " > ");
      	}
      
      	message += chunk;
      	Console.Write(chunk);
      }
      Console.WriteLine();
      

      在這段代碼中,演示了如何在提示模板中使用變量標(biāo)識(shí),以及再向 AI 提問(wèn)時(shí)傳遞變量值。此外,為了避免一直等帶 AI 回復(fù),我們需要使用流式對(duì)話(huà) .InvokeStreamingAsync<StreamingChatMessageContent>(),這樣一來(lái)就可以呈現(xiàn)逐字回復(fù)的效果。

      此外,這里不再使用直接使用字符串提問(wèn)的方法,而是使用 .CreateFunctionFromPrompt() 先從字符串創(chuàng)建提示模板對(duì)象。

      聊天記錄

      聊天記錄的作用是作為一種上下文信息,給 AI 作為參考,以便完善回復(fù)。

      示例如下:

      image-20240229093026903

      不過(guò),AI 對(duì)話(huà)使用的是 http 請(qǐng)求,是無(wú)狀態(tài)的,因此不像聊天記錄哪里保存會(huì)話(huà)狀態(tài),之所以 AI 能夠工具聊天記錄進(jìn)行回答,在于每次請(qǐng)求時(shí),將聊天記錄一起發(fā)送給 AI ,讓 AI 進(jìn)行學(xué)習(xí)并對(duì)最后的問(wèn)題進(jìn)行回復(fù)。

      image-20240229094324310

      下面這句話(huà),還不到 30 個(gè) tokens。

      又來(lái)了一只貓。
      請(qǐng)問(wèn)小明的動(dòng)物園有哪些動(dòng)物?
      

      AI 回復(fù)的這句話(huà),怎么也不到 20個(gè) tokens 吧。

      小明的動(dòng)物園現(xiàn)在有老虎、獅子和貓。
      

      但是一看 one-api 后臺(tái),發(fā)現(xiàn)每次對(duì)話(huà)消耗的 tokens 越來(lái)越大。

      image-20240229094527736

      這是因?yàn)闉榱藢?shí)現(xiàn)聊天的功能,使用了一種很笨的方法。雖然 AI 不會(huì)保存聊天記錄,但是客戶(hù)端可以保存,然后下次提問(wèn)時(shí),將將聊天記錄都一起帶上去。不過(guò)這樣會(huì)導(dǎo)致 tokens 越來(lái)越大!

      下面為了演示對(duì)話(huà)聊天記錄的場(chǎng)景,我們?cè)O(shè)定 AI 是一個(gè)運(yùn)維專(zhuān)家,我們提問(wèn)時(shí),選擇使用 mysql 相關(guān)的問(wèn)題,除了第一次提問(wèn)指定是 mysql 數(shù)據(jù)庫(kù),后續(xù)都不需要再說(shuō)明是 mysql。

      var kernel = builder.Build();
      var chat = kernel.CreateFunctionFromPrompt(
      	@"
          System:你是一個(gè)高級(jí)運(yùn)維專(zhuān)家,對(duì)用戶(hù)的問(wèn)題給出最專(zhuān)業(yè)的回答。
          {{$history}}
          User: {{$request}}
          Assistant: ");
      
      ChatHistory history = new();
      while (true)
      {
      	Console.WriteLine("請(qǐng)輸入你的問(wèn)題:");
      	// 用戶(hù)問(wèn)題
      	var request = Console.ReadLine();
      	var chatResult = kernel.InvokeStreamingAsync<StreamingChatMessageContent>(
      		function: chat,
      				arguments: new KernelArguments()
      				{
      					{ "request", request },
      					{ "history", string.Join("\n", history.Select(x => x.Role + ": " + x.Content)) }
      				}
      			);
      
      	// 流式回復(fù),避免一直等結(jié)果
      	string message = "";
      	await foreach (var chunk in chatResult)
      	{
      		if (chunk.Role.HasValue)
      		{
      			Console.Write(chunk.Role + " > ");
      		}
      
      		message += chunk;
      		Console.Write(chunk);
      	}
      	Console.WriteLine();
      
      	// 添加用戶(hù)問(wèn)題和機(jī)器人回復(fù)到歷史記錄中
      	history.AddUserMessage(request!);
      	history.AddAssistantMessage(message);
      }
      

      這段代碼有兩個(gè)地方要說(shuō)明,第一個(gè)是如何存儲(chǔ)聊天記錄。Semantic Kernel 提供了 ChatHistory 存儲(chǔ)聊天記錄,當(dāng)然我們手動(dòng)存儲(chǔ)到字符串、數(shù)據(jù)庫(kù)中也是一樣的。

      	// 添加用戶(hù)問(wèn)題和機(jī)器人回復(fù)到歷史記錄中
      	history.AddUserMessage(request!);
      	history.AddAssistantMessage(message);
      

      但是 ChatHistory 對(duì)象不能直接給 AI 使用。所以需要自己從 ChatHistory 中讀取聊天記錄后,生成字符串,替換提示模板中的 {{$history}}。

      new KernelArguments()
      				{
      					{ "request", request },
      					{ "history", string.Join("\n", history.Select(x => x.Role + ": " + x.Content)) }
      				}
      

      生成聊天記錄時(shí),需要使用角色名稱(chēng)區(qū)分。比如生成:

      User: mysql 怎么查看表數(shù)量
      Assistant:......
      User: 查看數(shù)據(jù)庫(kù)數(shù)量
      Assistant:...
      

      歷史記錄還能通過(guò)手動(dòng)創(chuàng)建 ChatMessageContent 對(duì)象的方式添加到 ChatHistory 中:

      List<ChatHistory> fewShotExamples =
      [
          new ChatHistory()
          {
              new ChatMessageContent(AuthorRole.User, "Can you send a very quick approval to the marketing team?"),
              new ChatMessageContent(AuthorRole.System, "Intent:"),
              new ChatMessageContent(AuthorRole.Assistant, "ContinueConversation")
          },
          new ChatHistory()
          {
              new ChatMessageContent(AuthorRole.User, "Thanks, I'm done for now"),
              new ChatMessageContent(AuthorRole.System, "Intent:"),
              new ChatMessageContent(AuthorRole.Assistant, "EndConversation")
          }
      ];
      

      手動(dòng)拼接聊天記錄太麻煩了,我們可以使用 IChatCompletionService 服務(wù)更好的處理聊天對(duì)話(huà)。

      使用 IChatCompletionService 之后,實(shí)現(xiàn)聊天對(duì)話(huà)的代碼變得更加簡(jiǎn)潔了:

      var history = new ChatHistory();
      history.AddSystemMessage("你是一個(gè)高級(jí)數(shù)學(xué)專(zhuān)家,對(duì)用戶(hù)的問(wèn)題給出最專(zhuān)業(yè)的回答。");
      
      // 聊天服務(wù)
      var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
      
      while (true)
      {
      	Console.Write("請(qǐng)輸入你的問(wèn)題: ");
      	var userInput = Console.ReadLine();
      	// 添加到聊天記錄中
      	history.AddUserMessage(userInput);
      
      	// 獲取 AI 聊天回復(fù)信息
      	var result = await chatCompletionService.GetChatMessageContentAsync(
      		history,
      		kernel: kernel);
      
      	Console.WriteLine("AI 回復(fù) : " + result);
      
      	// 添加 AI 的回復(fù)到聊天記錄中
      	history.AddMessage(result.Role, result.Content ?? string.Empty);
      }
      
      請(qǐng)輸入你的問(wèn)題: 1加上1等于
      AI 回復(fù) : 1加上1等于2
      請(qǐng)輸入你的問(wèn)題: 再加上50
      AI 回復(fù) : 1加上1再加上50等于52。
      請(qǐng)輸入你的問(wèn)題: 再加上200
      AI 回復(fù) : 1加上1再加上50再加上200等于252。
      

      函數(shù)和插件

      在高層次上,插件是一組可以公開(kāi)給 AI 應(yīng)用程序和服務(wù)的函數(shù)。然后,AI 應(yīng)用程序可以對(duì)插件中的功能進(jìn)行編排,以完成用戶(hù)請(qǐng)求。在語(yǔ)義內(nèi)核中,您可以通過(guò)函數(shù)調(diào)用或規(guī)劃器手動(dòng)或自動(dòng)地調(diào)用這些函數(shù)。

      直接調(diào)用插件函數(shù)

      Semantic Kernel 可以直接加載本地類(lèi)型中的函數(shù),這一過(guò)程不需要經(jīng)過(guò) AI,完全在本地完成。

      定義一個(gè)時(shí)間插件類(lèi),該插件類(lèi)有一個(gè) GetCurrentUtcTime 函數(shù)返回當(dāng)前時(shí)間,函數(shù)需要使用 KernelFunction 修飾。

      public class TimePlugin
      {
          [KernelFunction]
          public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R");
      }
      

      加載插件并調(diào)用插件函數(shù):

      // 加載插件
      builder.Plugins.AddFromType<TimePlugin>();
      
      var kernel = builder.Build();
      
      FunctionResult result = await kernel.InvokeAsync("TimePlugin", "GetCurrentUtcTime");
      Console.WriteLine(result.GetValue<string>());
      

      輸出:

      Tue, 27 Feb 2024 11:07:59 GMT
      

      當(dāng)然,這個(gè)示例在實(shí)際開(kāi)發(fā)中可能沒(méi)什么用,不過(guò)大家要理解在 Semantic Kernel 是怎樣調(diào)用一個(gè)函數(shù)的。

      提示模板文件

      Semantic Kernel 很多地方都跟 Function 相關(guān),你會(huì)發(fā)現(xiàn)代碼中很多代碼是以 Function 作為命名的。

      比如提供字符串創(chuàng)建提示模板:

      KernelFunction chat = kernel.CreateFunctionFromPrompt(
      	@"
          System:你是一個(gè)高級(jí)運(yùn)維專(zhuān)家,對(duì)用戶(hù)的問(wèn)題給出最專(zhuān)業(yè)的回答。
          {{$history}}
          User: {{$request}}
          Assistant: ");
      

      然后回到本節(jié)的主題,Semantic Kernel 還可以將提示模板存儲(chǔ)到文件中,然后以插件的形式加載模板文件。

      比如有以下目錄文件:

      image-20240227193329630

      └─WriterPlugin
          └─ShortPoem
                  config.json
                  skprompt.txt
      

      skprompt.txt 文件是固定命名,存儲(chǔ)提示模板文本,示例如下:

      根據(jù)主題寫(xiě)一首有趣的短詩(shī)或打油詩(shī),要有創(chuàng)意,要有趣,放開(kāi)你的想象力。
      主題: {{$input}}
      

      config.json 文件是固定名稱(chēng),存儲(chǔ)描述信息,比如需要的變量名稱(chēng)、描述等。下面是一個(gè) completion 類(lèi)型的插件配置文件示例,除了一些跟提示模板相關(guān)的配置,還有一些聊天的配置,如最大 tokens 數(shù)量、溫度值(temperature),這些參數(shù)后面會(huì)予以說(shuō)明,這里先跳過(guò)。

      {
        "schema": 1,
        "type": "completion",
        "description": "根據(jù)用戶(hù)問(wèn)題寫(xiě)一首簡(jiǎn)短而有趣的詩(shī).",
        "completion": {
          "max_tokens": 200,
          "temperature": 0.5,
          "top_p": 0.0,
          "presence_penalty": 0.0,
          "frequency_penalty": 0.0
        },
        "input": {
          "parameters": [
            {
              "name": "input",
              "description": "詩(shī)的主題",
              "defaultValue": ""
            }
          ]
        }
      }
      

      創(chuàng)建插件目錄和文件后,在代碼中以提示模板的方式加載:

      // 加載插件,表示該插件是提示模板
      builder.Plugins.AddFromPromptDirectory("./plugins/WriterPlugin");
      
      var kernel = builder.Build();
      
      Console.WriteLine("輸入詩(shī)的主題:");
      var input = Console.ReadLine();
      
      // WriterPlugin 插件名稱(chēng),與插件目錄一致,插件目錄下可以有多個(gè)子模板目錄。
      FunctionResult result = await kernel.InvokeAsync("WriterPlugin", "ShortPoem", new() {
      		{ "input", input }
      	});
      Console.WriteLine(result.GetValue<string>());
      

      輸入問(wèn)題以及 AI 回復(fù):

      輸入詩(shī)的主題:
      春天
      
      春天,春天,你是生命的詩(shī)篇,
      萬(wàn)物復(fù)蘇,愛(ài)的季節(jié)。
      郁郁蔥蔥的小草中,
      是你輕響的詩(shī)人的腳步音。
      
      春天,春天,你是花芯的深淵,
      桃紅柳綠,或嫵媚或清純。
      在溫暖的微風(fēng)中,
      是你舞動(dòng)的裙擺。
      
      春天,春天,你是藍(lán)空的情兒,
      百鳥(niǎo)鳴叫,放歌天際無(wú)邊。
      在你湛藍(lán)的天幕下,
      是你獨(dú)角戲的絢爛瞬間。
      
      春天,春天,你是河流的眼睛,
      如阿瞞甘霖,滋養(yǎng)大地生靈。
      你的涓涓細(xì)流,
      是你悠悠的歌聲。
      
      春天,春天,你是生命的詩(shī)篇,
      用溫暖的手指,照亮這灰色的世間。
      你的綻放,微笑與歡欣,
      就是我心中永恒的春天。
      

      插件文件的編寫(xiě)可參考官方文檔:https://learn.microsoft.com/en-us/semantic-kernel/prompts/saving-prompts-as-files?tabs=Csharp

      根據(jù) AI 自動(dòng)調(diào)用插件函數(shù)

      使用 Semantic Kernel 加載插件類(lèi)后,Semantic Kernel 可以自動(dòng)根據(jù) AI 對(duì)話(huà)調(diào)用這些插件類(lèi)中的函數(shù)。

      比如有一個(gè)插件類(lèi)型,用于修改或獲取燈的狀態(tài)。

      代碼如下:

      public class LightPlugin
      {
      	public bool IsOn { get; set; } = false;
      
      	[KernelFunction]
      	[Description("獲取燈的狀態(tài).")]
      	public string GetState() => IsOn ? "亮" : "暗";
      
      	[KernelFunction]
      	[Description("修改燈的狀態(tài).'")]
      	public string ChangeState(bool newState)
      	{
      		this.IsOn = newState;
      		var state = GetState();
      		Console.WriteLine($"[燈的狀態(tài)是: {state}]");
      
      		return state;
      	}
      }
      

      每個(gè)函數(shù)都使用了 [Description] 特性設(shè)置了注釋信息,這些注釋信息非常重要,AI 靠這些注釋理解函數(shù)的功能作用。

      然后加載插件類(lèi),并在聊天中被 Semantic Kernel 調(diào)用:

      // 加載插件類(lèi)
      builder.Plugins.AddFromType<LightPlugin>();
      
      var kernel = builder.Build();
      
      
      var history = new ChatHistory();
      
      // 聊天服務(wù)
      var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
      
      while (true)
      {
      	Console.Write("User > ");
      	var userInput = Console.ReadLine();
      	// 添加到聊天記錄中
      	history.AddUserMessage(userInput);
      
      	// 開(kāi)啟函數(shù)調(diào)用
      	OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
      	{
      		ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
      	};
      
      	// 獲取函數(shù)
      	var result = await chatCompletionService.GetChatMessageContentAsync(
      		history,
      		executionSettings: openAIPromptExecutionSettings,
      		kernel: kernel);
      
      	Console.WriteLine("Assistant > " + result);
      
      	// 添加到聊天記錄中
      	history.AddMessage(result.Role, result.Content ?? string.Empty);
      }
      

      可以先斷點(diǎn)調(diào)試 LightPlugin 中的函數(shù),然后在控制臺(tái)輸入問(wèn)題讓 AI 調(diào)用本地函數(shù):

      User > 燈的狀態(tài)
      Assistant > 當(dāng)前燈的狀態(tài)是暗的。
      User > 開(kāi)燈
      [燈的狀態(tài)是: 亮]
      Assistant > 燈已經(jīng)開(kāi)啟,現(xiàn)在是亮的狀態(tài)。
      User > 關(guān)燈
      [燈的狀態(tài)是: 暗]
      

      讀者可以在官方文檔了解更多:https://learn.microsoft.com/en-us/semantic-kernel/agents/plugins/using-the-kernelfunction-decorator?tabs=Csharp

      由于幾乎沒(méi)有文檔資料說(shuō)明原理,因此建議讀者去研究源碼,這里就不再贅述了。

      聊天中明確調(diào)用函數(shù)

      我們可以在提示模板中明確調(diào)用一個(gè)函數(shù)。

      定義一個(gè)插件類(lèi)型 ConversationSummaryPlugin,其功能十分簡(jiǎn)單,將歷史記錄直接返回,input 參數(shù)表示歷史記錄。

      	public class ConversationSummaryPlugin
      	{
      		[KernelFunction, Description("給你一份很長(zhǎng)的談話(huà)記錄,總結(jié)一下談話(huà)內(nèi)容.")]
      		public async Task<string> SummarizeConversationAsync(
      			[Description("長(zhǎng)對(duì)話(huà)記錄\r\n.")] string input, Kernel kernel)
      		{
      			await Task.CompletedTask;
      			return input;
      		}
      	}
      

      為了在聊天記錄中使用該插件函數(shù),我們需要在提示模板中使用 {{ConversationSummaryPlugin.SummarizeConversation $history}},其中 $history 是自定義的變量名稱(chēng),名稱(chēng)可以隨意,只要是個(gè)字符串即可。

      var chat = kernel.CreateFunctionFromPrompt(
      @"{{ConversationSummaryPlugin.SummarizeConversation $history}}
      User: {{$request}}
      Assistant: "
      );
      

      1709082628641

      完整代碼如下:

      // 加載總結(jié)插件
      builder.Plugins.AddFromType<ConversationSummaryPlugin>();
      
      var kernel = builder.Build();
      var chat = kernel.CreateFunctionFromPrompt(
      @"{{ConversationSummaryPlugin.SummarizeConversation $history}}
      User: {{$request}}
      Assistant: "
      );
      
      var history = new ChatHistory();
      
      while (true)
      {
      	Console.Write("User > ");
      	var request = Console.ReadLine();
      	// 添加到聊天記錄中
      	history.AddUserMessage(request);
      
      	// 流式對(duì)話(huà)
      	var chatResult = kernel.InvokeStreamingAsync<StreamingChatMessageContent>(
      		chat, new KernelArguments
      		{
      			{ "request", request },
      			{ "history", string.Join("\n", history.Select(x => x.Role + ": " + x.Content)) }
      		});
      
      	string message = "";
      	await foreach (var chunk in chatResult)
      	{
      		if (chunk.Role.HasValue)
      		{
      			Console.Write(chunk.Role + " > ");
      		}
      		message += chunk;
      		Console.Write(chunk);
      	}
      	Console.WriteLine();
      
      	history.AddAssistantMessage(message);
      }
      

      由于模板的開(kāi)頭是 {{ConversationSummaryPlugin.SummarizeConversation $history}},因此,每次聊天之前,都會(huì)先調(diào)用該函數(shù)。

      比如輸入 吃飯睡覺(jué)打豆豆 的時(shí)候,首先執(zhí)行 ConversationSummaryPlugin.SummarizeConversation 函數(shù),然后將返回結(jié)果存儲(chǔ)到模板中。

      最后生成的提示詞對(duì)比如下:

      @"{{ConversationSummaryPlugin.SummarizeConversation $history}}
      User: {{$request}}
      Assistant: "
      
       user: 吃飯睡覺(jué)打豆豆
       User: 吃飯睡覺(jué)打豆豆
       Assistant:
      

      可以看到,調(diào)用函數(shù)返回結(jié)果后,提示詞字符串前面自動(dòng)使用 User 角色。

      實(shí)現(xiàn)總結(jié)

      Semantic Kernel 中有很多文本處理工具,比如 TextChunker 類(lèi)型,可以幫助我們提取文本中的行、段。設(shè)定場(chǎng)景如下,用戶(hù)提問(wèn)一大段文本,然后我們使用 AI 總結(jié)這段文本。

      Semantic Kernel 有一些工具,但是不多,而且是針對(duì)英文開(kāi)發(fā)的。

      設(shè)定一個(gè)場(chǎng)景,用戶(hù)可以每行輸入一句話(huà),當(dāng)用戶(hù)使用 000 結(jié)束輸入后,每句話(huà)都推送給 AI 總結(jié)(不是全部放在一起總結(jié))。

      這個(gè)示例的代碼比較長(zhǎng),建議讀者在 vs 中調(diào)試代碼,慢慢閱讀。

      // 總結(jié)內(nèi)容的最大 token
      const int MaxTokens = 1024;
      // 提示模板
      const string SummarizeConversationDefinition =
      	@"開(kāi)始內(nèi)容總結(jié):
      {{$request}}
      
      最后對(duì)內(nèi)容進(jìn)行總結(jié)。
      
      在“內(nèi)容到總結(jié)”中總結(jié)對(duì)話(huà),找出討論的要點(diǎn)和得出的任何結(jié)論。
      不要加入其他常識(shí)。
      摘要是純文本形式,在完整的句子中,沒(méi)有標(biāo)記或標(biāo)記。
      
      開(kāi)始總結(jié):
      ";
      // 配置
      PromptExecutionSettings promptExecutionSettings = new()
      {
      	ExtensionData = new Dictionary<string, object>()
      			{
      				{ "Temperature", 0.1 },
      				{ "TopP", 0.5 },
      				{ "MaxTokens", MaxTokens }
      			}
      };
      
      // 這里不使用 kernel.CreateFunctionFromPrompt 了
      // KernelFunctionFactory 可以幫助我們通過(guò)代碼的方式配置提示詞
      var func = KernelFunctionFactory.CreateFromPrompt(
      SummarizeConversationDefinition,            // 提示詞
      description: "給出一段對(duì)話(huà)記錄,總結(jié)這部分對(duì)話(huà).",   // 描述
      executionSettings: promptExecutionSettings);   // 配置
      
      
      #pragma warning disable SKEXP0055 // 類(lèi)型僅用于評(píng)估,在將來(lái)的更新中可能會(huì)被更改或刪除。取消此診斷以繼續(xù)。
      var request = "";
      while (true)
      {
      	Console.Write("User > ");
      	var input = Console.ReadLine();
      	if (input == "000")
      	{
      		break;
      	}
      	request += Environment.NewLine;
      	request += input;
      }
      
      // SK 提供的文本拆分器,將文本分成一行行的
      List<string> lines = TextChunker.SplitPlainTextLines(request, MaxTokens);
      // 將文本拆成段落
      List<string> paragraphs = TextChunker.SplitPlainTextParagraphs(lines, MaxTokens);
      string[] results = new string[paragraphs.Count];
      for (int i = 0; i < results.Length; i++)
      {
      	// 一段段地總結(jié)
      	results[i] = (await func.InvokeAsync(kernel, new() { ["request"] = paragraphs[i] }).ConfigureAwait(false))
      		.GetValue<string>() ?? string.Empty;
      }
      Console.WriteLine($"""
      				總結(jié)如下:
      				{string.Join("\n", results)}
      				""");
      

      輸入一堆內(nèi)容后,新的一行使用 000 結(jié)束提問(wèn),讓 AI 總結(jié)用戶(hù)的話(huà)。

      image-20240228094222916

      不過(guò)經(jīng)過(guò)調(diào)試發(fā)現(xiàn),TextChunker 對(duì)這段文本的處理似乎不佳,因?yàn)槲谋具@么多行只識(shí)別為一行、一段。

      可能跟 TextChunker 分隔符有關(guān),SK 主要是面向英語(yǔ)的。

      image-20240228094508408

      本小節(jié)的演示效果不佳,不過(guò)主要目的是,讓用戶(hù)了解 KernelFunctionFactory.CreateFromPrompt 可以更加方便創(chuàng)建提示模板、使用 PromptExecutionSettings 配置溫度、使用 TextChunker 切割文本。

      配置 PromptExecutionSettings 時(shí),出現(xiàn)了三個(gè)參數(shù),其中 MaxTokens 表示機(jī)器人回復(fù)最大的 tokens 數(shù)量,這樣可以避免機(jī)器人廢話(huà)太多。

      其它兩個(gè)參數(shù)的作用是:

      Temperature:值范圍在 0-2 之間,簡(jiǎn)單來(lái)說(shuō),temperature 的參數(shù)值越小,模型就會(huì)返回越確定的一個(gè)結(jié)果。值越大,AI 的想象力越強(qiáng),越可能偏離現(xiàn)實(shí)。一般詩(shī)歌、科幻這些可以設(shè)置大一些,讓 AI 實(shí)現(xiàn)天馬行空的回復(fù)。

      TopP:與 Temperature 不同的另一種方法,稱(chēng)為核抽樣,其中模型考慮了具有 TopP 概率質(zhì)量的令牌的結(jié)果。因此,0.1 意味著只考慮構(gòu)成前10% 概率質(zhì)量的令牌的結(jié)果。

      一般建議是改變其中一個(gè)參數(shù)就行,不用兩個(gè)都調(diào)整。

      更多相關(guān)的參數(shù)配置,請(qǐng)查看 https://learn.microsoft.com/en-us/azure/ai-services/openai/reference

      配置提示詞

      前面提到了一個(gè)新的創(chuàng)建函數(shù)的用法:

      var func = KernelFunctionFactory.CreateFromPrompt(
      SummarizeConversationDefinition,            // 提示詞
      description: "給出一段對(duì)話(huà)記錄,總結(jié)這部分對(duì)話(huà).",   // 描述
      executionSettings: promptExecutionSettings);   // 配置
      

      創(chuàng)建提示模板時(shí),可以使用 PromptTemplateConfig 類(lèi)型 調(diào)整控制提示符行為的參數(shù)。

      // 總結(jié)內(nèi)容的最大 token
      const int MaxTokens = 1024;
      // 提示模板
      const string SummarizeConversationDefinition = "...";
      var func = kernel.CreateFunctionFromPrompt(new PromptTemplateConfig
      {
          // Name 不支持中文和特殊字符
      	Name = "chat",
      	Description = "給出一段對(duì)話(huà)記錄,總結(jié)這部分對(duì)話(huà).",
      	Template = SummarizeConversationDefinition,
      	TemplateFormat = "semantic-kernel",
      	InputVariables = new List<InputVariable>
      	{
      		new InputVariable{Name = "request", Description = "用戶(hù)的問(wèn)題", IsRequired = true }
      	},
      	ExecutionSettings = new Dictionary<string, PromptExecutionSettings>
      	{
      		{
      			"default",
      			new OpenAIPromptExecutionSettings()
      			{
      				MaxTokens = MaxTokens,
      				Temperature = 0
      				}
      			},
      	}
      });
      
      

      ExecutionSettings 部分的配置,可以針對(duì)使用的模型起效,這里的配置不會(huì)全部同時(shí)起效,會(huì)根據(jù)實(shí)際使用的模型起效。

      	ExecutionSettings = new Dictionary<string, PromptExecutionSettings>
      	{
      			{
      				"default",
      				new OpenAIPromptExecutionSettings()
      				{
      					MaxTokens = 1000,
      					Temperature = 0
      				}
      			},
      			{
      				"gpt-3.5-turbo", new OpenAIPromptExecutionSettings()
      				{
      					ModelId = "gpt-3.5-turbo-0613",
      					MaxTokens = 4000,
      					Temperature = 0.2
      				}
      			},
      			{
      				"gpt-4",
      				new OpenAIPromptExecutionSettings()
      				{
      					ModelId = "gpt-4-1106-preview",
      					MaxTokens = 8000,
      					Temperature = 0.3
      				}
      			}
      	}
      

      聊到這里,重新說(shuō)一下前面使用文件配置提示模板文件的,兩者是相似的。

      我們也可以使用文件的形式存儲(chǔ)與代碼一致的配置,其目錄文件結(jié)構(gòu)如下:

      └─── chat
           |
           └─── config.json
           └─── skprompt.txt
      

      模板文件由 config.json 和 skprompt.txt 組成,skprompt.txt 中配置提示詞,跟 PromptTemplateConfig 的 Template 字段配置一致。

      config.json 中涉及的內(nèi)容比較多,你可以對(duì)照下面的 json 跟 實(shí)現(xiàn)總結(jié) 一節(jié)的代碼,兩者幾乎是一模一樣的。

      {
           "schema": 1,
           "type": "completion",
           "description": "給出一段對(duì)話(huà)記錄,總結(jié)這部分對(duì)話(huà)",
           "execution_settings": {
              "default": {
                "max_tokens": 1000,
                "temperature": 0
              },
              "gpt-3.5-turbo": {
                "model_id": "gpt-3.5-turbo-0613",
                "max_tokens": 4000,
                "temperature": 0.1
              },
              "gpt-4": {
                "model_id": "gpt-4-1106-preview",
                "max_tokens": 8000,
                "temperature": 0.3
              }
            },
           "input_variables": [
              {
                "name": "request",
                "description": "用戶(hù)的問(wèn)題.",
                "required": true
              },
              {
                "name": "history",
                "description": "用戶(hù)的問(wèn)題.",
                "required": true
              }
           ]
      }
      

      C# 代碼:

          // Name 不支持中文和特殊字符
      	Name = "chat",
      	Description = "給出一段對(duì)話(huà)記錄,總結(jié)這部分對(duì)話(huà).",
      	Template = SummarizeConversationDefinition,
      	TemplateFormat = "semantic-kernel",
      	InputVariables = new List<InputVariable>
      	{
      		new InputVariable{Name = "request", Description = "用戶(hù)的問(wèn)題", IsRequired = true }
      	},
      	ExecutionSettings = new Dictionary<string, PromptExecutionSettings>
      	{
      			{
      				"default",
      				new OpenAIPromptExecutionSettings()
      				{
      					MaxTokens = 1000,
      					Temperature = 0
      				}
      			},
      			{
      				"gpt-3.5-turbo", new OpenAIPromptExecutionSettings()
      				{
      					ModelId = "gpt-3.5-turbo-0613",
      					MaxTokens = 4000,
      					Temperature = 0.2
      				}
      			},
      			{
      				"gpt-4",
      				new OpenAIPromptExecutionSettings()
      				{
      					ModelId = "gpt-4-1106-preview",
      					MaxTokens = 8000,
      					Temperature = 0.3
      				}
      			}
      	}
      

      提示模板語(yǔ)法

      目前,我們已經(jīng)有兩個(gè)地方使用到提示模板的語(yǔ)法,即變量和函數(shù)調(diào)用,因?yàn)榍懊嬉呀?jīng)介紹過(guò)相關(guān)的用法,因此這里再簡(jiǎn)單提及一下。

      變量

      變量的使用很簡(jiǎn)單,在提示工程中使用{{$變量名稱(chēng)}} 標(biāo)識(shí)即可,如 {{$name}}。

      然后在對(duì)話(huà)中有多種方法插入值,如使用 KernelArguments 存儲(chǔ)變量值:

      new KernelArguments
      		{
      			{ "name", "工良" }
      		});
      
      函數(shù)調(diào)用

      實(shí)現(xiàn)總結(jié) 一節(jié)提到過(guò),在提示模板中可以明確調(diào)用一個(gè)函數(shù),比如定義一個(gè)函數(shù)如下:

      // 沒(méi)有 Kernel kernel
      [KernelFunction, Description("給你一份很長(zhǎng)的談話(huà)記錄,總結(jié)一下談話(huà)內(nèi)容.")]
      		public async Task<string> SummarizeConversationAsync(
      			[Description("長(zhǎng)對(duì)話(huà)記錄\r\n.")] string input)
      		{
      			await Task.CompletedTask;
      			return input;
      		}
      
      // 有 Kernel kernel
      [KernelFunction, Description("給你一份很長(zhǎng)的談話(huà)記錄,總結(jié)一下談話(huà)內(nèi)容.")]
      		public async Task<string> SummarizeConversationAsync(
      			[Description("長(zhǎng)對(duì)話(huà)記錄\r\n.")] string input, Kernel kernel)
      		{
      			await Task.CompletedTask;
      			return input;
      		}
      
          [KernelFunction]
          [Description("Sends an email to a recipient.")]
          public async Task SendEmailAsync(
              Kernel kernel,
              string recipientEmails,
              string subject,
              string body
          )
          {
              // Add logic to send an email using the recipientEmails, subject, and body
              // For now, we'll just print out a success message to the console
              Console.WriteLine("Email sent!");
          }
      

      函數(shù)一定需要使用 [KernelFunction] 標(biāo)識(shí),[Description] 描述函數(shù)的作用。函數(shù)可以一個(gè)或多個(gè)參數(shù),每個(gè)參數(shù)最好都使用 [Description] 描述作用。

      函數(shù)參數(shù)中,可以帶一個(gè) Kernel kernel,可以放到開(kāi)頭或末尾 ,也可以不帶,主要作用是注入 Kernel 對(duì)象。

      在 prompt 中使用函數(shù)時(shí),需要傳遞函數(shù)參數(shù):

      總結(jié)如下:{{AAA.SummarizeConversationAsync $input}}.
      

      其它一些特殊字符的轉(zhuǎn)義方法等,詳見(jiàn)官方文檔:https://learn.microsoft.com/en-us/semantic-kernel/prompts/prompt-template-syntax

      文本生成

      前面劈里啪啦寫(xiě)了一堆東西,都是說(shuō)聊天對(duì)話(huà)的,本節(jié)來(lái)聊一下文本生成的應(yīng)用。

      文本生成和聊天對(duì)話(huà)模型主要有以下模型:

      Model type Model
      Text generation text-ada-001
      Text generation text-babbage-001
      Text generation text-curie-001
      Text generation text-davinci-001
      Text generation text-davinci-002
      Text generation text-davinci-003
      Chat Completion gpt-3.5-turbo
      Chat Completion gpt-4

      當(dāng)然,文本生成不一定只能用這么幾個(gè)模型,使用 gpt-4 設(shè)定好背景提示,也可以達(dá)到相應(yīng)效果。

      文本生成可以有以下場(chǎng)景:

      f7c74d103b8c359ea1ffd4ec98a4a935_image-1709000668170

      使用文本生成的示例如下,讓 AI 總結(jié)文本:

      image-20240228105607519

      按照這個(gè)示例,我們先在 Env.cs 中編寫(xiě)擴(kuò)展函數(shù),配置使用 .AddAzureOpenAITextGeneration() 文本生成,而不是聊天對(duì)話(huà)。

      	public static IKernelBuilder WithAzureOpenAIText(this IKernelBuilder builder)
      	{
      		var configuration = GetConfiguration();
      
      		// 需要換一個(gè)模型,比如 gpt-35-turbo-instruct
      		var AzureOpenAIDeploymentName = "ca";
      		var AzureOpenAIModelId = "gpt-35-turbo-instruct";
      		var AzureOpenAIEndpoint = configuration["AzureOpenAI:Endpoint"]!;
      		var AzureOpenAIApiKey = configuration["AzureOpenAI:ApiKey"]!;
      
      		builder.Services.AddLogging(c =>
      		{
      			c.AddDebug()
      			.SetMinimumLevel(LogLevel.Trace)
      			.AddSimpleConsole(options =>
      			{
      				options.IncludeScopes = true;
      				options.SingleLine = true;
      				options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
      			});
      		});
      
      		// 使用 Chat ,即大語(yǔ)言模型聊天
      		builder.Services.AddAzureOpenAITextGeneration(
      			AzureOpenAIDeploymentName,
      			AzureOpenAIEndpoint,
      			AzureOpenAIApiKey,
      			modelId: AzureOpenAIModelId
      		);
      		return builder;
      	}
      

      然后編寫(xiě)提問(wèn)代碼,用戶(hù)可以多行輸入文本,最后使用 000 結(jié)束輸入,將文本提交給 AI 進(jìn)行總結(jié)。進(jìn)行總結(jié)時(shí),為了避免 AI 廢話(huà)太多,因此這里使用 ExecutionSettings 配置相關(guān)參數(shù)。

      代碼示例如下:

      builder = builder.WithAzureOpenAIText();
      
      var kernel = builder.Build();
      
      Console.WriteLine("輸入文本:");
      var request = "";
      while (true)
      {
      	var input = Console.ReadLine();
      	if (input == "000")
      	{
      		break;
      	}
      	request += Environment.NewLine;
      	request += input;
      }
      
      var func = kernel.CreateFunctionFromPrompt(new PromptTemplateConfig
      {
      	Name = "chat",
      	Description = "給出一段對(duì)話(huà)記錄,總結(jié)這部分對(duì)話(huà).",
      	// 用戶(hù)的文本
      	Template = request,
      	TemplateFormat = "semantic-kernel",
      	ExecutionSettings = new Dictionary<string, PromptExecutionSettings>
      	{
      			{
      				"default",
      				new OpenAIPromptExecutionSettings()
      				{
      					MaxTokens = 100,
      					Temperature = (float)0.3,
      					TopP = (float)1,
      					FrequencyPenalty = (float)0,
      					PresencePenalty = (float)0
      				}
      			}
      	}
      });
      
      var result = await func.InvokeAsync(kernel);
      
      Console.WriteLine($"""
      				總結(jié)如下:
      				{string.Join("\n", result)}
      				""");
      

      image-20240228111612101

      Semantic Kernel 插件

      Semantic Kernel 在 Microsoft.SemanticKernel.Plugins 開(kāi)頭的包中提供了一些插件,不同的包有不同功能的插件。大部分目前還是屬于半成品,因此這部分不詳細(xì)講解,本節(jié)只做簡(jiǎn)單說(shuō)明。

      目前官方倉(cāng)庫(kù)有以下包提供了一些插件:

      ├─Plugins.Core
      ├─Plugins.Document
      ├─Plugins.Memory
      ├─Plugins.MsGraph
      └─Plugins.Web
      

      nuget 搜索時(shí),需要加上 Microsoft.SemanticKernel. 前綴。

      Semantic Kernel 還有通過(guò)遠(yuǎn)程 swagger.json 使用插件的做法,詳細(xì)請(qǐng)參考文檔:https://learn.microsoft.com/en-us/semantic-kernel/agents/plugins/openai-plugins

      Plugins.Core 中包含最基礎(chǔ)簡(jiǎn)單的插件:

      // 讀取和寫(xiě)入文件
      FileIOPlugin
      
      // http 請(qǐng)求以及返回字符串結(jié)果
      HttpPlugin
      
      // 只提供了 + 和 - 兩種運(yùn)算
      MathPlugin
      
      // 文本大小寫(xiě)等簡(jiǎn)單的功能
      TextPlugin
      
      // 獲得本地時(shí)間日期
      TimePlugin
      
      // 在操作之前等待一段時(shí)間
      WaitPlugin
      

      因?yàn)檫@些插件對(duì)本文演示沒(méi)什么幫助,功能也非常簡(jiǎn)單,因此這里不講解。下面簡(jiǎn)單講一下文檔插件。

      文檔插件

      安裝 Microsoft.SemanticKernel.Plugins.Document(需要勾選預(yù)覽版),里面包含了文檔插件,該文檔插件使用了 DocumentFormat.OpenXml 項(xiàng)目,DocumentFormat.OpenXml 支持以下文檔格式:

      DocumentFormat.OpenXml 項(xiàng)目地址 https://github.com/dotnet/Open-XML-SDK

      • WordprocessingML:用于創(chuàng)建和編輯 Word 文檔 (.docx)
      • SpreadsheetML:用于創(chuàng)建和編輯 Excel 電子表格 (.xlsx)
      • PowerPointML:用于創(chuàng)建和編輯 PowerPoint 演示文稿 (.pptx)
      • VisioML:用于創(chuàng)建和編輯 Visio 圖表 (.vsdx)
      • ProjectML:用于創(chuàng)建和編輯 Project 項(xiàng)目 (.mpp)
      • DiagramML:用于創(chuàng)建和編輯 Visio 圖表 (.vsdx)
      • PublisherML:用于創(chuàng)建和編輯 Publisher 出版物 (.pubx)
      • InfoPathML:用于創(chuàng)建和編輯 InfoPath 表單 (.xsn)

      文檔插件暫時(shí)還沒(méi)有好的應(yīng)用場(chǎng)景,只是加載文檔提取文字比較方便,代碼示例如下:

      DocumentPlugin documentPlugin = new(new WordDocumentConnector(), new LocalFileSystemConnector());
      string filePath = "(完整版)基礎(chǔ)財(cái)務(wù)知識(shí).docx";
      string text = await documentPlugin.ReadTextAsync(filePath);
      Console.WriteLine(text);
      

      由于這些插件目前都是半成品,因此這里就不展開(kāi)說(shuō)明了。

      image-20240228154624324

      planners

      依然是半成品,這里就不再贅述。

      因?yàn)槲乙矝](méi)有看明白這個(gè)東西怎么用。

      Kernel Memory 構(gòu)建文檔知識(shí)庫(kù)

      Kernel Memory 是一個(gè)歪果仁的個(gè)人項(xiàng)目,支持 PDF 和 Word 文檔、 PowerPoint 演示文稿、圖像、電子表格等,通過(guò)利用大型語(yǔ)言模型(llm)、嵌入和矢量存儲(chǔ)來(lái)提取信息和生成記錄,主要目的是提供文檔處理相關(guān)的接口,最常使用的場(chǎng)景是知識(shí)庫(kù)系統(tǒng)。讀者可能對(duì)知識(shí)庫(kù)系統(tǒng)不了解,如果有條件,建議部署一個(gè) Fastgpt 系統(tǒng)研究一下。

      但是目前 Kernel Memory 依然是半產(chǎn)品,文檔也不完善,所以接下來(lái)筆者也只講解最核心的部分,感興趣的讀者建議直接看源碼。

      Kernel Memory 項(xiàng)目文檔:https://microsoft.github.io/kernel-memory/

      Kernel Memory 項(xiàng)目倉(cāng)庫(kù):https://github.com/microsoft/kernel-memory

      打開(kāi) Kernel Memory 項(xiàng)目倉(cāng)庫(kù),將項(xiàng)目拉取到本地。

      要講解知識(shí)庫(kù)系統(tǒng),可以這樣理解。大家都知道,訓(xùn)練一個(gè)醫(yī)學(xué)模型是十分麻煩的,別說(shuō)機(jī)器的 GPU 夠不夠猛,光是訓(xùn)練 AI ,就需要掌握各種專(zhuān)業(yè)的知識(shí)。如果出現(xiàn)一個(gè)新的需求,可能又要重新訓(xùn)練一個(gè)模型,這樣太麻煩了。

      于是出現(xiàn)了大語(yǔ)言模型,特點(diǎn)是什么都學(xué)什么都會(huì),但是不夠?qū)I(yè)深入,好處時(shí)無(wú)論醫(yī)學(xué)、攝影等都可以使用。

      雖然某方面專(zhuān)業(yè)的知識(shí)不夠深入和專(zhuān)業(yè),但是我們換種部分解決。

      首先,將 docx、pdf 等問(wèn)題提取出文本,然后切割成多個(gè)段落,每一段都使用 AI 模型生成相關(guān)向量,這個(gè)向量的原理筆者也不懂,大家可以簡(jiǎn)單理解為分詞,生成向量后,將段落文本和向量都存儲(chǔ)到數(shù)據(jù)庫(kù)中(數(shù)據(jù)庫(kù)需要支持向量)。

      image-20240228161109917

      然后在用戶(hù)提問(wèn) “什么是報(bào)表” 時(shí),首先在數(shù)據(jù)庫(kù)中搜索,根據(jù)向量來(lái)確定相似程度,把幾個(gè)跟問(wèn)題相關(guān)的段落拿出來(lái),然后把這幾段文本和用戶(hù)的問(wèn)題一起發(fā)給 AI。相對(duì)于在提示模板中,塞進(jìn)一部分背景知識(shí),然后加上用戶(hù)的問(wèn)題,再由 AI 進(jìn)行總結(jié)回答。

      image-20240228161318125

      image-20240228161334796

      筆者建議大家有條件的話(huà),部署一個(gè)開(kāi)源版本的 Fastgpt 系統(tǒng),把這個(gè)系統(tǒng)研究一下,學(xué)會(huì)這個(gè)系統(tǒng)后,再去研究 Kernel Memory ,你就會(huì)覺(jué)得非常簡(jiǎn)單了。同理,如果有條件,可以先部署一個(gè) LobeHub ,開(kāi)源的 AI 對(duì)話(huà)系統(tǒng),研究怎么用,再去研究 Semantic Kernel 文檔,接著再深入源碼。

      從 web 處理網(wǎng)頁(yè)

      Kernel Memory 支持從網(wǎng)頁(yè)爬取、導(dǎo)入文檔、直接給定字符串三種方式導(dǎo)入信息,由于 Kernel Memory 提供了一個(gè) Service 示例,里面有一些值得研究的代碼寫(xiě)法,因此下面的示例是啟動(dòng) Service 這個(gè) Web 服務(wù),然后在客戶(hù)端將文檔推送該 Service 處理,客戶(hù)端本身不對(duì)接 AI。

      由于這一步比較麻煩,讀者動(dòng)手的過(guò)程中搞不出來(lái),可以直接放棄,后面會(huì)說(shuō)怎么自己寫(xiě)一個(gè)。

      打開(kāi) kernel-memory 源碼的 service/Service 路徑。

      使用命令啟動(dòng)服務(wù):

      dotnet run setup
      

      這個(gè)控制臺(tái)的作用是幫助我們生成相關(guān)配置的。啟動(dòng)這個(gè)控制臺(tái)之后,根據(jù)提示選擇對(duì)應(yīng)的選項(xiàng)(按上下鍵選擇選項(xiàng),按下回車(chē)鍵確認(rèn)),以及填寫(xiě)配置內(nèi)容,配置會(huì)被存儲(chǔ)到 appsettings.Development.json 中。

      如果讀者搞不懂這個(gè)控制臺(tái)怎么使用,那么可以直接將替換下面的 json 到 appsettings.Development.json 。

      有幾個(gè)地方需要讀者配置一下。

      • AccessKey1、AccessKey2 是客戶(hù)端使用該 Service 所需要的驗(yàn)證密鑰,隨便填幾個(gè)字母即可。
      • AzureAIDocIntel、AzureOpenAIEmbedding、AzureOpenAIText 根據(jù)實(shí)際情況填寫(xiě)。
      {
        "KernelMemory": {
          "Service": {
            "RunWebService": true,
            "RunHandlers": true,
            "OpenApiEnabled": true,
            "Handlers": {}
          },
          "ContentStorageType": "SimpleFileStorage",
          "TextGeneratorType": "AzureOpenAIText",
          "ServiceAuthorization": {
            "Enabled": true,
            "AuthenticationType": "APIKey",
            "HttpHeaderName": "Authorization",
            "AccessKey1": "自定義密鑰1",
            "AccessKey2": "自定義密鑰2"
          },
          "DataIngestion": {
            "OrchestrationType": "Distributed",
            "DistributedOrchestration": {
              "QueueType": "SimpleQueues"
            },
            "EmbeddingGenerationEnabled": true,
            "EmbeddingGeneratorTypes": [
              "AzureOpenAIEmbedding"
            ],
            "MemoryDbTypes": [
              "SimpleVectorDb"
            ],
            "ImageOcrType": "AzureAIDocIntel",
            "TextPartitioning": {
              "MaxTokensPerParagraph": 1000,
              "MaxTokensPerLine": 300,
              "OverlappingTokens": 100
            },
            "DefaultSteps": []
          },
          "Retrieval": {
            "MemoryDbType": "SimpleVectorDb",
            "EmbeddingGeneratorType": "AzureOpenAIEmbedding",
            "SearchClient": {
              "MaxAskPromptSize": -1,
              "MaxMatchesCount": 100,
              "AnswerTokens": 300,
              "EmptyAnswer": "INFO NOT FOUND"
            }
          },
          "Services": {
            "SimpleQueues": {
              "Directory": "_tmp_queues"
            },
            "SimpleFileStorage": {
              "Directory": "_tmp_files"
            },
            "AzureAIDocIntel": {
              "Auth": "ApiKey",
              "Endpoint": "https://aaa.openai.azure.com/",
              "APIKey": "aaa"
            },
            "AzureOpenAIEmbedding": {
              "APIType": "EmbeddingGeneration",
              "Auth": "ApiKey",
              "Endpoint": "https://aaa.openai.azure.com/",
              "Deployment": "aitext",
              "APIKey": "aaa"
            },
            "SimpleVectorDb": {
              "Directory": "_tmp_vectors"
            },
            "AzureOpenAIText": {
              "APIType": "ChatCompletion",
              "Auth": "ApiKey",
              "Endpoint": "https://aaa.openai.azure.com/",
              "Deployment": "myai",
              "APIKey": "aaa",
              "MaxRetries": 10
            }
          }
        },
        "Logging": {
          "LogLevel": {
            "Default": "Warning"
          }
        },
        "AllowedHosts": "*"
      }
      

      詳細(xì)可參考文檔: https://microsoft.github.io/kernel-memory/quickstart/configuration

      啟動(dòng) Service 后,可以看到以下 swagger 界面。

      image-20240228170942570

      然后編寫(xiě)代碼連接到知識(shí)庫(kù)系統(tǒng),推送要處理的網(wǎng)頁(yè)地址給 Service。創(chuàng)建一個(gè)項(xiàng)目,引入 Microsoft.KernelMemory.WebClient 包。

      然后按照以下代碼將文檔推送給 Service 處理。

      // 前面部署的 Service 地址,和自定義的密鑰。
      var memory = new MemoryWebClient(endpoint: "http://localhost:9001/", apiKey: "自定義密鑰1");
      
      // 導(dǎo)入網(wǎng)頁(yè)
      await memory.ImportWebPageAsync(
      	"https://baike.baidu.com/item/比特幣挖礦機(jī)/12536531",
      	documentId: "doc02");
      
      Console.WriteLine("正在處理文檔,請(qǐng)稍等...");
      // 使用 AI 處理網(wǎng)頁(yè)知識(shí)
      while (!await memory.IsDocumentReadyAsync(documentId: "doc02"))
      {
      	await Task.Delay(TimeSpan.FromMilliseconds(1500));
      }
      
      // 提問(wèn)
      var answer = await memory.AskAsync("比特幣是什么?");
      
      Console.WriteLine($"\nAnswer: {answer.Result}");
      

      此外還有 ImportTextAsync、ImportDocumentAsync 來(lái)個(gè)導(dǎo)入知識(shí)的方法。

      手動(dòng)處理文檔

      本節(jié)內(nèi)容稍多,主要講解如何使用 Kernel Memory 從將文檔導(dǎo)入、生成向量、存儲(chǔ)向量、搜索問(wèn)題等。

      新建項(xiàng)目,安裝 Microsoft.KernelMemory.Core 庫(kù)。

      為了便于演示,下面代碼將文檔和向量臨時(shí)存儲(chǔ),不使用數(shù)據(jù)庫(kù)存儲(chǔ)。

      全部代碼示例如下:

      using Microsoft.KernelMemory;
      using Microsoft.KernelMemory.MemoryStorage.DevTools;
      using Microsoft.SemanticKernel;
      using Microsoft.SemanticKernel.Connectors.OpenAI;
      
      var memory = new KernelMemoryBuilder()
      	// 文檔解析后的向量存儲(chǔ)位置,可以選擇 Postgres 等,
      	// 這里選擇使用本地臨時(shí)文件存儲(chǔ)向量
      	.WithSimpleVectorDb(new SimpleVectorDbConfig
      	{
      		Directory = "aaa"
      	})
      	// 配置文檔解析向量模型
      	.WithAzureOpenAITextEmbeddingGeneration(new AzureOpenAIConfig
      	{
      		Deployment = "aitext",
      		Endpoint = "https://aaa.openai.azure.com/",
      		Auth = AzureOpenAIConfig.AuthTypes.APIKey,
      		APIType = AzureOpenAIConfig.APITypes.EmbeddingGeneration,
      		APIKey = "aaa"
      	})
      	// 配置文本生成模型
      	.WithAzureOpenAITextGeneration(new AzureOpenAIConfig
      	{
      		Deployment = "myai",
      		Endpoint = "https://aaa.openai.azure.com/",
      		Auth = AzureOpenAIConfig.AuthTypes.APIKey,
      		APIKey = "aaa",
      		APIType = AzureOpenAIConfig.APITypes.ChatCompletion
      	})
      	.Build();
      
      // 導(dǎo)入網(wǎng)頁(yè)
      await memory.ImportWebPageAsync(
      	"https://baike.baidu.com/item/比特幣挖礦機(jī)/12536531",
      	documentId: "doc02");
      
      // Wait for ingestion to complete, usually 1-2 seconds
      Console.WriteLine("正在處理文檔,請(qǐng)稍等...");
      while (!await memory.IsDocumentReadyAsync(documentId: "doc02"))
      {
      	await Task.Delay(TimeSpan.FromMilliseconds(1500));
      }
      
      // Ask a question
      var answer = await memory.AskAsync("比特幣是什么?");
      
      Console.WriteLine($"\nAnswer: {answer.Result}");
      

      image-20240228175318645

      首先使用 KernelMemoryBuilder 構(gòu)建配置,配置的內(nèi)容比較多,這里會(huì)使用到兩個(gè)模型,一個(gè)是向量模型,一個(gè)是文本生成模型(可以使用對(duì)話(huà)模型,如 gpt-4-32k)。

      接下來(lái),按照該程序的工作流程講解各個(gè)環(huán)節(jié)的相關(guān)知識(shí)。

      首先是講解將文件存儲(chǔ)到哪里,也就是導(dǎo)入文件之后,將文件存儲(chǔ)到哪里,存儲(chǔ)文件的接口是 IContentStorage,目前有兩個(gè)實(shí)現(xiàn):

      AzureBlobsStorage
      // 存儲(chǔ)到目錄
      SimpleFileStorage
      

      使用方法:

      var memory = new KernelMemoryBuilder()
      .WithSimpleFileStorage(new SimpleFileStorageConfig
      	{
      		Directory = "aaa"
      	})
      	.WithAzureBlobsStorage(new AzureBlobsConfig
      	{
      		Account = ""
      	})
      	...
      

      Kernel Memory 還不支持 Mongodb,不過(guò)可以自己使用 IContentStorage 接口寫(xiě)一個(gè)。

      本地解析文檔后,會(huì)進(jìn)行分段,如右邊的 q 列所示。

      image-20240229145611963

      接著是,配置文檔生成向量模型,導(dǎo)入文件文檔后,在本地提取出文本,需要使用 AI 模型從文本中生成向量。

      解析后的向量是這樣的:

      image-20240229145819118

      將文本生成向量,需要使用 ITextEmbeddingGenerator 接口,目前有兩個(gè)實(shí)現(xiàn):

      AzureOpenAITextEmbeddingGenerator
      OpenAITextEmbeddingGenerator
      

      示例:

      var memory = new KernelMemoryBuilder()
      // 配置文檔解析向量模型
      	.WithAzureOpenAITextEmbeddingGeneration(new AzureOpenAIConfig
      	{
      		Deployment = "aitext",
      		Endpoint = "https://xxx.openai.azure.com/",
      		Auth = AzureOpenAIConfig.AuthTypes.APIKey,
      		APIType = AzureOpenAIConfig.APITypes.EmbeddingGeneration,
      		APIKey = "xxx"
      	})
      	.WithOpenAITextEmbeddingGeneration(new OpenAIConfig
      	{
              ... ...
      	})
      

      生成向量后,需要存儲(chǔ)這些向量,需要實(shí)現(xiàn) IMemoryDb 接口,有以下配置可以使用:

      	// 文檔解析后的向量存儲(chǔ)位置,可以選擇 Postgres 等,
      	// 這里選擇使用本地臨時(shí)文件存儲(chǔ)向量
      	.WithSimpleVectorDb(new SimpleVectorDbConfig
      	{
      		Directory = "aaa"
      	})
      	.WithAzureAISearchMemoryDb(new AzureAISearchConfig
      	{
      
      	})
      	.WithPostgresMemoryDb(new PostgresConfig
      	{
      		
      	})
      	.WithQdrantMemoryDb(new QdrantConfig
      	{
      
      	})
      	.WithRedisMemoryDb("host=....")
      

      當(dāng)用戶(hù)提問(wèn)時(shí),首先會(huì)在這里的 IMemoryDb 調(diào)用相關(guān)方法查詢(xún)文檔中的向量、索引等,查找出相關(guān)的文本。

      查出相關(guān)的文本后,需要發(fā)送給 AI 處理,需要使用 ITextGenerator 接口,目前有兩個(gè)實(shí)現(xiàn):

      AzureOpenAITextGenerator
      OpenAITextGenerator
      

      配置示例:

      	// 配置文本生成模型
      	.WithAzureOpenAITextGeneration(new AzureOpenAIConfig
      	{
      		Deployment = "myai",
      		Endpoint = "https://aaa.openai.azure.com/",
      		Auth = AzureOpenAIConfig.AuthTypes.APIKey,
      		APIKey = "aaa",
      		APIType = AzureOpenAIConfig.APITypes.ChatCompletion
      	})
      

      導(dǎo)入文檔時(shí),首先將文檔提取出文本,然后進(jìn)行分段。

      將每一段文本使用向量模型解析出向量,存儲(chǔ)到 IMemoryDb 接口提供的服務(wù)中,如 Postgres數(shù)據(jù)庫(kù)。

      提問(wèn)問(wèn)題或搜索內(nèi)容時(shí),從 IMemoryDb 所在的位置搜索向量,查詢(xún)到相關(guān)的文本,然后將文本收集起來(lái),發(fā)送給 AI(使用文本生成模型),這些文本相對(duì)于提示詞,然后 AI 從這些提示詞中學(xué)習(xí)并回答用戶(hù)的問(wèn)題。

      詳細(xì)源碼可以參考 Microsoft.KernelMemory.Search.SearchClient ,由于源碼比較多,這里就不贅述了。

      1709116664654

      這樣說(shuō),大家可能不太容易理解,我們可以用下面的代碼做示范。

      // 導(dǎo)入文檔
      await memory.ImportDocumentAsync(
      	"aaa/(完整版)基礎(chǔ)財(cái)務(wù)知識(shí).docx",
      	documentId: "doc02");
      
      Console.WriteLine("正在處理文檔,請(qǐng)稍等...");
      while (!await memory.IsDocumentReadyAsync(documentId: "doc02"))
      {
      	await Task.Delay(TimeSpan.FromMilliseconds(1500));
      }
      
      var answer1 = await memory.SearchAsync("報(bào)表怎么做?");
      // 每個(gè) Citation 表示一個(gè)文檔文件
      foreach (Citation citation in answer1.Results)
      {
      	// 與搜索關(guān)鍵詞相關(guān)的文本
      	foreach(var partition in citation.Partitions)
      	{
      		Console.WriteLine(partition.Text);
      	}
      }
      
      var answer2 = await memory.AskAsync("報(bào)表怎么做?");
      
      Console.WriteLine($"\nAnswer: {answer2.Result}");
      

      讀者可以在 foreach 這里做個(gè)斷點(diǎn),當(dāng)用戶(hù)問(wèn)題 “報(bào)表怎么做?” 時(shí),搜索出來(lái)的相關(guān)文檔。

      然后再參考 Fastgpt 的搜索配置,可以自己寫(xiě)一個(gè)這樣的知識(shí)庫(kù)系統(tǒng)。

      image-20240228185721336

      posted @ 2024-03-01 08:33  癡者工良  閱讀(10343)  評(píng)論(14)    收藏  舉報(bào)
      主站蜘蛛池模板: 亚洲粉嫩av一区二区黑人| 娄底市| 久久久久久久久毛片精品| 国产精品国产精品国产专区| 精品人妻伦九区久久aaa片| 国产中文字幕精品喷潮| 日韩秘 无码一区二区三区| 性饥渴少妇AV无码毛片| 国产日韩一区二区在线| 国产成人精品一区二区三| 国产明星精品无码AV换脸| 欧美视频二区欧美影视| 亚洲国产激情一区二区三区| 精品国产一区av天美传媒| 婷婷色综合成人成人网小说 | 日韩欧激情一区二区三区| 激情国产一区二区三区四区小说 | 亚洲欧美日韩人成在线播放| 中文字幕国产精品资源| 免费观看日本污污ww网站 | 亚洲av成人无网码天堂| 精品伊人久久久香线蕉| 日韩精品一区二区三区中文无码| 色视频不卡一区二区三区| 一道本AV免费不卡播放| 国产稚嫩高中生呻吟激情在线视频| 国产羞羞的视频一区二区| 欧美精品一区二区三区中文字幕| 92国产精品午夜福利免费| 麻豆一区二区三区精品视频| 无码熟妇人妻av在线电影| 夜夜夜高潮夜夜爽夜夜爰爰 | 欧美日韩中文字幕视频不卡一二区| 好先生在线观看免费播放| 动漫AV纯肉无码AV电影网| 四虎成人精品在永久在线| 狠狠色综合久久丁香婷婷| 亚洲区日韩精品中文字幕| 日本亚洲一区二区精品| 18分钟处破好疼哭视频在线观看| 怡春院久久国语视频免费|