一次失败的尝试: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
- 通过阿里云灵积 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
这三者的哪个环节。
这个问题没有找到原因,暂且放一放。
更新:后来向阿里云提交工单知道了答案—— DashScope 暂不支持 function calling