原本打算通過 OpenAIChatCompletionService + one-api + DashScope + qwen-max(通義千問千億級大模型)運行一個非常簡單的 Semantic Kernel plugin,卻沒有成功,不確定是 one-api 還是 DashScope(阿里雲模型服務靈積) 或者通義千問模型不支持 Semantic Kernel 所使用的 function calling 請求,在這篇博文中記錄一下嘗試的過程。
準備 dashscope 與 one-api
- 通過阿里雲靈積 DashScope 控制檯創建 api key
- 部署 one-api(詳見之前的博文 藉助 one-api 調用阿里雲靈積 DashScope api)
- 選用通義千問
qwen-max
模型,準備好 one-api 的token
準備 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)
雖然顯示測試通過,但插件沒有被執行,LightPlugin
的 ChangeState
方法並沒有被調用。
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
這三者的哪個環節。
這個問題沒有找到原因,暫且放一放。