深入解析Semantic Kernel的编码误区:如何让本地模型“不再犯傻”

        Semantic Kernel在AI领域的应用越来越广泛。然而,在使用Semantic Kernel时,如果不注意一些细节问题,可能会导致你的模型表现异常,甚至出现“胡说八道”的情况。今天,我将分享一个关于使用Semantic Kernel的小细节,这个问题曾让我一度陷入困惑,幸好最终找到了问题的根源。

问题背景

        在我的一个项目中,我遇到了一个奇怪的问题:当我使用OpenAI时,模型表现非常智能,但是一旦切换到本地模型,输出结果就变得非常“弱智”。起初,我怀疑这是模型自身的问题,但通过使用Postman进行调试,发现并非如此。

        为了排查问题,我决定从请求报文入手,使用HttpClientHandler进行请求拦截。这一步非常关键,揭示了问题的所在。

抓取请求报文

        首先,我们通过如下代码来创建一个OpenAI的HttpClientHandler,用以拦截和修改请求:

var handler = new OpenAIHttpClientHandler();
services.AddTransient<Kernel>((serviceProvider) =>
{
    if (_kernel == null)
    {
        _kernel = Kernel.CreateBuilder()
            .AddOpenAIChatCompletion(
                modelId: OpenAIOption.ChatModel,
                apiKey: OpenAIOption.Key,
                httpClient: new HttpClient(handler)
)
            .Build();
    }
    return _kernel;
});

然后,通过自定义的OpenAIHttpClientHandler来拦截和修改请求内容。代码如下:

public class OpenAIHttpClientHandler : HttpClientHandler
{
    private string _endPoint { get; set; }

    public OpenAIHttpClientHandler(string endPoint)
    {
        this._endPoint = endPoint;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        UriBuilder uriBuilder;
        Regex regex = new Regex(@"(https?)://([^/:]+)(:\d+)?/(.*)");
        Match match = regex.Match(_endPoint);

        if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development" && request.Content != null)
        {
            string requestBody = await request.Content.ReadAsStringAsync();
            //便于调试查看请求prompt
            Log.Information(requestBody);
        }

        // Modify request based on endpoint
        if (match.Success)
        {
            string xieyi = match.Groups[1].Value;
            string host = match.Groups[2].Value;
            string port = match.Groups[3].Value;
            port = string.IsNullOrEmpty(port) ? port : port.Substring(1);
            var hostnew = string.IsNullOrEmpty(port) ? host : $"{host}:{port}";

            switch (request.RequestUri.LocalPath)
            {
                case "/v1/chat/completions":
                    uriBuilder = new UriBuilder(request.RequestUri)
                    {
                        Scheme = $"{xieyi}://{hostnew}/",
                        Host = host,
                        Path = $"{route}v1/chat/completions",
                    };
                    if (port.ConvertToInt32() != 0) uriBuilder.Port = Convert.ToInt32(port);
                    request.RequestUri = uriBuilder.Uri;
                    break;

                case "/v1/embeddings":
                    uriBuilder = new UriBuilder(request.RequestUri)
                    {
                        Scheme = $"{xieyi}://{host}/",
                        Host = host,
                        Path = $"{route}v1/embeddings",
                    };
                    if (port.ConvertToInt32() != 0) uriBuilder.Port = Convert.ToInt32(port);
                    request.RequestUri = uriBuilder.Uri;
                    break;
            }
        }

        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        return response;
    }
}
发现问题:Unicode编码

图片

图片

        通过拦截并查看请求报文内容,我发现请求中的内容并非直接的中文,而是Unicode编码。这对OpenAI服务而言,并不会影响识别和处理,但对本地模型来说,表现出明显的智力差异。

        于是,我决定对Unicode编码进行解码,通过下面的方法来实现:

public static string Unescape(this string value)
{
    if (value.IsNull()) return "";

    try
    {
        Formatting formatting = Formatting.None;
        object jsonObj = JsonConvert.DeserializeObject(value);
        string unescapeValue = JsonConvert.SerializeObject(jsonObj, formatting);
        return unescapeValue;
    }
    catch (Exception ex)
    {
        Log.Error(ex.ToString());
        return "";
    }
}

接下来,我们对HttpClientHandler进行修改,以便在发送请求前进行解码:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    UriBuilder uriBuilder;
    Regex regex = new Regex(@"(https?)://([^/:]+)(:\d+)?/(.*)");
    Match match = regex.Match(_endPoint);
    string guid = Guid.NewGuid().ToString();
    var mediaType = request.Content.Headers.ContentType.MediaType;
    string requestBody = (await request.Content.ReadAsStringAsync()).Unescape();
    var uncaseBody = new StringContent(requestBody, Encoding.UTF8, mediaType);
    request.Content = uncaseBody;

    if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Production")
    {
        Log.Information("{Message}", $"【模型服务接口调用-{guid},host:{_endPoint}】: {Environment.NewLine}{requestBody}");
    }

    // Modify request based on endpoint
    if (match.Success)
    {
        string xieyi = match.Groups[1].Value;
        string host = match.Groups[2].Value;
        string port = match.Groups[3].Value;
        port = string.IsNullOrEmpty(port) ? port : port.Substring(1);
        var hostnew = string.IsNullOrEmpty(port) ? host : $"{host}:{port}";

        switch (request.RequestUri.LocalPath)
        {
            case "/v1/chat/completions":
                uriBuilder = new UriBuilder(request.RequestUri)
                {
                    Scheme = $"{xieyi}://{hostnew}/",
                    Host = host,
                    Path = $"{route}v1/chat/completions",
                };
                if (Convert.ToInt32(port) != 0) uriBuilder.Port = Convert.ToInt32(port);
                request.RequestUri = uriBuilder.Uri;
                break;

            case "/v1/embeddings":
                uriBuilder = new UriBuilder(request.RequestUri)
                {
                    Scheme = $"{xieyi}://{host}/",
                    Host = host,
                    Path = $"{route}v1/embeddings",
                };
                if (Convert.ToInt32(port) != 0) uriBuilder.Port = Convert.ToInt32(port);
                request.RequestUri = uriBuilder.Uri;
                break;
        }
    }

    HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
    
    if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Production")
    {
        string responseContent = response.Content.ReadAsStringAsync().Result.Unescape();
        Log.Information("{Message}", $"【模型服务接口返回-{guid},host:{_endPoint}】: {Environment.NewLine}{responseContent}");
    }
    
    return response;
}
最终测试

图片

        经过以上调整后,我们再次验证请求内容,可以发现请求已经恢复为正常的中文编码,本地模型的表现也随之恢复正常。

        接下来,我们来探究一下为什么Semantic Kernel在传输过程中会使用Unicode编码。目前我的猜测是,在使用Function Call时,函数参数可能包含各种不规则的JSON数据。这些数据在进行序列化和反序列化时可能会引发问题,因此,Semantic Kernel可能为了确保数据的完整性和一致性,选择将参数进行Unicode编码后再进行解码处理。这样一来可以减少因不规则JSON导致的错误。

总结

        通过修改请求内容的编码方式,我们成功解决了由于Unicode编码导致本地模型表现不佳的问题。这次的经验告诉我,编码方式在AI模型中的影响不可忽视。希望通过本文的分享,能够帮助到遇到类似问题的开发者,提升你们使用Semantic Kernel的体验。

        如果你有任何问题或想法,欢迎关注我的公众号加入我们的交流群,与我和其他开发者一起探讨更多技术细节。我们下期再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许泽宇的技术分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值