普通的AI大模型的数据都是在一开始训练的时候决定的,所以大模型的数据来源都可能存在时效性。
下面我们会利用SK插件来给AI大模型添加联网功能。
准备工作
创建一个名称为5_SK_Plugin_Web
的控制台项目 复制以下代码到5_SK_Plugin_Web
项目文件中
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.13.0" />
</ItemGroup>
</Project>
创建OpenAIHttpClientHandler.cs
namespace ConsoleApp1;
public class OpenAIHttpClientHandler : HttpClientHandler
{
private readonly string _uri;
public OpenAIHttpClientHandler(string uri) => _uri = uri.TrimEnd('/');
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
UriBuilder uriBuilder;
if (request.RequestUri?.LocalPath == "/v1/chat/completions")
{
uriBuilder = new UriBuilder(_uri "/v1/chat/completions");
request.RequestUri = uriBuilder.Uri;
}
else if (request.RequestUri?.LocalPath == "/v1/embeddings")
{
uriBuilder = new UriBuilder(_uri "/v1/embeddings");
request.RequestUri = uriBuilder.Uri;
}
return await base.SendAsync(request, cancellationToken);
}
}
创建联网插件
HttpClientFunction.cs
public class HttpClientFunction(IHttpClientFactory httpClientFactory, IChatCompletionService completionService)
{
private const string BingTemplate = "https://cn.bing.com/search?q={0}";
private const string SystemTemplate =
@"
## 角色:
你是一款专业的搜索引擎助手。你的主要任务是从Html根据标签生成md的内容,并专注于准确地总结段落的大意,而不包含任何其他多余的信息或解释。
## 能力:
- 解析html中标签生成对应的md。
- 将提取的信息准确地总结为一段简洁的文本。
- 不属于用户提问的数据则不用整理。
## 指南:
- 这是一个完整的html标签,您需要根据标签生成对应的md格式。
- 只包含关键信息,尽量减少非主要信息的出现。
- 完成总结后,立即向用户提供,不需要询问用户是否满意或是否需要进一步的修改和优化。
";
/// <summary>
/// 搜索用户提出的问题
/// </summary>
[KernelFunction, Description("搜索用户提出的问题")]
public async Task<string> GetAsync(string value)
{
var http = httpClientFactory.CreateClient(nameof(HttpClientFunction));
var html = await http.GetStringAsync(string.Format(BingTemplate, value)).ConfigureAwait(false);
var scriptRegex = new Regex(@"<script[^>]*>[sS]*?</script>");
var styleRegex = new Regex(@"<style[^>]*>[sS]*?</style>");
var commentRegex = new Regex(@"<!--[sS]*?-->");
var headRegex = new Regex(@"<head[^>]*>[sS]*?</head>");
var tagAttributesRegex = new Regex(@"<(w )(?:s [^>]*)?>");
var emptyTagsRegex = new Regex(@"<(w )(?:s [^>]*)?>s*</1>");
html = scriptRegex.Replace(html, "");
html = styleRegex.Replace(html, "");
html = commentRegex.Replace(html, "");
html = headRegex.Replace(html, "");
html = tagAttributesRegex.Replace(html, "<$1>");
html = emptyTagsRegex.Replace(html, "");
var result = await completionService.GetChatMessageContentsAsync(new ChatHistory(SystemTemplate)
{
new(AuthorRole.User, html),
new(AuthorRole.User, value)
}, new OpenAIPromptExecutionSettings()
{
ModelId = "gpt-3.5-turbo-0125"
});
Console.WriteLine("搜索结果:" result.FirstOrDefault()?.Content);
return result.FirstOrDefault()?.Content ?? "抱歉,未找到相关信息。";
}
}
我们使用HttpClientFunction
类来实现一个搜索引擎插件,该插件可以根据用户提出的问题搜索相关信息。 利用了https://cn.bing.com/search?q={0}
接口去获取我们需要的信息,然后返回Html,使用正则表达式将html中大部分不需要的内容去掉。
然后我们在将获取的Html使用以下提示词进行内容精简。
代码语言:javascript复制## 角色:
你是一款专业的搜索引擎助手。你的主要任务是从Html根据标签生成md的内容,并专注于准确地总结段落的大意,而不包含任何其他多余的信息或解释。
## 能力:
- 解析html中标签生成对应的md。
- 将提取的信息准确地总结为一段简洁的文本。
- 不属于用户提问的数据则不用整理。
## 指南:
- 这是一个完整的html标签,您需要根据标签生成对应的md格式。
- 只包含关键信息,尽量减少非主要信息的出现。
- 完成总结后,立即向用户提供,不需要询问用户是否满意或是否需要进一步的修改和优化。
然后我们实现我们的核心逻辑。
打开Program.cs
文件
var kernelBuilder = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "gpt-3.5-turbo-0125",
apiKey: "这里填写在https://api.token-ai.cn/创建的令牌",
httpClient: new HttpClient(new OpenAIHttpClientHandler("https://api.token-ai.cn/")));
kernelBuilder.Services.AddHttpClient();
var kernel = kernelBuilder.Build();
kernel.Plugins.AddFromType<HttpClientFunction>(serviceProvider: kernel.Services);
var chat = kernel.GetRequiredService<IChatCompletionService>();
var openAIPromptExecutionSettings = new OpenAIPromptExecutionSettings()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
while (true)
{
Console.WriteLine("请输入您的问题:");
var str = Console.ReadLine();
if (str == "exit")
{
break;
}
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage(str);
await foreach (var item in
chat.GetStreamingChatMessageContentsAsync(chatHistory, openAIPromptExecutionSettings, kernel))
{
Console.Write(item?.Content);
}
Console.WriteLine();
}
解析上面的代码,
代码语言:javascript复制
var kernelBuilder = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "gpt-3.5-turbo-0125",
apiKey: "这里填写在https://api.token-ai.cn/创建的令牌",
httpClient: new HttpClient(new OpenAIHttpClientHandler("https://api.token-ai.cn/")));
kernelBuilder.Services.AddHttpClient();
var kernel = kernelBuilder.Build();
在这里我们创建了一个kernelBuilder
,然后我们添加了一个OpenAIChatCompletion
插件,这个插件是用来调用OpenAI的API的,我们需要填写我们在https://api.token-ai.cn/
创建的令牌, 然后我们在kernelBuilder
中的Services注册了我们的HttpClient
服务,以便插件的依赖注入的IHttpClientFactory
能够正常工作。
kernel.Plugins.AddFromType<HttpClientFunction>(serviceProvider: kernel.Services);
这一行代码是将我们的HttpClientFunction
插件添加到kernel
中,这样我们就可以在kernel
中使用我们的插件了。
var chat = kernel.GetRequiredService<IChatCompletionService>();
var openAIPromptExecutionSettings = new OpenAIPromptExecutionSettings()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
在这里我们通过kernel
中的GetRequiredService
方法获取了IChatCompletionService
服务,这个服务是用来调用OpenAI的API的。 然后我们创建了一个OpenAIPromptExecutionSettings
对象,这个对象是用来设置我们的插件的行为的,这里我们设置了ToolCallBehavior
为AutoInvokeKernelFunctions
,这样我们的插件就会自动调用kernel
中的函数了。
while (true)
{
Console.WriteLine("请输入您的问题:");
var str = Console.ReadLine();
if (str == "exit")
{
break;
}
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage(str);
await foreach (var item in
chat.GetStreamingChatMessageContentsAsync(chatHistory, openAIPromptExecutionSettings, kernel))
{
Console.Write(item?.Content);
}
Console.WriteLine();
}
我们在这里创建了一个循环,然后我们在循环中获取用户输入的问题,然后我们创建了一个ChatHistory
对象,这个对象是用来存储我们的对话历史的,然后我们调用chat.GetStreamingChatMessageContentsAsync
方法。 对话的执行流程是:
- 用户输入问题
- 调用
chat.GetStreamingChatMessageContentsAsync
方法,然后传递kernel
,让它自动调用插件,然后根据用户提问去判断调用哪个插件。 - 返回需要调用的插件。
- 调用插件
HttpClientFunction.GetAsync
方法,然后得到有用的信息。 - 整理信息,返回给用户。
运行
代码语言:javascript复制请输入您的问题:
庆余年最新一集?
搜索结果:**庆余年最新一集**是庆余年第二季的剧情:在悬空寺上,庆帝遭遇三连刺杀,范闲出手相救却导致武功全废。危机四伏,压力陡增,范闲别无选择,他必须以这样的身体下江南,挑战庞大的势力与既定的游戏规则,以求彻底夺回内库。
庆余年第二季的最新一集剧情是:在悬空寺上,庆帝遭遇三连刺杀,范闲出手相救却导致武功全废。危机四伏,压力陡增,范闲别无选择,他必须以这样的身体下江南,挑战庞大的势力与既定的游戏规则,以求彻底夺回内库。
请输入您的问题
总结
我们通过上面的代码实现了一个搜索引擎插件,这个插件可以根据用户提出的问题搜索相关信息,然后返回给用户。