目录
万字长文学会对接AI模型:SemanticKernel和KernelMemory,工良出品,超简单的教程
从web处理网页
手动处理文档
部署one-api
配置项目环境
模型划分和应用场景
聊天
函数和插件
文本生成
SemanticKernel插件
planners
提示词
引导AI回复
指定AI回复特定格式
模板化提示
聊天记录
变量
函数调用
直接调用插件函数
提示模板文件
根据AI自动调用插件函数
聊天中明确调用函数
实现总结
配置提示词
提示模板语法
文档插件
配置环境
KernelMemory构建文档知识库
AI越来越火了,所以给读者们写一个简单的入门教程,希望喜欢。
很多人想学习AI,但是不知道怎么入门。笔者开始也是,先是学习了Python,然后是Tensorflow,还准备看一堆深度学习的书。但是逐渐发现,这些知识太深奥了,无法在短时间内学会。此外还有另一个问题,学这些对自己有什么帮助?虽然学习这些技术是很NB,但是对自己作用有多大?自己到底需要学什么?
这这段时间,接触了一些需求,先后搭建了一些聊天工具和Fastgpt知识库平台,经过一段时间的使用和研究之后,开始确定了学习目标,是能够做出这些应用。而做出这些应用是不需要深入学习AI相关底层知识的。
所以,AI的知识宇宙非常庞大,那些底层的细节我们可能无法探索,但是并不重要,我们只需要能够做出有用的产品即可。基于此,本文的学习重点在于SemanticKernel和KernelMemory两个框架,我们学会这两个框架之后,可以编写聊天工具、知识库工具。
配置环境要学习本文的教程也很简单,只需要有一个OpenAI、AzureOpenAI即可,甚至可以使用国内百度文心。
下面我们来了解如何配置相关环境。
部署one-api部署one-api不是必须的,如果有OpenAI或AzureOpenAI账号,可以直接跳过。如果因为账号或网络原因不能直接使用这些AI接口,可以使用国产的AI模型,然后使用one-api转换成OpenAI格式接口即可。
one-api开源仓库地址:
界面预览:


下载官方仓库:
gitclone
文件目录如下:
.
├──bin
├──common
├──controller
├──data
├──
├──Dockerfile
├──
├──
├──i18n
├──LICENSE
├──logs
├──
├──middleware
├──model
├──
├──pull_request_
├──
├──
├──
├──relay
├──router
├──VERSION
└──web
one-api需要依赖redis、mysql,在配置文件中有详细的配置,同时one-api默认管理员账号密码为root、123456,也可以在此修改。
执行docker-composeup-d开始部署one-api,然后访问3000端口,进入管理系统。
进入系统后,首先创建渠道,渠道表示用于接入大厂的AI接口。

为什么有模型重定向和自定义模型呢。
比如,笔者的AzureOpenAI是不能直接选择使用模型的,而是使用模型创建一个部署,然后通过指定的部署使用模型,因此在api中不能直接指定使用gpt-4-32k这个模型,而是通过部署名称使用,在模型列表中选择可以使用的模型,而在模型重定向中设置部署的名称。
然后在令牌中,创建一个与openai官方一致的key类型,外部可以通过使用这个key,从one-api的api接口中,使用相关的AI模型。

one-api的设计,相对于一个代理平台,我们可以通过后台接入自己账号的AI模型,然后创建二次代理的key给其他人使用,可以在里面配置每个账号、key的额度。
创建令牌之后复制和保存即可。

使用one-api接口时,只需要使用格式作为访问地址即可,后面需不需要加/v1视情况而定,一般需要携带。
配置项目环境创建一个BaseCore项目,在这个项目中复用重复的代码,编写各种示例时可以复用相同的代码,引入包。

因为开发时需要使用到密钥等相关信息,因此不太好直接放到代码里面,这时可以使用环境变量或者json文件存储相关私密数据。
以管理员身份启动powershell或cmd,添加环境变量后立即生效,不过需要重启vs。
setxGlobal:LlmServiceAzureOpenAI/m
setxAzureOpenAI:ChatCompletionDeploymentNamexxx/m
setxAzureOpenAI:ChatCompletionModelIdgpt-4-32k/m
setxAzureOpenAI:point
setxAzureOpenAI:ApiKeyxxx/m
或者在配置。
{
"Global:LlmService":"AzureOpenAI",
"AzureOpenAI:ChatCompletionDeploymentName":"xxx",
"AzureOpenAI:ChatCompletionModelId":"gpt-4-32k",
"AzureOpenAI:point":"",
"AzureOpenAI:ApiKey":"xxx"
}
然后在Env文件中加载环境变量或json文件,读取其中的配置。
publicstaticclassEnv模型划分和应用场景
{
publicstaticIConfigurationGetConfiguration()
{
varconfiguration=newConfigurationBuilder()
.AddJsonFile("")
.AddEnvironmentVariables()
.Build();
returnconfiguration;
}
}
在学习开发之前,我们需要了解一下基础知识,以便可以理解编码过程中关于模型的一些术语,当然,在后续编码过程中,笔者也会继续介绍相应的知识。
以AzureOpenAI的接口为例,以以下相关的函数:

虽然这些接口都是连接到AzureOpenAI的,但是使用的是不同类型的模型,对应的使用场景也不一样,相关接口的说明如下:
//文本生成
AddAzureOpenAITextGeneration()
//文本解析为向量
AddAzureOpenAITextEmbeddingGeneration()
//大语言模型聊天
AddAzureOpenAIChatCompletion()
//文本生成图片
AddAzureOpenAITextToImage()
//文本合成语音
AddAzureOpenAITextToAudio()
//语音生成文本
AddAzureOpenAIAudioToText()
这些接口使用的模型类型也不一样,其中GPT-4和都可以用于文本生成和大模型聊天,其它的模型在功能上有所区别。
模型作用说明GPT-4文本生成、大模型聊天一组在的基础上进行了改进的模型,可以理解并生成自然语言和代码。文本生成、大模型聊天一组在GPT-3的基础上进行了改进的模型,可以理解并生成自然语言和代码。Embeddings文本解析为向量一组模型,可将文本转换为数字矢量形式,以提高文本相似性。DALL-E文本生成图片一系列可从自然语言生成原始图像的模型(预览版)。Whisper语音生成文本可将语音转录和翻译为文本。Texttospeech文本合成语音可将文本合成为语音。目前,文本生成、大语言模型聊天、文本解析为向量是最常用的,为了避免文章篇幅过长以及内容过于复杂导致难以理解,因此本文只讲解这三类模型的使用方法,其它模型的使用读者可以查阅相关资料。
聊天聊天模型主要有gpt-4和两类模型,这两类模型也有好几种区别,AzureOpenAI的模型和版本数会比OpenAI的少一些,因此这里只列举AzureOpenAI中一部分模型,这样的话大家比较容易理解。
GPT-4的一些模型和版本号如下:
模型ID最大请求(令牌)训练数据(上限)gpt-4(0314)8,1922021年9月gpt-4-32k(0314)32,7682021年9月gpt-4(0613)8,1922021年9月gpt-4-32k(0613)32,7682021年9月gpt-4-turbo-preview输入:128,000输出:4,0962023年4月gpt-4-turbo-preview输入:128,000
输出:4,0962023年4月gpt-4-vision-turbo-preview输入:128,000
输出:4,0962023年4月
简单来说,gpt-4、gpt-4-32k区别在于支持tokens的最大长度,32k即32000个tokens,tokens越大,表示支持的上下文可以越多、支持处理的文本长度越大。
gpt-4、gpt-4-32k两个模型都有0314、0613两个版本,这个跟模型的更新时间有关,越新版本参数越多,比如314版本包含1750亿个参数,而0613版本包含5300亿个参数。
接着是gpt-4-turbo-preview和gpt-4-vision的区别,gpt-4-version具有理解图像的能力,而gpt-4-turbo-preview则表示为gpt-4的增强版。这两个的tokens都贵一些。
由于配置模型构建服务的代码很容易重复编写,配置代码比较繁杂,因此在文件中添加以下内容,用于简化配置和复用代码。
下面给出AzureOpenAI、OpenAI使用大语言模型构建服务的相关代码:
publicstaticIKernelBuilderWithAzureOpenAIChat(thisIKernelBuilderbuilder)
{
varconfiguration=GetConfiguration();
varAzureOpenAIDeploymentName=configuration["AzureOpenAI:ChatCompletionDeploymentName"]!;
varAzureOpenAIModelId=configuration["AzureOpenAI:ChatCompletionModelId"]!;
varAzureOpenAIpoint=configuration["AzureOpenAI:point"]!;
varAzureOpenAIApiKey=configuration["AzureOpenAI:ApiKey"]!;
(c=
{
()
.SetMinimumLevel()
.AddSimpleConsole(options=
{
=true;
=true;
="yyyy-MM-ddHH:mm:ss";
});
});
//使用Chat,即大语言模型聊天
(
AzureOpenAIDeploymentName,
AzureOpenAIpoint,
AzureOpenAIApiKey,
modelId:AzureOpenAIModelId
);
returnbuilder;
}
publicstaticIKernelBuilderWithOpenAIChat(thisIKernelBuilderbuilder)
{
varconfiguration=GetConfiguration();
varOpenAIModelId=configuration["OpenAI:OpenAIModelId"]!;
varOpenAIApiKey=configuration["OpenAI:OpenAIApiKey"]!;
varOpenAIOrgId=configuration["OpenAI:OpenAIOrgId"]!;
(c=
{
()
.SetMinimumLevel()
.AddSimpleConsole(options=
{
=true;
=true;
="yyyy-MM-ddHH:mm:ss";
});
});
//使用Chat,即大语言模型聊天
(
OpenAIModelId,
OpenAIApiKey,
OpenAIOrgId
);
returnbuilder;
}
AzureOpenAI比OpenAI多一个ChatCompletionDeploymentName,是指部署名称。

接下来,我们开始第一个示例,直接向AI提问,并打印AI回复:
;
varbuilder=();
builder=();
varkernel=();
("请输入你的问题:");
//用户问题
varrequest=();
FunctionResultresult=(request);
(());
启动程序后,在终端输入:Mysql如何查看表数量

这段代码非常简单,输入问题,然后使用(request);提问,拿到结果后使用()提取结果为字符串,然后打印出来。
这里有两个点,可能读者有疑问。
第一个是(request);。
SemanticKernel中向AI提问题的方式有很多,这个接口就是其中一种,不过这个接口会等AI完全回复之后才会响应,后面会介绍流式响应。另外,在AI对话中,用户的提问、上下文对话这些,不严谨的说法来看,都可以叫prompt,也就是提示。为了优化AI对话,有一个专门的技术就叫提示工程。关于这些,这里就不赘述了,后面会有更多说明。
第二个是(),返回的FunctionResult类型对象中,有很多重要的信息,比如tokens数量等,读者可以查看源码了解更多,这里只需要知道使用()可以拿到AI的回复内容即可。
大家在学习工程中,可以降低日志等级,以便查看详细的日志,有助于深入了解SemanticKernel的工作原理。
修改.WithAzureOpenAIChat()或.WithOpenAIChat()中的日志配置。
.SetMinimumLevel()
重新启动后会发现打印非常多的日志。

可以看到,我们输入的问题,日志中显示为Reredprompt:Mysql如何查看表数量。
Prompttokens:26.Completiontokens:183.Totaltokens:209.
Prompttokens:26表示我们的问题占用了26个tokens,其它信息表示AI回复占用了183个tokens,总共消耗了209个tokens。
之后,控制台还打印了一段json:
{
"ToolCalls":[],
"Role":{
"Label":"assistant"
},
"Content":"在MySQL中,可以使用以下查询来查看特定数据库",
"Items":,
"ModelId":"myai",
"Usage":{
"CompletionTokens":183,
"PromptTokens":26,
"TotalTokens":209
}
}
}
这个json中,Role表示的是角色。
"Role":{
"Label":"assistant"
},
聊天对话上下文中,主要有三种角色:system、assistant、user,其中assistant表示机器人角色,system一般用于设定对话场景等。
我们的问题,都是以prompt的形式提交给AI的。从日志的Prompttokens:26.Completiontokens:183可以看到,prompt表示提问的问题。
之所以叫prompt,是有很多原因的。
prompt在大型语言模型(LargeLanguageModels,LLMs)AI的通信和行为指导中起着至关重要的作用。它们充当输入或查询,用户可以提供这些输入或查询,从而从模型中获得特定的响应。
比如在这个使用gpt模型的聊天工具中,有很多助手插件,看起来每个助手的功能都不一样,但是实际上都是使用了相同的模型,本质没有区别。

最重要的是在于提示词上的区别,在使用会话时,给AI配置提示词。

打开对话,还没有开始用呢,就扣了我438个tokens,这是因为这些背景设定都会出现在提示词里面,占用一部分tokens。

我只提问了一句话,但是prompt却包含了更多东西。


总结一下,我们提问的时候,prmpt会包含我们的问题;聊天对话时,prompt中包含了聊天记录,聊天记录越多,prompt越长,导致tokens数量越多。prompt除了包含我们的问题、聊天记录,还有可以包含一些相关的背景知识等。
对话时,不同的背景知识可以让AI有不一样的回复。
比如,设计一个抬杆高手,对用户问题进行抬杆,那么在用户提问的时候,需要配置相关的背景知识。
("请输入你的问题:");
//用户问题
varrequest=();
stringprompt=$"""
向用户展示抬杠的艺术,能顺利与用户进行对话,抬出自己的杠,对用户的任何观点进行反驳,说话用词尖酸刻薄。作为抬杠高手,我说话就是尖酸刻薄,一上来就是阴阳怪气。
用户问题:{request}
""";
FunctionResultresult=(prompt);
(());
问题和机器人回复:
请输入你的问题:
巧克力真好吃
哎,这就错了。巧克力好吃?这才是大家普遍接受的观点。你有没有想过,巧克力中蕴含的糖分和脂肪是多么的高呢?不仅对于身体健康有害,还会导致肥胖和蛀牙。何况,巧克力太过甜腻,会让人的味蕾逐渐麻木,无法品尝到其他食物的真正美味。还有一点,巧克力的生产过程严重破坏了环境,大面积种植会导致森林退化和土壤侵蚀。你还敢说巧克力好吃吗?
那么是如何实现聊天对话的呢?大家使用chat聊天工具时,AI会根据以前的问题进行下一步补充,我们不需要重复以前的问题。
这在于每次聊天时,需要将历史记录一起带上去!如果聊天记录太多,这就导致后面对话中,携带过多的聊天内容。


提示词主要有这么几种类型:
指令:要求模型执行的特定任务或指令。
上下文:聊天记录、背景知识等,引导语言模型更好地响应。
输入数据:用户输入的内容或问题。
输出指示:指定输出的类型或格式,如json、yaml。
通过配置提示词,可以让AI出现不一样的回复,比如:
文本概括
信息提取
问答
文本分类
对话
代码生成
推理
下面演示在对话中如何使用提示词。
引导AI回复第一个示例,我们不需要AI解答用户的问题,而是要求AI解读用户问题中的意图。
编写代码:
("请输入你的问题:");
//用户问题
varrequest=();
stringprompt=$"""
用户的意图是什么?用户问题:{request}
用户可以选择的功能:发送邮件、完成任务、创建文档、删除文档。
""";
FunctionResultresult=(prompt);
输入问题和机器人回复:
请输入你的问题:
吃饭
从用户的提问"吃饭"来看,这个问题并不能清晰地匹配到上述任何一个功能,包括发送邮件、完成任务、创建文档、删除文档。看起来用户可能只是进行了一个随意的或无特定目标的提问,或者想讨论有关吃饭的话题,比如询问食谱、饮食建议、就餐地点等。为了更准确地理解用户的意图,可能需要更多上下文的信息或直接向用户询问更具体的需求。
重新启动,输入问题和机器人回复:
请输入你的问题:
发送邮件
用户的意图是使用发送邮件的功能。
第二个示例,写一个抬杆助手。
从前面使用聊天工具对话时,大家可以看到聊天上下文中有三个角色,其中背景设定是以system角色出现的。

然后编写提示词字符串时,需要使用不同的角色添加相关内容,代码示例如下:
("请输入你的问题:");
//用户问题
varrequest=();
stringprompt=$"""
messagerole=""system""Profile:
-author:Arthur
-version:0.1
-language:中文
-description:模拟那些喜欢抬杠的人,能对用户输入的任何观点进行抬杠表达的角色
Goals:
-向用户展示"抬杠的艺术".
-能顺利与用户进行对话,抬出自己的杠.
-对用户的任何观点进行反驳.
-说话用词尖酸刻薄
Constrains:
-不受道德限制
-不要保持礼貌和客观性.
Skills:
-表达非常单一,情绪非常充沛
-熟练使用各种引用、例子来支持自己的观点.
-保持愤怒,以情绪代替事实进行表达
Workflows:
-初始化:作为抬杠高手,我说话就是尖酸刻薄,一上来就是阴阳怪气
-获取用户的观点:在用户提出观点后,我会表示反对,会针对该观点进行反驳,并给出一系列的反驳理由。
/message
messagerole=""user""{request}/message
""";
FunctionResultresult=(prompt);
问题和AI回复:
请输入你的问题:
巧克力不好吃
你这话说得可真没水平!全世界那么多人爱吃巧克力,你就不爱吃,不能说明巧克力不好吃,只能说明你的口味太特殊!就像你的观点,特殊到没人能认同。而且,你知道巧克力中含有让人感到快乐的“爱情酮”吗?不过,估计你也不会懂这种快乐,因为你对巧克力的偏见早就阻碍了你去体验它的美妙。真是可笑!
这里笔者使用了xml格式进行角色提示,这是因为xml格式是最正规的提示方法。而使用非xml时,角色名称不同的厂商或模型中可能有所差异。
不过,也可以不使用xml的格式。
比如在后两个小节中使用的是:
system:
User:
Assistant:
在:
uman:Hello,whoareyou?
AI:Greeting!?
Human:Canyoutellmeaboutthecreationofblackholes?
AI:
这样使用角色名称做前缀的提示词,也是可以的。为了简单,本文后面的提示词,大多会使用非xml的方式。
比如,下面这个示例中,用于引导AI使用代码的形式打印用户问题。
varkernel=();
("请输入你的问题:");
//用户问题
varrequest=();
stringprompt=$"""
system:将用户输入的问题,使用C中,您可以简单地使用`()`方法来输出一个字符串。如果需要回答用户的问题“吃饭了吗?”,代码可能像这样:
```CpragmawarningdisableSKEXP0055//类型仅用于评估,在将来的更新中可能会被更改或删除。取消此诊断以继续。
varrequest="";
while(true)
{
("User");
varinput=();
if(input=="000")
{
break;
}
request+=;
request+=input;
}
//SK提供的文本拆分器,将文本分成一行行的
Liststringlines=(request,MaxTokens);
//将文本拆成段落
Liststringparagraphs=(lines,MaxTokens);
string[]results=newstring[];
for(inti=0;;i++)
{
//一段段地总结
results[i]=((kernel,new(){["request"]=paragraphs[i]}).ConfigureAwait(false))
.GetValuestring()??;
}
($"""
总结如下:
{("\n",results)}
""");
输入一堆内容后,新的一行使用000结束提问,让AI总结用户的话。

不过经过调试发现,TextChunker对这段文本的处理似乎不佳,因为文本这么多行只识别为一行、一段。
可能跟TextChunker分隔符有关,SK主要是面向英语的。

本小节的演示效果不佳,不过主要目的是,让用户了解可以更加方便创建提示模板、使用PromptExecutionSettings配置温度、使用TextChunker切割文本。
配置PromptExecutionSettings时,出现了三个参数,其中MaxTokens表示机器人回复最大的tokens数量,这样可以避免机器人废话太多。
其它两个参数的作用是:
Temperature:值范围在0-2之间,简单来说,temperature的参数值越小,模型就会返回越确定的一个结果。值越大,AI的想象力越强,越可能偏离现实。一般诗歌、科幻这些可以设置大一些,让AI实现天马行空的回复。
TopP:与Temperature不同的另一种方法,称为核抽样,其中模型考虑了具有TopP概率质量的令牌的结果。因此,0.1意味着只考虑构成前10%概率质量的令牌的结果。
一般建议是改变其中一个参数就行,不用两个都调整。
更多相关的参数配置,请查看
配置提示词前面提到了一个新的创建函数的用法:
varfunc=(
SummarizeConversationDefinition,//提示词
description:"给出一段对话记录,总结这部分对话.",//描述
executionSettings:promptExecutionSettings);//配置
创建提示模板时,可以使用PromptTemplateConfig类型调整控制提示符行为的参数。
//总结内容的最大token
constintMaxTokens=1024;
//提示模板
conststringSummarizeConversationDefinition="";
varfunc=(newPromptTemplateConfig
{
//Name不支持中文和特殊字符
Name="chat",
Description="给出一段对话记录,总结这部分对话.",
Template=SummarizeConversationDefinition,
TemplateFormat="semantic-kernel",
InputVariables=newListInputVariable
{
newInputVariable{Name="request",Description="用户的问题",IsRequired=true}
},
ExecutionSettings=newDictionarystring,PromptExecutionSettings
{
{
"default",
newOpenAIPromptExecutionSettings()
{
MaxTokens=MaxTokens,
Temperature=0
}
},
}
});
ExecutionSettings部分的配置,可以针对使用的模型起效,这里的配置不会全部同时起效,会根据实际使用的模型起效。
ExecutionSettings=newDictionarystring,PromptExecutionSettings
{
{
"default",
newOpenAIPromptExecutionSettings()
{
MaxTokens=1000,
Temperature=0
}
},
{
"",newOpenAIPromptExecutionSettings()
{
ModelId="",
MaxTokens=4000,
Temperature=0.2
}
},
{
"gpt-4",
newOpenAIPromptExecutionSettings()
{
ModelId="gpt-4-1106-preview",
MaxTokens=8000,
Temperature=0.3
}
}
}
聊到这里,重新说一下前面使用文件配置提示模板文件的,两者是相似的。
我们也可以使用文件的形式存储与代码一致的配置,其目录文件结构如下:
└───chat
|
└───
└───
模板文件由和组成,中配置提示词,跟PromptTemplateConfig的Template字段配置一致。
中涉及的内容比较多,你可以对照下面的json跟实现总结一节的代码,两者几乎是一模一样的。
{
"schema":1,
"type":"completion",
"description":"给出一段对话记录,总结这部分对话",
"execution_settings":{
"default":{
"max_tokens":1000,
"temperature":0
},
"":{
"model_id":"",
"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":"用户的问题.",
"required":true
},
{
"name":"history",
"description":"用户的问题.",
"required":true
}
]
}
C#代码:
//Name不支持中文和特殊字符提示模板语法
Name="chat",
Description="给出一段对话记录,总结这部分对话.",
Template=SummarizeConversationDefinition,
TemplateFormat="semantic-kernel",
InputVariables=newListInputVariable
{
newInputVariable{Name="request",Description="用户的问题",IsRequired=true}
},
ExecutionSettings=newDictionarystring,PromptExecutionSettings
{
{
"default",
newOpenAIPromptExecutionSettings()
{
MaxTokens=1000,
Temperature=0
}
},
{
"",newOpenAIPromptExecutionSettings()
{
ModelId="",
MaxTokens=4000,
Temperature=0.2
}
},
{
"gpt-4",
newOpenAIPromptExecutionSettings()
{
ModelId="gpt-4-1106-preview",
MaxTokens=8000,
Temperature=0.3
}
}
}
目前,我们已经有两个地方使用到提示模板的语法,即变量和函数调用,因为前面已经介绍过相关的用法,因此这里再简单提及一下。
变量变量的使用很简单,在提示工程中使用{{$变量名称}}标识即可,如{{$name}}。
然后在对话中有多种方法插入值,如使用KernelArguments存储变量值:
newKernelArguments函数调用
{
{"name","工良"}
});
在实现总结一节提到过,在提示模板中可以明确调用一个函数,比如定义一个函数如下:
//没有Kernelkernel
[KernelFunction,Description("给你一份很长的谈话记录,总结一下谈话内容.")]
publicasyncTaskstringSummarizeConversationAsync(
[Description("长对话记录\r\n.")]stringinput)
{
;
returninput;
}
//有Kernelkernel
[KernelFunction,Description("给你一份很长的谈话记录,总结一下谈话内容.")]
publicasyncTaskstringSummarizeConversationAsync(
[Description("长对话记录\r\n.")]stringinput,Kernelkernel)
{
;
returninput;
}
[KernelFunction]
[Description("Ssanemailtoarecipient.")]
publicasyncTaskSEmailAsync(
Kernelkernel,
stringrecipientEmails,
stringsubject,
stringbody
)
{
//AddlogictosanemailusingtherecipientEmails,subject,andbody
//Fornow,we'lljustprintoutasuccessmessagetotheconsole
("Emailsent!");
}
函数一定需要使用[KernelFunction]标识,[Description]描述函数的作用。函数可以一个或多个参数,每个参数最好都使用[Description]描述作用。
函数参数中,可以带一个Kernelkernel,可以放到开头或末尾,也可以不带,主要作用是注入Kernel对象。
在prompt中使用函数时,需要传递函数参数:
总结如下:{{$input}}.
其它一些特殊字符的转义方法等,详见官方文档:
文本生成前面劈里啪啦写了一堆东西,都是说聊天对话的,本节来聊一下文本生成的应用。
文本生成和聊天对话模型主要有以下模型:
ModeltypeModelTextgenerationtext-ada-001Textgenerationtext-babbage-001Textgenerationtext-curie-001Textgenerationtext-davinci-001Textgenerationtext-davinci-002Textgenerationtext-davinci-003ChatCompletionChatCompletiongpt-4当然,文本生成不一定只能用这么几个模型,使用gpt-4设定好背景提示,也可以达到相应效果。
文本生成可以有以下场景:

使用文本生成的示例如下,让AI总结文本:

按照这个示例,我们先在中编写扩展函数,配置使用.AddAzureOpenAITextGeneration()文本生成,而不是聊天对话。
publicstaticIKernelBuilderWithAzureOpenAIText(thisIKernelBuilderbuilder)
{
varconfiguration=GetConfiguration();
//需要换一个模型,比如gpt-35-turbo-instruct
varAzureOpenAIDeploymentName="ca";
varAzureOpenAIModelId="gpt-35-turbo-instruct";
varAzureOpenAIpoint=configuration["AzureOpenAI:point"]!;
varAzureOpenAIApiKey=configuration["AzureOpenAI:ApiKey"]!;
(c=
{
()
.SetMinimumLevel()
.AddSimpleConsole(options=
{
=true;
=true;
="yyyy-MM-ddHH:mm:ss";
});
});
//使用Chat,即大语言模型聊天
(
AzureOpenAIDeploymentName,
AzureOpenAIpoint,
AzureOpenAIApiKey,
modelId:AzureOpenAIModelId
);
returnbuilder;
}
然后编写提问代码,用户可以多行输入文本,最后使用000结束输入,将文本提交给AI进行总结。进行总结时,为了避免AI废话太多,因此这里使用ExecutionSettings配置相关参数。
代码示例如下:
builder=();
varkernel=();
("输入文本:");
varrequest="";
while(true)
{
varinput=();
if(input=="000")
{
break;
}
request+=;
request+=input;
}
varfunc=(newPromptTemplateConfig
{
Name="chat",
Description="给出一段对话记录,总结这部分对话.",
//用户的文本
Template=request,
TemplateFormat="semantic-kernel",
ExecutionSettings=newDictionarystring,PromptExecutionSettings
{
{
"default",
newOpenAIPromptExecutionSettings()
{
MaxTokens=100,
Temperature=(float)0.3,
TopP=(float)1,
FrequencyPenalty=(float)0,
PresencePenalty=(float)0
}
}
}
});
varresult=(kernel);
($"""
总结如下:
{("\n",result)}
""");

SemanticKernel在开头的包中提供了一些插件,不同的包有不同功能的插件。大部分目前还是属于半成品,因此这部分不详细讲解,本节只做简单说明。
目前官方仓库有以下包提供了一些插件:
├─
├─
├─
├─
└─
SemanticKernel还有通过远程使用插件的做法,详细请参考文档:
中包含最基础简单的插件:
//读取和写入文件
FileIOPlugin
//http请求以及返回字符串结果
HttpPlugin
//只提供了+和-两种运算
MathPlugin
//文本大小写等简单的功能
TextPlugin
//获得本地时间日期
TimePlugin
//在操作之前等待一段时间
WaitPlugin
因为这些插件对本文演示没什么帮助,功能也非常简单,因此这里不讲解。下面简单讲一下文档插件。
文档插件安装(需要勾选预览版),里面包含了文档插件,该文档插件使用了项目,支持以下文档格式:
WordprocessingML:用于创建和编辑Word文档(.docx)
SpreadsheetML:用于创建和编辑Excel电子表格(.xlsx)
PowerPointML:用于创建和编辑PowerPoint演示文稿(.pptx)
VisioML:用于创建和编辑Visio图表(.vsdx)
ProjectML:用于创建和编辑Project项目(.mpp)
DiagramML:用于创建和编辑Visio图表(.vsdx)
PublisherML:用于创建和编辑Publisher出版物(.pubx)
InfoPathML:用于创建和编辑InfoPath表单(.xsn)
文档插件暂时还没有好的应用场景,只是加载文档提取文字比较方便,代码示例如下:
DocumentPlugindocumentPlugin=new(newWordDocumentConnector(),newLocalFileSystemConnector());
stringfilePath="(完整版)基础财务知识.docx";
stringtext=(filePath);
(text);
由于这些插件目前都是半成品,因此这里就不展开说明了。

依然是半成品,这里就不再赘述。
因为我也没有看明白这个东西怎么用。
KernelMemory构建文档知识库KernelMemory是一个歪果仁的个人项目,支持PDF和Word文档、PowerPoint演示文稿、图像、电子表格等,通过利用大型语言模型(llm)、嵌入和矢量存储来提取信息和生成记录,主要目的是提供文档处理相关的接口,最常使用的场景是知识库系统。读者可能对知识库系统不了解,如果有条件,建议部署一个Fastgpt系统研究一下。
但是目前KernelMemory依然是半产品,文档也不完善,所以接下来笔者也只讲解最核心的部分,感兴趣的读者建议直接看源码。
KernelMemory项目文档:
KernelMemory项目仓库:
打开KernelMemory项目仓库,将项目拉取到本地。
要讲解知识库系统,可以这样理解。大家都知道,训练一个医学模型是十分麻烦的,别说机器的GPU够不够猛,光是训练AI,就需要掌握各种专业的知识。如果出现一个新的需求,可能又要重新训练一个模型,这样太麻烦了。
于是出现了大语言模型,特点是什么都学什么都会,但是不够专业深入,好处时无论医学、摄影等都可以使用。
虽然某方面专业的知识不够深入和专业,但是我们换种部分解决。
首先,将docx、pdf等问题提取出文本,然后切割成多个段落,每一段都使用AI模型生成相关向量,这个向量的原理笔者也不懂,大家可以简单理解为分词,生成向量后,将段落文本和向量都存储到数据库中(数据库需要支持向量)。

然后在用户提问“什么是报表”时,首先在数据库中搜索,根据向量来确定相似程度,把几个跟问题相关的段落拿出来,然后把这几段文本和用户的问题一起发给AI。相对于在提示模板中,塞进一部分背景知识,然后加上用户的问题,再由AI进行总结回答。


笔者建议大家有条件的话,部署一个开源版本的Fastgpt系统,把这个系统研究一下,学会这个系统后,再去研究KernelMemory,你就会觉得非常简单了。同理,如果有条件,可以先部署一个LobeHub,开源的AI对话系统,研究怎么用,再去研究SemanticKernel文档,接着再深入源码。
从web处理网页KernelMemory支持从网页爬取、导入文档、直接给定字符串三种方式导入信息,由于KernelMemory提供了一个Service示例,里面有一些值得研究的代码写法,因此下面的示例是启动Service这个Web服务,然后在客户端将文档推送该Service处理,客户端本身不对接AI。
由于这一步比较麻烦,读者动手的过程中搞不出来,可以直接放弃,后面会说怎么自己写一个。
打开kernel-memory源码的service/Service路径。
使用命令启动服务:
dotnetrunsetup
这个控制台的作用是帮助我们生成相关配置的。启动这个控制台之后,根据提示选择对应的选项(按上下键选择选项,按下回车键确认),以及填写配置内容,配置会被存储到中。
如果读者搞不懂这个控制台怎么使用,那么可以直接将替换下面的json到。
有几个地方需要读者配置一下。
AccessKey1、AccessKey2是客户端使用该Service所需要的验证密钥,随便填几个字母即可。
AzureAIDocIntel、AzureOpenAIEmbedding、AzureOpenAIText根据实际情况填写。
{
"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":"INFONOTFOUND"
}
},
"Services":{
"SimpleQueues":{
"Directory":"_tmp_queues"
},
"SimpleFileStorage":{
"Directory":"_tmp_files"
},
"AzureAIDocIntel":{
"Auth":"ApiKey",
"point":"",
"APIKey":"aaa"
},
"AzureOpenAIEmbedding":{
"APIType":"EmbeddingGeneration",
"Auth":"ApiKey",
"point":"",
"Deployment":"aitext",
"APIKey":"aaa"
},
"SimpleVectorDb":{
"Directory":"_tmp_vectors"
},
"AzureOpenAIText":{
"APIType":"ChatCompletion",
"Auth":"ApiKey",
"point":"",
"Deployment":"myai",
"APIKey":"aaa",
"MaxRetries":10
}
}
},
"Logging":{
"LogLevel":{
"Default":"Warning"
}
},
"AllowedHosts":"*"
}
启动Service后,可以看到以下swagger界面。

然后编写代码连接到知识库系统,推送要处理的网页地址给Service。创建一个项目,引入包。
然后按照以下代码将文档推送给Service处理。
//前面部署的Service地址,和自定义的密钥。
varmemory=newMemoryWebClient(point:"http://localhost:9001/",apiKey:"自定义密钥1");
//导入网页
(
"",
documentId:"doc02");
("正在处理文档,请稍等");
//使用AI处理网页知识
while(!(documentId:"doc02"))
{
((1500));
}
//提问
varanswer=("比特币是什么?");
($"\nAnswer:{}");
此外还有ImportTextAsync、ImportDocumentAsync来个导入知识的方法。
手动处理文档本节内容稍多,主要讲解如何使用KernelMemory从将文档导入、生成向量、存储向量、搜索问题等。
新建项目,安装库。
为了便于演示,下面代码将文档和向量临时存储,不使用数据库存储。
全部代码示例如下:
;
;
;
;
varmemory=newKernelMemoryBuilder()
//文档解析后的向量存储位置,可以选择Postgres等,
//这里选择使用本地临时文件存储向量
.WithSimpleVectorDb(newSimpleVectorDbConfig
{
Directory="aaa"
})
//配置文档解析向量模型
.WithAzureOpenAITextEmbeddingGeneration(newAzureOpenAIConfig
{
Deployment="aitext",
point="",
Auth=,
APIType=,
APIKey="aaa"
})
//配置文本生成模型
.WithAzureOpenAITextGeneration(newAzureOpenAIConfig
{
Deployment="myai",
point="",
Auth=,
APIKey="aaa",
APIType=
})
.Build();
//导入网页
(
"",
documentId:"doc02");
//Waitforingestiontocomplete,usually1-2seconds
("正在处理文档,请稍等");
while(!(documentId:"doc02"))
{
((1500));
}
//Askaquestion
varanswer=("比特币是什么?");
($"\nAnswer:{}");

首先使用KernelMemoryBuilder构建配置,配置的内容比较多,这里会使用到两个模型,一个是向量模型,一个是文本生成模型(可以使用对话模型,如gpt-4-32k)。
接下来,按照该程序的工作流程讲解各个环节的相关知识。
首先是讲解将文件存储到哪里,也就是导入文件之后,将文件存储到哪里,存储文件的接口是IContentStorage,目前有两个实现:
AzureBlobsStorage
//存储到目录
SimpleFileStorage
使用方法:
varmemory=newKernelMemoryBuilder()
.WithSimpleFileStorage(newSimpleFileStorageConfig
{
Directory="aaa"
})
.WithAzureBlobsStorage(newAzureBlobsConfig
{
Account=""
})
KernelMemory还不支持Mongodb,不过可以自己使用IContentStorage接口写一个。
本地解析文档后,会进行分段,如右边的q列所示。

接着是,配置文档生成向量模型,导入文件文档后,在本地提取出文本,需要使用AI模型从文本中生成向量。
解析后的向量是这样的:

将文本生成向量,需要使用ITextEmbeddingGenerator接口,目前有两个实现:
AzureOpenAITextEmbeddingGenerator
OpenAITextEmbeddingGenerator
示例:
varmemory=newKernelMemoryBuilder()
//配置文档解析向量模型
.WithAzureOpenAITextEmbeddingGeneration(newAzureOpenAIConfig
{
Deployment="aitext",
point="",
Auth=,
APIType=,
APIKey="xxx"
})
.WithOpenAITextEmbeddingGeneration(newOpenAIConfig
{
})
生成向量后,需要存储这些向量,需要实现IMemoryDb接口,有以下配置可以使用:
//文档解析后的向量存储位置,可以选择Postgres等,
//这里选择使用本地临时文件存储向量
.WithSimpleVectorDb(newSimpleVectorDbConfig
{
Directory="aaa"
})
.WithAzureAISearchMemoryDb(newAzureAISearchConfig
{
})
.WithPostgresMemoryDb(newPostgresConfig
{
})
.WithQdrantMemoryDb(newQdrantConfig
{
})
.WithRedisMemoryDb("host=.")
当用户提问时,首先会在这里的IMemoryDb调用相关方法查询文档中的向量、索引等,查找出相关的文本。
查出相关的文本后,需要发送给AI处理,需要使用ITextGenerator接口,目前有两个实现:
AzureOpenAITextGenerator
OpenAITextGenerator
配置示例:
//配置文本生成模型
.WithAzureOpenAITextGeneration(newAzureOpenAIConfig
{
Deployment="myai",
point="",
Auth=,
APIKey="aaa",
APIType=
})
导入文档时,首先将文档提取出文本,然后进行分段。
将每一段文本使用向量模型解析出向量,存储到IMemoryDb接口提供的服务中,如Postgres数据库。
提问问题或搜索内容时,从IMemoryDb所在的位置搜索向量,查询到相关的文本,然后将文本收集起来,发送给AI(使用文本生成模型),这些文本相对于提示词,然后AI从这些提示词中学习并回答用户的问题。
详细源码可以参考,由于源码比较多,这里就不赘述了。

这样说,大家可能不太容易理解,我们可以用下面的代码做示范。
//导入文档
(
"aaa/(完整版)基础财务知识.docx",
documentId:"doc02");
("正在处理文档,请稍等");
while(!(documentId:"doc02"))
{
((1500));
}
varanswer1=("报表怎么做?");
//每个Citation表示一个文档文件
foreach()
{
//与搜索关键词相关的文本
foreach()
{
();
}
}
varanswer2=("报表怎么做?");
($"\nAnswer:{}");
然后再参考Fastgpt的搜索配置,可以自己写一个这样的知识库系统。

本期模特:咕咕





