一次失敗的嘗試: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 這三者的哪個環節。

這個問題沒有找到原因,暫且放一放。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章