ChatUI:使用Gradio.NET为LLamaWorker快速创建大模型演示界面

news2024/12/23 11:30:23

Gradio.NET 是 Gradio 的.NET 移植版本。它是一个能够助力迅速搭建机器学习模型演示界面的库,其提供了简洁的 API,仅需寥寥数行代码就能创建出一个具备交互性的界面。在本篇文章中,我们将会阐述如何借助 Gradio.NET 为 LLamaWorker 快捷地创建一个大型模型演示界面。

1. 背景

前面一篇文章我们认识了 LLamaWorker 项目,它是一个专为 .NET 开发者设计的大型语言模型服务。LLamaWorker 提供了与 OpenAI 类似的 API,支持多模型切换、流式响应、嵌入支持等特性。此外,LLamaWorker 还提供了一个基于 Gradio.NET 的 UI 演示,使得开发者能够更快地体验和调试模型。

2. Gradio.NET 简介

Gradio.NET 是 Gradio 的.NET 移植版本。Gradio 作为一个开源 Python 包,允许为机器学习模型、API 或任何任意 Python 函数快速构建演示或 Web 应用程序,无需具备 JavaScript、CSS 经验。使用 Gradio,能够基于机器学习模型或数据科学工作流迅速创建一个用户友好的界面,让用户可以通过浏览器进行诸如拖放图像、粘贴文本、录制声音等操作,并与演示程序进行交互。

3. 为什么选择 Gradio.NET

LLamaWorker 是提供有 swagger 页面的 API 服务,但是 swagger 页面并不能直观地展示模型的效果。提供一个直观的演示界面,能够让用户更快地了解模型的效果,同时也能够帮助开发者更快地调试模型,是我一直在考虑的问题。

当然,选择技术框架是一个关键的决策。起初,我考虑使用 Vue3 从零开始搭建,但这需要耗费大量的时间和精力。恰好在这个时候,我发现了社区新推出的开源项目 Gradio.NET。

我抱着学习新技术的心态尝试了一下,同时也想为开发者们测试一下这个新框架,发现问题并提出改进的建议。对于初次接触 Gradio 的人,比如我来说,可能会在初期感到有些吃力。然而,如果之前就熟悉 Python 的 Gradio,那么使用 Gradio.NET 将会变得非常轻松。

需要注意的是,目前 Gradio.NET 仍在不断完善之中,还有许多库尚未完成迁移。但我相信,只要大家共同努力,积极参与建设,一定能够让 Gradio.NET 变得更加完善和强大。

4. 为 LLamaWorker 创建演示界面

接下来,我们将会为 LLamaWorker 创建一个简单的演示界面。整体代码包含注释不过 300 行,但却能够实现一个具有交互性的界面。在这个界面中,我们可以输入文本,然后点击“生成”按钮,即可获取模型的回复。

在 ChatUI 项目中,我们使用了 Gradio.NET 多个组件和相关功能,期间也发现并提交了多个 issues 到 Gradio.NET。对于学习 Gradio.NET 的同学来说,这个实际的使用案例将会非常有帮助。特别是刷新 Dropdown,网络请求,以及流式响应的处理等。

4.1. 服务设置

LLamaWorker 提供了API Key 的支持,并提供了模型配置信息获取的接口,在 ChatUI 项目中,我们将会使用这些接口来获取模型的配置信息。

在页面的顶部,我们设置了一个输入框用于输入 LLamaWorker 服务的 URL,一个输入框用于输入 API Key,一个按钮用于获取模型配置信息,以及一个下拉框用于选择模型。

gr.Markdown("# LLamaWorker");
Textbox input,token;
Dropdown model;
Button btnset;

using (gr.Row())
{
    input = gr.Textbox("http://localhost:5000", placeholder: "LLamaWorker Server URL", label: "Server");
    token = gr.Textbox(placeholder: "API Key", label: "API Key",  maxLines:1, type:TextboxType.Password);
    btnset = gr.Button("Get Models", variant: ButtonVariant.Primary);
    model = gr.Dropdown(choices: [], label: "Model Select", allowCustomValue:true);
}

在上面的代码中,我们设置了一个输入框用于输入 API Key,并惊奇设置为密码输入框 TextboxType.Password,以便隐藏输入的内容。这里的 Dropdown 组件我们没有设置选项,并且允许其可以获取用户的自定义值 allowCustomValue:true,方便用户输入自定义的模型名称,同时也可以使 ChatUI 项目调用其他的服务,比如阿里灵积的大模型服务等。

请添加图片描述

上图展示的是在移动端的界面,Gradio.NET 会自动处理流式布局,使得界面在不同设备上都能够正常显示。

在设置好基础界面后,我们需要为按钮添加点击事件,以便获取模型配置信息。在 Gradio.NET 中,可以通过 ButtonClick 事件来实现。

btnset?.Click(update_models, inputs: [input, token], outputs: [model]);

在点击按钮后,会调用 update_models 方法,该方法会向 LLamaWorker 服务发送请求,获取模型配置信息,并更新下拉框的选项。

static async Task<Output> update_models(Input input)
{
    string server = Textbox.Payload(input.Data[0]);
    string token = Textbox.Payload(input.Data[1]);
    if (server == "")
    {
        throw new Exception("Server URL cannot be empty.");
    }
    if (!string.IsNullOrWhiteSpace(token))
    {
        Utils.client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    }
    var res = await Utils.client.GetFromJsonAsync<ConfigModels>(server + "/models/config");
    if (res?.Models == null || res.Models.Count==0)
    {
        throw new Exception("Failed to fetch models from the server.");
    }
    Utils.config = res;
    var models = res.Models.Select(x => x.Name).ToList();
    return gr.Output(gr.Dropdown(choices: models,value: models[res.Current], interactive: true));
}

update_models 方法中,我们首先获取输入的服务 URL 和 API Key,然后向服务发送请求获取模型配置信息。如果请求成功,我们将会更新下拉框的选项。在这个过程中,我们还会根据服务返回的当前模型,设置下拉框的默认值。

这里的网络请求使用了Utils类中HttpClient的单例模式,以便在整个项目中共享一个HttpClient实例。HttpClient实例是设计为可以被多个请求重用的,这有助于减少资源消耗和提高应用程序的性能。

4.2. Dropdown 组件的模型切换

在获取到模型配置信息后,我们需要为下拉框的选项添加点击事件,以便切换模型。在 Gradio.NET 中,可以通过 DropdownChange 事件来实现。

model?.Change(change_models, inputs: [input, model], outputs: [model]);

在点击下拉框选项后,会调用 change_models 方法,该方法会向 LLamaWorker 服务发送请求,切换模型。

static async Task<Output> change_models(Input input)
{
    string server = Textbox.Payload(input.Data[0]);
    string model = Dropdown.Payload(input.Data[1]).Single();

    var models = Utils.config?.Models?.Select(x => x.Name).ToList();
    // 未使用服务端模型配置,允许自定义模型
    if (models == null)
    {
        return gr.Output(gr.Dropdown(choices: [model], value: model, interactive: true, allowCustomValue: true));
    }
    if (server == "")
    {
        throw new Exception("Server URL cannot be empty.");
    }

    // 取得模型是第几个
    var index = models.IndexOf(model);
    if (index == -1)
    {
        throw new Exception("Model not found in the list of available models.");
    }
    if (Utils.config.Current == index)
    {
        // 没有切换模型
        return gr.Output(gr.Dropdown(choices: models, value: model, interactive: true));
    }
    var res = await Utils.client.PutAsync($"{server}/models/{index}/switch", null);
    // 请求失败
    if (!res.IsSuccessStatusCode)
    {
        // 错误信息未返回
        gr.Warning("Failed to switch model.");
        await Task.Delay(2000);
        return gr.Output(gr.Dropdown(choices: models, value: models[Utils.config.Current], interactive: true));
    }
    Utils.config.Current = index;
    return gr.Output(gr.Dropdown(choices: models, value: model, interactive: true));
}

change_models 方法中,我们首先获取模型配置信息,然后获取输入的服务 URL 和模型名称,向服务发送请求切换模型。如果请求成功,我们将会更新下拉框的选项。同时在不存在服务端模型配置的情况下,我们允许用户自定义模型。

这里需要注意的是,在切换失败的情况下,我们会展示一个警告信息,并在2秒后恢复下拉框的选项。但是,恢复下拉框的选项会重复调用Change事件,这样会造成Warning提示框不显示,所以需要在Warning提示框显示后延迟2秒再恢复下拉框的选项,重复调用倒是不算大问题。

4.3. 模型交互

在设置好服务和模型切换后,我们添加一个Tab组件,用于展示模型的不同能力对话和文本生成。

using (gr.Tab("Chat"))
{
    // Chat 交互界面组件
}
using (gr.Tab("Completion"))
{
    // Completion 交互界面组件
}

在 Chat 交互界面中,我们可以直接使用 Chatbot 组件,用于展示对话消息列表,并添加一个输入框用于输入文本,同时提供三个按钮用于发送文本、重新生成和清空对话。

Chatbot chatBot = gr.Chatbot(label: "LLamaWorker Chat", showCopyButton: true, placeholder: "Chat history",height:520);
Textbox userInput = gr.Textbox(label: "Input", placeholder: "Type a message...");

Button sendButton, resetButton, regenerateButton;

using (gr.Row())
{
    sendButton = gr.Button("✉️ Send", variant: ButtonVariant.Primary);
    regenerateButton = gr.Button("🔃 Retry", variant: ButtonVariant.Secondary);
    resetButton = gr.Button("🗑️  Clear", variant: ButtonVariant.Stop);
}

接下来我们添加三个按钮的点击事件,以便发送文本、重新生成和清空对话。

sendButton?.Click(streamingFn: i =>
{
    string server = Textbox.Payload(i.Data[0]);
    string token = Textbox.Payload(i.Data[3]);
    string model = Dropdown.Payload(i.Data[4]).Single();
    IList<ChatbotMessagePair> chatHistory = Chatbot.Payload(i.Data[1]);
    string userInput = Textbox.Payload(i.Data[2]);
    return ProcessChatMessages(server, token, model, chatHistory, userInput);
}, inputs: [input, chatBot, userInput, token, model], outputs: [userInput, chatBot]);
regenerateButton?.Click(streamingFn: i =>
{
    string server = Textbox.Payload(i.Data[0]);
    string token = Textbox.Payload(i.Data[2]);
    string model = Dropdown.Payload(i.Data[3]).Single();
    IList<ChatbotMessagePair> chatHistory = Chatbot.Payload(i.Data[1]);
    if (chatHistory.Count == 0)
    {
        throw new Exception("No chat history available for regeneration.");
    }
    string userInput = chatHistory[^1].HumanMessage.TextMessage;
    chatHistory.RemoveAt(chatHistory.Count - 1);
    return ProcessChatMessages(server, token, model, chatHistory, userInput);
}, inputs: [input, chatBot, token, model], outputs: [userInput, chatBot]);
resetButton?.Click(i => Task.FromResult(gr.Output(Array.Empty<ChatbotMessagePair>(), "")), outputs: [chatBot, userInput]);

在点击按钮后,会调用 ProcessChatMessages 方法,该方法会向 LLamaWorker 服务发送请求,获取模型的回复,并更新对话消息列表。

static async IAsyncEnumerable<Output> ProcessChatMessages(string server, string token, string model, IList<ChatbotMessagePair> chatHistory, string message)
{
    if (message == "")
    {
        yield return gr.Output("", chatHistory);
        yield break;
    }

    // 添加用户输入到历史记录
    chatHistory.Add(new ChatbotMessagePair(message, ""));

    // sse 请求
    var request = new HttpRequestMessage(HttpMethod.Post, $"{server}/v1/chat/completions");
    request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream"));
    if (!string.IsNullOrWhiteSpace(token))
    {
        Utils.client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    }

    var messages =new List<ChatCompletionMessage>();
    foreach (var item in chatHistory)
    {
        messages.Add(new ChatCompletionMessage
        {
            role = "user",
            content = item.HumanMessage.TextMessage
        });
        messages.Add(new ChatCompletionMessage
        {
            role = "assistant",
            content = item.AiMessage.TextMessage
        });
    }
    messages.Add(new ChatCompletionMessage
    {
        role = "user",
        content = message
    });


    request.Content = new StringContent(JsonSerializer.Serialize(new ChatCompletionRequest
    {
        stream = true,
        messages = messages.ToArray(),
        model = model,
        max_tokens = 1024,
        temperature = 0.9f,
        top_p = 0.9f,
    }), Encoding.UTF8, "application/json");

    using var response = await Utils.client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    response.EnsureSuccessStatusCode();
    using (var stream = await response.Content.ReadAsStreamAsync())
    using (var reader = new System.IO.StreamReader(stream))
    {
        while (!reader.EndOfStream)
        {
            var line = await reader.ReadLineAsync();
            if (line.StartsWith("data:"))
            {
                var data = line.Substring(5).Trim();

                // 结束
                if(data == "[DONE]")
                {
                    yield break;
                }

                // 解析返回的数据
                var completionResponse = JsonSerializer.Deserialize<ChatCompletionChunkResponse>(data);
                var text = completionResponse?.choices[0]?.delta?.content;
                if (string.IsNullOrEmpty(text))
                {
                    continue;
                }
                chatHistory[^1].AiMessage.TextMessage += text;
                yield return gr.Output("", chatHistory);
            }
        }
    }
}

ProcessChatMessages 方法中,我们首先获取输入的服务 URL、API Key、对话消息列表和文本,然后向服务发送请求获取模型的回复。在这个过程中,我们使用了 SSE 请求,以便实现流式响应。在获取到模型的回复后,我们将会更新对话消息列表。

对于文本生成界面,我们可以直接使用 Textbox 组件,用于输入文本,同时添加一个按钮用于生成文本。其相关的事件处理和流程与 Chat 交互界面类似,这里不再赘述。完整的代码可以在 LLamaWorker 项目的 ChatUI 中查看。

5. 效果

在运行 LLamaWorker 服务后,我们可以在 ChatUI 项目中输入服务 URL 和 API Key(若有配置),然后点击“Get Models”按钮,即可获取模型配置信息。接着,我们可以选择模型,然后在 Chat 交互界面中输入文本,点击“Send”按钮,即可获取模型的回复。

当然你也可以选择其他服务,比如阿里灵积的大模型服务,只需要修改服务 URL:https://dashscope.aliyuncs.com/compatible-mode 和 API Key,通过手动输入你要体验的模型,如 “qwen-long” 即可体验阿里灵积的大模型服务。

请添加图片描述

6. 总结

在本篇文章中,我们阐述了如何使用 Gradio.NET 为 LLamaWorker 快捷地创建一个大型模型演示界面。通过 Gradio.NET,我们可以快速搭建一个具备交互性的界面,帮助开发者更快地了解和体验模型的效果。同时,我们还展示了如何使用 Gradio.NET 的多个组件和相关功能,以及如何处理网络请求和流式响应。希望这个实际的使用案例能够帮助大家更好地学习和使用 Gradio.NET。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1865885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ora-15025 ora-27041问题处理

这个问题先排查 [oracleracdg2-2 ~]$ cd $ORACLE_HOME/bin [oracleracdg2-2 bin]$ ls -ld oracle -rwsr-s--x 1 oracle oinstall 239626641 Jun 25 19:09 oracle 正常的属组是 [gridracdg2-1 ~]$ setasmgidwrap -o /u01/app/oracle/product/11.2.0.4/dbhome_1/bin/oracle […

OZON跨境卖家爆款产品有哪些

OZON跨境卖家爆款产品有哪些&#xff1f;国内的Ozon跨境卖家做这几个品&#xff0c;不爆都难&#xff01; Top1 太阳镜 Очки солнцезащитные 商品id&#xff1a;1556874194 月销量&#xff1a;1095 OZON跨境卖家爆款产品工具&#xff1a;D。DDqbt。COm/…

牛客周赛 Round 48 解题报告 | 珂学家

前言 题解 这场感觉有点难&#xff0c;D完全没思路, EF很典&#xff0c;能够学到知识. E我的思路是容斥贡献&#xff0c;F很典&#xff0c;上周考过一次&#xff0c;引入虚拟节点质数(有点像种类并查集类似的技巧). 欢迎关注 珂朵莉 牛客周赛专栏 珂朵莉 牛客小白月赛专栏 …

深度相机辅助导航避障(三):地面点云滤除

前面的章节介绍了坐标变换,以及如何设置深度相机的坐标变换。那就可以很直观从机器人的坐标系对深度相机扫描到的障碍物点云进行处理。 在实际应用中,机器人正确估计周围地形,对于道路的可通过性、路径规划和障碍物检测等方面都很重要。那么在获取深度相机点云数据后就得准…

pytorch基础知识Tensor算术运算

1、Tensor的基本概念 标量是零维的张量&#xff0c;向量是一维的张量&#xff0c;矩阵是二维的张量 2、Tensor的创建 import torch"""常见的几个tensor创建""" a torch.Tensor([[1,2],[3,4]]) #2行2列的 print(a, a.type()) print(torch.on…

在前端开发过程中如果函数参数很多,该如何精简

1. 在前端开发过程中如果函数参数很多&#xff0c;该如何精简 1.1. 对象参数&#xff08;对象字面量&#xff09;&#xff1a;1.2. 默认参数和解构赋值&#xff1a;1.3. 使用类或构造函数&#xff1a;1.4. 利用闭包或者高阶函数&#xff1a;1.5. 利用ES6的扩展运算符&#xff1…

利用ChatGPT优化程序员工作流程:实用案例分享

近年来&#xff0c;人工智能技术的迅猛发展给各行各业带来了翻天覆地的变化。作为其中的一员&#xff0c;程序员在工作中也受益匪浅。其中&#xff0c;ChatGPT的出现&#xff0c;更是成为优化程序员工作流程的得力助手。本文将通过多个实用案例&#xff0c;分享如何利用ChatGPT…

数据质量低下会造成什么后果?应从哪些维度衡量数据质量?

大数据时代的到来&#xff0c;预示着前所未有的商业机遇和洞察力。然而&#xff0c;要将这些海量数据中蕴含的巨大价值转化为实际的业务成果&#xff0c;一个关键的前提条件是必须确保所收集数据的质量。数据质量是大数据价值链上的第一道关卡&#xff0c;它的高低直接关系到数…

OpenCV中掩膜(mask)图像的创建和使用

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 功能描述 掩模图像&#xff08;Mask Image&#xff09;是一种特殊类型的形象数据&#xff0c;在图像处理和计算机视觉中扮演着重要角色。它通常是一个二维数组…

uniapp H5端使用百度地图

1、登录百度地图开放平台 https://lbsyun.baidu.com/&#xff08;没有账号则先去创建一个百度账号&#xff09; 2、进入百度地图开放平台控制台&#xff08;导航栏“控制台”&#xff09;&#xff0c;点击“应用管理”-“我的应用” 3、选择“创建应用”&#xff0c;应用模块选…

还不会找1688对标竞品?那你别说自己是做运营的

如何找正确的对标竞品 找到竞品→模仿竞品→学习竞品 找到正确的对标竞品&#xff0c;是至关重要的一个环节&#xff0c;如果你想要把流量做起来&#xff0c;却没找到正确的对标竞品是完全不可能的。因为你没有对标&#xff0c;你就完全不知道同行做什么动作才有流量&#xf…

Opencv学习项目2——pytesseract

上一次我们使用pytesseract.image_to_boxes来检测字符&#xff0c;今天我们使用pytesseract.image_to_data来检测文本并显示 实战教程 和上一次一样&#xff0c;添加opencv-python和pytesseract库 首先我们先来了解一下pytesseract.image_to_data pytesseract.image_to_data(…

k8s手撕架构图+详解

“如果您在解决类似问题时也遇到了困难&#xff0c;希望我的经验分享对您有所帮助。如果您有任何疑问或者想分享您的经历&#xff0c;欢迎在评论区留言&#xff0c;我们可以一起探讨解决方案。祝您在编程路上顺利前行&#xff0c;不断突破技术的难关&#xff0c;感谢您的阅读&a…

MySQL 超出月份最大日期(工作总结)

前几天帮同事修改了一个bug&#xff0c;这个bug是怎么造成的呢。先来看需求&#xff0c;系统需要统计某个月份的数据。很简单的一个需求。 同事的写的MySQL语句 SELECTREPLACE(FORMAT(sum(count_value),2), ,, ) as value,<if test"type day">count_date as…

Vue-内容渲染,属性渲染指令

内容渲染 在Vue中渲染元素&#xff0c;用双花括号{{}}的语法进行插值&#xff0c;称之为插值表达式 双花括号会渲染hi里面的值 <body><div id"app">{{hi}}</div> <script>const vm{data(){return{hi:hello world}}}const appVue.createAp…

【containerd】Containerd高阶命令行工具nerdctl

前言 对于习惯了使用docker cli的用户来说&#xff0c;containerd的命令行工具ctr使用起来不是很顺手&#xff0c;此时别慌&#xff0c;还有另外一个命令行工具项目nerdctl可供我们选择。 nerdctl是一个与docker cli风格兼容的containerd的cli工具。 nerdctl已经作为子项目加入…

u盘插到另一台电脑上数据丢失怎么办?提供实用的解决方案

在现代数字化生活中&#xff0c;U盘作为一种便携式存储设备&#xff0c;承载着我们重要的数据和信息。然而&#xff0c;有时当我们将U盘插入另一台电脑时&#xff0c;可能会遇到数据丢失的棘手问题。这可能是由于多种原因造成的&#xff0c;那么&#xff0c;U盘插到另一台电脑上…

cs与msf权限传递,与mimikatz抓取win2012明文密码

CS与MSF的权限互相传递抓取windows2012的明文密码 CS与MSF的权限互相传递 1、启动cs服务端 2、客户端连接 3、配置监听&#xff0c;并设置监听端口为9999 4、生成脚本 5、开启服务&#xff0c;下载并运行木马 已获取权限 6、进入msf并设置监听 7、cs新建监听&#xff0c;与m…

20240626让飞凌的OK3588-C开发板在相机使用1080p60分辨率下预览

20240626让飞凌的OK3588-C开发板在相机使用1080p60分辨率下预览 2024/6/26 15:15 4.2.1 全编译测试 在源码路径内&#xff0c;提供了编译脚本 build.sh&#xff0c;运行该脚本对整个源码进行编译&#xff0c;需要在终端切换到解压 出来的源码路径&#xff0c;找到 build.sh 文件…

海纳斯 hinas 的hi3798mv100 华为悦盒 6108v9 安装wifi模块

hi3798mv100安装wifi模块 1.执行脚本 &#xff0c;执行完毕后重启服务器2. 继续执行脚本3.检查网卡驱动安装是否正确4.查看网卡安装状态5.连接wifi结尾 1.执行脚本 &#xff0c;执行完毕后重启服务器 bash <(curl -sSL https://gitee.com/xjxjin/scripts/raw/main/install_…