一次失败的尝试:one-api + dashscope + qwen-max 运行 Semantic Kernel 插件

原本打算通过 OpenAIChatCompletionService + one-api + DashScope + qwen-max(通义千问千亿级大模型)运行一个非常简单的 Semantic Kernel plugin,却没有成功,不确定是 one-api 还是 DashScope(阿里云模型服务灵积) 或者通义千问模型不支持 Semantic Kernel 所使用的 function calling 请求,在这篇博文中记录一下尝试的过程。

准备 dashscope 与 one-api

准备 Semantic Kernel 源码

  • 从 github 签出 Semantic Kernel 源码并切换到 dotnet-1.4.0 分支
git clone https://github.com/microsoft/semantic-kernel.git
git checkout dotnet-1.4.0

修改测试代码

  • 修改 Examples.Plugin 测试代码
  • 删除 Kernel.CreateBuilder() 之前的10行代码
  • 用下面2行代码替换 .AddAzureOpenAIChatCompletion 部分的代码
builder.Services.AddOpenAIChatCompletion("qwen-max", "sk-one-api-token");
builder.Services.ConfigureHttpClientDefaults(b =>
    b.ConfigurePrimaryHttpMessageHandler(() => new OneApiRedirectingHandler()));
  • 添加 OneApiRedirectingHandler 实现代码
public class OneApiRedirectingHandler() : DelegatingHandler(new HttpClientHandler())
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.RequestUri = new UriBuilder(request.RequestUri!) { Scheme = "http", Host = "one-api", Port = 3000 }.Uri;
        return base.SendAsync(request, cancellationToken);
    }
}

支持插件调用的关键代码

OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};

完整代码见 https://www.cnblogs.com/dudu/articles/18017439

  • 实现 ConsoleTestoutputHelper,为了命令行跑测试时能在控制台看到输出,在 BaseTest 中添加下面的实现代码
private class ConsoleTestoutputHelper : ITestOutputHelper
{
    public void WriteLine(string message)
    {
        Console.WriteLine(message);
    }

    public void WriteLine(string format, params object[] args)
    {
        Console.WriteLine(format, args);
    }
}

并且在 BaseTest 的构造函数中使用 ConsoleTestoutputHelper

protected BaseTest(ITestOutputHelper output)
{
    this.Output = new ConsoleTestoutputHelper();
}

运行测试

dotnet test --filter "FullyQualifiedName=Examples.Plugin.RunAsync"

运行测试时 prompt 是自动输入的,第1次输入的是 Hello,第2次输入的 Can you turn on the lights,控制台输出如下

======== Plugin ========
User >
Assistant > Hello! How can I help you today? Is there something you'd like to talk about or ask me? I'm here to provide information and answer any questions you may have.
User >
Assistant > I'm sorry, but as a text-based AI language model, I don't have direct access to your physical environment or any connected devices. I exist solely to provide information and engage in conversations with you. If you're looking to control smart lights, you might want to use voice commands through a compatible virtual assistant like Amazon Alexa, Google Assistant, or Apple Siri if your lights are set up for that kind of control.
User >

Passed!  - Failed:     0, Passed:     1, Skipped:     0, Total:     1, Duration: < 1 ms - DocumentationExamples.dll (net6.0)

虽然显示测试通过,但插件没有被执行,LightPluginChangeState 方法并没有被调用。

LightPlugin 的实现代码:

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

    [KernelFunction]
    [Description("Gets the state of the light.")]
    public string GetState() => IsOn ? "on" : "off";

    [KernelFunction]
    [Description("Changes the state of the light.'")]
    public string ChangeState(bool newState)
    {
        this.IsOn = newState;
        var state = GetState();

        Console.WriteLine($"[Light is now {state}]");

        return state;
    }
}

排查问题:打印 SemanticKernel 调用 api 时的请求内容

OneApiRedirectingHandler 的实现代码中添加 Console.WriteLine 代码

public class OneApiRedirectingHandler() : DelegatingHandler(new HttpClientHandler())
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.RequestUri = new UriBuilder(request.RequestUri!) { Scheme = "http", Host = "one-api", Port = 3000 }.Uri;
        Console.WriteLine(await request.Content.ReadAsStringAsync());
        return await base.SendAsync(request, cancellationToken);
    }
}

再次运行测试,发现 api 请求中包含下面的 json

{
  "tools": [
    {
      "function": { "name": "LightPlugin-GetState", "description": "Gets the state of the light.", "parameters": { "type": "object", "required": [], "properties": {} } },
      "type": "function"
    },
    {
      "function": {
        "name": "LightPlugin-ChangeState",
        "description": "Changes the state of the light.\u0027",
        "parameters": { "type": "object", "required": ["newState"], "properties": { "newState": { "type": "boolean" } } }
      },
      "type": "function"
    }
  ],
  "tool_choice": "auto"
}

这是让大模型通过 function calling 执行 plugin 中 [KernelFunction] 方法的关键,却没有起作用,不知道问题出在 one-api, dashscope, qwen-max 这三者的哪个环节。

这个问题没有找到原因,暂且放一放。

更新:后来向阿里云提交工单知道了答案—— DashScope 暂不支持 function calling

posted @ 2024-02-17 19:33  dudu  阅读(215)  评论(0编辑  收藏  举报