如何通过SK集成chatGPT实现DotNet项目工程化?

智能助手服务

以下案例将讲解如何实现天气插件

当前文档对应src/assistant/Chat.SemanticServer项目

首先我们介绍一下Chat.SemanticServer的技术架构

SemanticKernel 是什么?

Semantic Kernel是一个SDK,它将OpenAI、Azure OpenAI和Hugging Face等大型语言模型(LLMs)与传统的编程语言如C#、Python和Java集成在一起。Semantic Kernel通过允许您定义可以在几行代码中链接在一起的插件来实现这一目标。

如何集成使用SemanticKernel

以下是添加IKernel,OpenAIOptions.ModelOpenAIOptions.Key在一开始使用了builder.Configuration.GetSection("OpenAI").Get<OpenAIOptions>();绑定。对应配置文件,OpenAIChatCompletion则是用于直接请求OpenAI。

json 复制代码
"OpenAI": {
    "Key": "",
    "Endpoint": "",
    "Model": "gpt-3.5-turbo"
  }
csharp 复制代码
builder.Services.AddTransient<IKernel>((services) =>
{
    var httpClientFactory = services.GetRequiredService<IHttpClientFactory>();
    return Kernel.Builder
        .WithOpenAIChatCompletionService(
            OpenAIOptions.Model,
            OpenAIOptions.Key,
            httpClient: httpClientFactory.CreateClient("ChatGPT"))
        .Build();
}).AddSingleton<OpenAIChatCompletion>((services) =>
{
    var httpClientFactory = services.GetRequiredService<IHttpClientFactory>();
    return new OpenAIChatCompletion(OpenAIOptions.Model, OpenAIOptions.Key,
        httpClient: httpClientFactory.CreateClient("ChatGPT"));
});

在项目中存在plugins文件夹,这是提供的插件目录,在BasePlugin目录下存在一个识别意图的插件。

config.json对应当前插件的一些参数配置,

json 复制代码
{
  "schema": 1,
  "type": "completion",
  "description": "获取用户的意图。",
  "completion": {
    "max_tokens": 500,
    "temperature": 0.0,
    "top_p": 0.0,
    "presence_penalty": 0.0,
    "frequency_penalty": 0.0
  },
  "input": {
    "parameters": [
      {
        "name": "input",
        "description": "用户的请求。",
        "defaultValue": ""
      },
      {
        "name": "history",
        "description": "对话的历史。",
        "defaultValue": ""
      },
      {
        "name": "options",
        "description": "可供选择的选项。",
        "defaultValue": ""
      }
    ]
  }
}

skprompt.txt则是当前插件使用的prompt

加载插件

在这里我们注入了IKernel

csharp 复制代码
    private readonly IKernel _kernel;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly RedisClient _redisClient;
    private readonly ILogger<IntelligentAssistantHandle> _logger;
    private readonly OpenAIChatCompletion _chatCompletion;

    public IntelligentAssistantHandle(IKernel kernel, RedisClient redisClient,
        ILogger<IntelligentAssistantHandle> logger, IHttpClientFactory httpClientFactory,
        OpenAIChatCompletion chatCompletion)
    {
        _kernel = kernel;
        _redisClient = redisClient;
        _logger = logger;
        _httpClientFactory = httpClientFactory;
        _chatCompletion = chatCompletion;

        _redisClient.Subscribe(nameof(IntelligentAssistantEto),
            ((s, o) => { HandleAsync(JsonSerializer.Deserialize<IntelligentAssistantEto>(o as string)); }));
    }

然后准备加载插件。

csharp 复制代码
//对话摘要  SK.Skills.Core 核心技能
_kernel.ImportSkill(new ConversationSummarySkill(_kernel), "ConversationSummarySkill");

// 插件根目录
var pluginsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "plugins");

// 这个是添加BasePlugin目录下面的所有插件,会自动扫描
var intentPlugin = _kernel
                .ImportSemanticSkillFromDirectory(pluginsDirectory, "BasePlugin");

// 这个是添加Travel目录下面的所有插件,会自动扫描
var travelPlugin = _kernel
                .ImportSemanticSkillFromDirectory(pluginsDirectory, "Travel");

// 这个是添加ChatPlugin目录下面的所有插件,会自动扫描
var chatPlugin = _kernel
                .ImportSemanticSkillFromDirectory(pluginsDirectory, "ChatPlugin");

// 这个是添加WeatherPlugin类插件,并且给定插件命名WeatherPlugin
var getWeather = _kernel.ImportSkill(new WeatherPlugin(_httpClientFactory), "WeatherPlugin");

使用插件,首先我们创建了一个ContextVariablesinput则是GetIntent插件中的的{``{$input}}options则对应{``{$options}}getIntentVariables则将替换对应的prompt中响应的参数。

csharp 复制代码
var getIntentVariables = new ContextVariables
            {
                ["input"] = value,
                ["options"] = "Weather,Attractions,Delicacy,Traffic" //给GPT的意图,通过Prompt限定选用这些里面的
            };
string intent = (await _kernel.RunAsync(getIntentVariables, intentPlugin["GetIntent"])).Result.Trim();

plugins/BasePlugin/GetIntent/skprompt.txt内容

tex 复制代码
{{ConversationSummarySkill.SummarizeConversation $history}}
用户: {{$input}}

---------------------------------------------
提供用户的意图。其意图应为以下内容之一: {{$options}}

意图: 

意图识别完成以后,当执行完成GetIntentintent相应会根据options中提供的参数返回与之匹配的参数,

然后下面的代码将根据返回的意图进行实际上的操作,或加载相应的插件,比如当intent返回Weather,则首先从chatPlugin中使用Weather插件,并且传递当前用户输入内容,在这里将提取用户需要获取天气的城市。

完成返回以后将在使用MathFunction = _kernel.Skills.GetFunction("WeatherPlugin", "GetWeather")的方式获取WeatherPlugin插件的GetWeather方法,并且将得到的参数传递到_kernel.RunAsync执行的时候则会掉用GetWeather方法,这个时候会由插件返回的json在组合成定义的模板消息进行返回,就完成了调用。

csharp 复制代码
            ISKFunction MathFunction = null;
            SKContext? result = null;

            //获取意图后动态调用Fun
            if (intent is "Attractions" or "Delicacy" or "Traffic")
            {
                MathFunction = _kernel.Skills.GetFunction("Travel", intent);
                result = await _kernel.RunAsync(value, MathFunction);
            }
            else if (intent is "Weather")
            {
                var newValue = (await _kernel.RunAsync(new ContextVariables
                {
                    ["input"] = value
                }, chatPlugin["Weather"])).Result;
                MathFunction = _kernel.Skills.GetFunction("WeatherPlugin", "GetWeather");
                result = await _kernel.RunAsync(newValue, MathFunction);

                if (!result.Result.IsNullOrWhiteSpace())
                {
                    if (result.Result.IsNullOrEmpty())
                    {
                        await SendMessage("获取天气失败了!", item.RevertId, item.Id);
                        return;
                    }

                    var weather = JsonSerializer.Deserialize<GetWeatherModule>(result.Result);
                    var live = weather?.lives.FirstOrDefault();
                    await SendMessage(WeatherTemplate
                        .Replace("{province}", live!.city)
                        .Replace("{weather}", live?.weather)
                        .Replace("{temperature_float}", live?.temperature_float)
                        .Replace("{winddirection}", live?.winddirection)
                        .Replace("{humidity}", live.humidity), item.RevertId, item.Id);
                    return;
                }
            }
            else
            {
                var chatHistory = _chatCompletion.CreateNewChat();
                chatHistory.AddUserMessage(value);
                var reply = await _chatCompletion.GenerateMessageAsync(chatHistory);
                
                return;
            }

Weather的prompt

tex 复制代码
我会给你一句话,你需要找到需要获取天气的城市,如果存在时间也提供给我:
{{$input}}

仅返回结果,除此之外不要有多余内容,按照如下格式:
{
    "city":"",
    "time":""
}

WeatherPlugin获取天气插件

csharp 复制代码
/// <summary>
/// 获取天气插件
/// </summary>
public class WeatherPlugin
{
    private static List<AdCode>? _codes;

    static WeatherPlugin()
    {
        var path = Path.Combine(AppContext.BaseDirectory, "adcode.json");
        if (File.Exists(path))
        {
            var str = File.ReadAllText(path);
            _codes = JsonSerializer.Deserialize<List<AdCode>>(str);
        }

        _codes ??= new List<AdCode>();
    }

    private readonly IHttpClientFactory _httpClientFactory;

    public WeatherPlugin(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [SKFunction, Description("获取天气")]
    [SKParameter("input", "入参")]
    public async Task<string> GetWeather(SKContext context)
    {
        var weatherInput = JsonSerializer.Deserialize<WeatherInput>(context.Result);
        var value = _codes.FirstOrDefault(x => x.name.StartsWith(weatherInput.city));
        if (value == null)
        {
            return "请先描述指定城市!";
        }

        var http = _httpClientFactory.CreateClient(nameof(WeatherPlugin));
        var result = await http.GetAsync(
            "https://restapi.amap.com/v3/weather/weatherInfo?key={高德天气api的key}&extensions=base&output=JSON&city=" +
            value.adcode);

        if (result.IsSuccessStatusCode)
        {
            return await result.Content.ReadAsStringAsync();
        }
        
        return string.Empty;
    }
}

public class WeatherInput
{
    public string city { get; set; }
    public string time { get; set; }
}

public class AdCode
{
    public string name { get; set; }

    public string adcode { get; set; }

    public string citycode { get; set; }
}

效果图

以上代码可从仓库获取

项目开源地址

体验地址:https://chat.tokengo.top/ (可以使用Gitee快捷登录)

Github : https://github.com/239573049/chat

Gitee: https://gitee.com/hejiale010426/chat

相关推荐
fly-974 小时前
LLM大模型微调入门Lora(LlamaFactory)
chatgpt·nlp
程序员洲洲8 小时前
重构开发之道,Blackbox.AI为技术注入智能新动力
chatgpt·blackbox.ai·blackboxai·图片转应用
海边散步的蜗牛12 小时前
学术论文写作丨机器学习与深度学习
人工智能·深度学习·机器学习·chatgpt·数据分析·ai写作
爱技术的小伙子13 小时前
【ChatGPT】通过Prompt技巧优化ChatGPT的营销文案输出
人工智能·chatgpt·prompt
智匠MindCraft Al14 小时前
GPT o1 模型使用及API调用
人工智能·gpt·ai·语言模型·chatgpt
HelpLook HelpLook16 小时前
人工智能:重塑医疗、企业与生活的未来知识管理——以HelpLook为例
人工智能·chatgpt·生活·知识库·helplook·知识库搭建
tyler_download19 小时前
手撸 chatgpt 大模型:简述 LLM 的架构,算法和训练流程
算法·chatgpt
大数据面试宝典19 小时前
用AI来写SQL:让ChatGPT成为你的数据库助手
数据库·人工智能·chatgpt
IPdodo全球网络1 天前
解析“ChatGPT网络错误”:从网络专线到IP地址的根源与解决方案
网络·tcp/ip·chatgpt
思绪无限1 天前
详解Gemini API的使用:在国内实现大模型对话与目标检测教程
人工智能·目标检测·计算机视觉·chatgpt·大模型·使用教程·gemini api