当前位置:首页 > AI > 正文内容

歪门邪道:凭借 HttpClientHandler 阻拦恳求,体会 Semantic Kernel 插件

邻居的猫1个月前 (12-09)AI392

前天测验经过 one-api + dashscope(阿里云灵积) + qwen(通义千问)运转 Semantic Kernel 插件(Plugin) ,成果测验失利,详见前天的博文。

今日换一种方法测验,挑选了一个歪门邪道走走看,看能不能在不运用大模型的情况下让 Semantic Kernel 插件运转起来,这个歪门邪道便是从 Stephen Toub 那偷学到的一招 —— 凭借 DelegatingHandler(new HttpClientHandler()) 阻拦 HttpClient 恳求,直接以模仿数据进行呼应。

先创立一个 .NET 控制台项目

dotnet new console
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.Extensions.Http

参照 Semantic Kernel 源码中的示例代码创立一个十分简略的插件 LightPlugin

public class LightPlugin
{
    public bool IsOn { get; set; } = false;

    [KernelFunction]
    [Description("帮看一下灯是开是关")]
    public string GetState() => IsOn ? "on" : "off";

    [KernelFunction]
    [Description("开灯或许关灯")]
    public string ChangeState(bool newState)
    {
        IsOn = newState;
        var state = GetState();
        Console.WriteLine(state == "on" ? $"[开灯啦]" : "[关灯咯]");
        return state;
    }
}

接着创立歪门邪道 BackdoorHandler,先完结一个最简略的功用,打印 HttpClient 恳求内容

public class BypassHandler() : DelegatingHandler(new HttpClientHandler())
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Console.WriteLine(await request.Content!.ReadAsStringAsync());
        // return await base.SendAsync(request, cancellationToken);
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
}

然后携 LightPluginBypassHandler 创立 Semantic Kernel 的 Kernel

var builder = Kernel.CreateBuilder();
builder.Services.AddOpenAIChatCompletion("qwen-max", "sk-xxxxxx");
builder.Services.ConfigureHttpClientDefaults(b =>
    b.ConfigurePrimaryHttpMessageHandler(() => new BypassHandler()));
builder.Plugins.AddFromType<LightPlugin>();
Kernel kernel = builder.Build();

再然后,发送带着 prompt 的恳求并获取呼应内容

var history = new ChatHistory();
history.AddUserMessage("请开灯");
Console.WriteLine("User > " + history[0].Content);
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

// Enable auto function calling
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};

var result = await chatCompletionService.GetChatMessageContentAsync(
    history,
    executionSettings: openAIPromptExecutionSettings,
    kernel: kernel);

Console.WriteLine("Assistant > " + result);

运转控制台程序,BypassHandler 就会在控制台输出恳求的 json 内容(为了阅览便利对json进行了格式化):

点击检查 json
{
  "messages": [
    {
      "content": "Assistant is a large language model.",
      "role": "system"
    },
    {
      "content": "\u8BF7\u5F00\u706F",
      "role": "user"
    }
  ],
  "temperature": 1,
  "top_p": 1,
  "n": 1,
  "presence_penalty": 0,
  "frequency_penalty": 0,
  "model": "qwen-max",
  "tools": [
    {
      "function": {
        "name": "LightPlugin-GetState",
        "description": "\u5E2E\u770B\u4E00\u4E0B\u706F\u662F\u5F00\u662F\u5173",
        "parameters": {
          "type": "object",
          "required": [],
          "properties": {}
        }
      },
      "type": "function"
    },
    {
      "function": {
        "name": "LightPlugin-ChangeState",
        "description": "\u5F00\u706F\u6216\u8005\u5173\u706F",
        "parameters": {
          "type": "object",
          "required": [
            "newState"
          ],
          "properties": {
            "newState": {
              "type": "boolean"
            }
          }
        }
      },
      "type": "function"
    }
  ],
  "tool_choice": "auto"
}

为了能反序列化这个 json ,咱们需求界说一个类型 ChatCompletionRequest,Sermantic Kernel 中没有现成能够运用的,完结代码如下:

点击检查 ChatCompletionRequest
public class ChatCompletionRequest
{
    [JsonPropertyName("messages")]
    public IReadOnlyList<RequestMessage>? Messages { get; set; }

    [JsonPropertyName("temperature")]
    public double Temperature { get; set; } = 1;

    [JsonPropertyName("top_p")]
    public double TopP { get; set; } = 1;

    [JsonPropertyName("n")]
    public int? N { get; set; } = 1;

    [JsonPropertyName("presence_penalty")]
    public double PresencePenalty { get; set; } = 0;

    [JsonPropertyName("frequency_penalty")]
    public double FrequencyPenalty { get; set; } = 0;

    [JsonPropertyName("model")]
    public required string Model { get; set; }

    [JsonPropertyName("tools")]
    public IReadOnlyList<Tool>? Tools { get; set; }

    [JsonPropertyName("tool_choice")]
    public string? ToolChoice { get; set; }
}

public class RequestMessage
{
    [JsonPropertyName("role")]
    public string? Role { get; set; }

    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("content")]
    public string? Content { get; set; }
}

public class Tool
{
    [JsonPropertyName("function")]
    public FunctionDefinition? Function { get; set; }

    [JsonPropertyName("type")]
    public string? Type { get; set; }
}

public class FunctionDefinition
{
    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("description")]
    public string? Description { get; set; }

    [JsonPropertyName("parameters")]
    public ParameterDefinition Parameters { get; set; }

    public struct ParameterDefinition
    {
        [JsonPropertyName("type")]
        public required string Type { get; set; }

        [JsonPropertyName("description")]
        public string? Description { get; set; }

        [JsonPropertyName("required")]
        public string[]? Required { get; set; }

        [JsonPropertyName("properties")]
        public Dictionary<string, PropertyDefinition>? Properties { get; set; }

        public struct PropertyDefinition
        {
            [JsonPropertyName("type")]
            public required PropertyType Type { get; set; }
        }

        [JsonConverter(typeof(JsonStringEnumConverter))]
        public enum PropertyType
        {
            Number,
            String,
            Boolean
        }
    }
}

有了这个类,咱们就能够从恳求中获取对应 Plugin 的 function 信息,比方下面的代码:

var function = chatCompletionRequest?.Tools.FirstOrDefault(x => x.Function.Description.Contains("开灯"))?.Function;
var functionName = function.Name;
var parameterName = function.Parameters.Properties.FirstOrDefault(x => x.Value.Type == PropertyType.Boolean).Key;

接下来便是歪门邪道的要害,直接在 BypassHandler 中呼应 Semantic Kernel 经过 OpenAI.ClientCore 宣布的 http 恳求。

首要创立用于 json 序列化的类 ChatCompletionResponse

点击检查 ChatCompletionResponse
public class ChatCompletionResponse
{
    [JsonPropertyName("id")]
    public string? Id { get; set; }

    [JsonPropertyName("object")]
    public string? Object { get; set; }

    [JsonPropertyName("created")]
    public long Created { get; set; }

    [JsonPropertyName("model")]
    public string? Model { get; set; }

    [JsonPropertyName("usage"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public Usage? Usage { get; set; }

    [JsonPropertyName("choices")]
    public List<Choice>? Choices { get; set; }
}

public class Choice
{
    [JsonPropertyName("message")]
    public ResponseMessage? Message { get; set; }

    /// <summary>
    /// The message in this response (when streaming a response).
    /// </summary>
    [JsonPropertyName("delta"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public ResponseMessage? Delta { get; set; }

    [JsonPropertyName("finish_reason")]
    public string? FinishReason { get; set; }

    /// <summary>
    /// The index of this response in the array of choices.
    /// </summary>
    [JsonPropertyName("index")]
    public int Index { get; set; }
}

public class ResponseMessage
{
    [JsonPropertyName("role")]
    public string? Role { get; set; }

    [JsonPropertyName("name"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Name { get; set; }

    [JsonPropertyName("content"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Content { get; set; }

    [JsonPropertyName("tool_calls")]
    public IReadOnlyList<ToolCall>? ToolCalls { get; set; }
}

public class ToolCall
{
    [JsonPropertyName("id")]
    public string? Id { get; set; }

    [JsonPropertyName("function")]
    public FunctionCall? Function { get; set; }

    [JsonPropertyName("type")]
    public string? Type { get; set; }
}

public class Usage
{
    [JsonPropertyName("prompt_tokens")]
    public int PromptTokens { get; set; }

    [JsonPropertyName("completion_tokens")]
    public int CompletionTokens { get; set; }

    [JsonPropertyName("total_tokens")]
    public int TotalTokens { get; set; }
}

public class FunctionCall
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("arguments")]
    public string Arguments { get; set; } = string.Empty;
}

先试试不履行 function calling ,直接以 assistant 人物回复一句话

public class BypassHandler() : DelegatingHandler(new HttpClientHandler())
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var chatCompletion = new ChatCompletionResponse
        {
            Id = Guid.NewGuid().ToString(),
            Model = "fake-mode",
            Object = "chat.completion",
            Created = DateTimeOffset.Now.ToUnixTimeSeconds(),
            Choices =
               [
                   new()
                   {
                       Message = new ResponseMessage
                       {
                           Content = "自己着手,锦衣玉食",
                           Role = "assistant"
                       },
                       FinishReason = "stop"
                   }
               ]
        };

        var json = JsonSerializer.Serialize(chatCompletion, GetJsonSerializerOptions());
        return new HttpResponseMessage
        {
            Content = new StringContent(json, Encoding.UTF8, "application/json")
        };
    }
}

运转控制台程序,输出如下:

User > 请开灯
Assistant > 自己着手,锦衣玉食

成功呼应,到此,歪门邪道成功了一半。

接下来在之前创立的 chatCompletion 基础上增加针对 function calling 的 ToolCall 部分。

先准备好 ChangeState(bool newState) 的参数值

Dictionary<string, bool> arguments = new()
{
    { parameterName, true }
};

并将回复内容由 "自己着手,锦衣玉食" 改为 "客官,灯已开"

Message = new ResponseMessage
{
    Content = "客官,灯已开",
    Role = "assistant"
}

然后为 chatCompletion 创立 ToolCalls 实例用于呼应 function calling

var messages = chatCompletionRequest.Messages;
if (messages.First(x => x.Role == "user").Content.Contains("开灯") == true)
{
    chatCompletion.Choices[0].Message.ToolCalls = new List<ToolCall>()
    {
        new ToolCall
        {
            Id = Guid.NewGuid().ToString(),
            Type = "function",
            Function = new FunctionCall
            {
                Name = function.Name,
                Arguments = JsonSerializer.Serialize(arguments, GetJsonSerializerOptions())
            }
        }
    };
}

运转控制台程序看看作用

User > 请开灯
[开灯啦]
[开灯啦]
[开灯啦]
[开灯啦]
[开灯啦]
Assistant > 客官,灯已开

耶!成功开灯!可是,居然开了5次,差点把灯给开爆了。

BypassHandler 中打印一下恳求内容看看哪里出了问题

var json = await request.Content!.ReadAsStringAsync();
Console.WriteLine(json);

本来别离恳求/呼应了5次,第2次恳求开端,json 中 messages 部分多了 tool_callstool_call_id 内容

{
  "messages": [
    {
      "content": "\u5BA2\u5B98\uFF0C\u706F\u5DF2\u5F00",
      "tool_calls": [
        {
          "function": {
            "name": "LightPlugin-ChangeState",
            "arguments": "{\u0022newState\u0022:true}"
          },
          "type": "function",
          "id": "76f8dead-b5ad-4e6d-b343-7f78d68fac8e"
        }
      ],
      "role": "assistant"
    },
    {
      "content": "on",
      "tool_call_id": "76f8dead-b5ad-4e6d-b343-7f78d68fac8e",
      "role": "tool"
    }
  ]
}

这时茅塞顿开,之前 AI assistant 对 function calling 的呼应仅仅让 Plugin 履行对应的 function,assistant 还需求根据履行的成果决议下一下做什么,第2次恳求中的 tool_callstool_call_id 便是为了告知 assistant 履行的成果,所以,还需求针对这个恳求进行专门的呼应。

到了歪门邪道最终100米冲刺的时间!

RequestMessage 增加 ToolCallId 特点

public class RequestMessage
{
    [JsonPropertyName("role")]
    public string? Role { get; set; }

    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("content")]
    public string? Content { get; set; }

    [JsonPropertyName("tool_call_id")]
    public string? ToolCallId { get; set; }
}

BypassHandler 中呼应时判别一下 ToolCallId,假如是针对 Plugin 的 function 履行成果的恳求,只回来 Message.Content,不进行 function calling 呼应

var messages = chatCompletionRequest.Messages;
var toolCallId = "76f8dead- b5ad-4e6d-b343-7f78d68fac8e";
var toolCallIdMessage = messages.FirstOrDefault(x => x.Role == "tool" && x.ToolCallId == toolCallId);

if (toolCallIdMessage != null && toolCallIdMessage.Content == "on")
{
    chatCompletion.Choices[0].Message.Content = "客官,灯已开";
}
else if (messages.First(x => x.Role == "user").Content.Contains("开灯") == true)
{  
    chatCompletion.Choices[0].Message.Content = "";
    //..
}

改善代码完结,到了最终10米冲刺的时间,再次运转控制台程序

User > 请开灯
[开灯啦]
Assistant > 客官,灯已开

只要一次开灯,冲刺成功,歪门邪道走通,用这种方法体会一下 Semantic Kernel Plugin,也别有一番风味。

完好示例代码已上传到 github https://github.com/cnblogs-dudu/sk-plugin-sample-101

弥补:假如不运用 BypassHandler,直接接入 OpenAI,运转成果如下

User > 请开灯
[开灯啦]
Assistant > 灯现已打开了。

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=403

分享给朋友:

“歪门邪道:凭借 HttpClientHandler 阻拦恳求,体会 Semantic Kernel 插件” 的相关文章

机器学习 回归,理解与应用

机器学习 回归,理解与应用

机器学习回归:理解与应用在机器学习领域,回归分析是一种重要的预测方法,它用于预测一个或多个连续变量的值。本文将深入探讨回归分析的基本概念、常见类型、应用场景以及如何在实际项目中应用回归模型。一、回归分析的基本概念回归分析是一种统计方法,用于研究变量之间的关系。在回归分析中,我们通常将一个变量视为因变...

cdn机器学习,提升内容分发网络性能的新篇章

CDN(内容分发网络)与机器学习的结合正在推动内容分发技术的智能化和高效化。以下是CDN与机器学习结合的主要应用和研究进展:1. 性能预测与优化: AI算法的应用:AI算法,特别是机器学习和深度学习技术,能够处理和分析CDN系统产生的大量数据,如日志数据、用户行为数据和网络质量数据。这些技术可...

AI写ppt,高效与创意的完美结合

AI写ppt,高效与创意的完美结合

1. 确定PPT的主题和目标受众,以便AI为你生成更符合需求的内容。2. 提供关键信息点,例如:主要观点、论据、数据等,让AI为你组织内容。3. 选择合适的模板和设计风格,以提升PPT的美观度和易读性。4. 利用AI生成图片、图表等视觉元素,以丰富PPT内容。5. 根据实际情况,调整AI生成的文本,...

邹博 机器学习,机器学习领域的杰出讲师与研究者

邹博 机器学习,机器学习领域的杰出讲师与研究者

邹博是一位在机器学习领域有着丰富经验和深入研究的专业人士。他目前是中国科学院的副研究员,同时也是天津大学软件学院的创业导师,并在多个公司担任技术顾问。邹博的研究方向主要集中在机器学习、深度学习和计算几何等方面,这些技术被广泛应用于大型气象设备图像与文本挖掘、股票交易与预测、量子化学等领域。1. 视频...

机器学习实战,构建基于K-means算法的客户细分模型

机器学习实战,构建基于K-means算法的客户细分模型

1. 理解基本概念:在开始实战之前,需要理解机器学习的基本概念,如监督学习、非监督学习、强化学习等,以及常见的算法,如线性回归、决策树、支持向量机等。2. 选择工具和库:选择适合的编程语言和机器学习库,如Python的scikitlearn、TensorFlow、Keras等,或者R的caret、x...

学习机器人玩具,探索机器人玩具的乐趣与教育价值

学习机器人玩具,探索机器人玩具的乐趣与教育价值

学习机器人玩具是一种旨在教育儿童和青少年关于机器人技术和编程的玩具。这些玩具通常包括可编程的机器人,以及相关的软件和教材,以便用户可以学习如何编写代码来控制机器人的动作和功能。学习机器人玩具通常具有以下特点:1. 可编程性:用户可以使用编程语言或图形化编程界面来编写代码,以控制机器人的动作、传感器和...