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

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。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 基础 (标准库):堆 heap

1. 官方文档 heapq --- 堆队列算法 — Python 3.12.4 文档 2. 相关概念 堆 heap 是一种具体的数据结构&#xff08;concrete data structures&#xff09;&#xff1b;优先级队列 priority queue 是一种抽象的数据结构&#xff08;abstract data structures&#xff09;&…

【数据结构】——链表经典OJ(leetcode)

文章目录 一、 相交链表二、 反转链表三、 回文链表四、 环形链表五、 环形链表 II六、 合并两个有序链表七、 两数相加八、 删除链表的倒数第N个节点九、 随机链表的复制 一、 相交链表 双指针法 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListN…

MySQL进阶-索引-使用规则-最左前缀法则和范围查询

文章目录 1、最左前缀法则2、启动mysql3、查询tb_user4、查看tb_user的索引5、执行计划 profession 软件工程 and age31 and status 06、执行计划 profession 软件工程 and age317、执行计划 profession 软件工程8、执行计划 age31 and status 09、执行计划 status 010、执行…

直流电机双闭环调速Simulink仿真

直流电机参数&#xff1a; 仿真模型算法介绍&#xff1a; 1&#xff09;三相整流桥&#xff0c;采用半控功率器件SCR晶闸管&#xff1b; 2&#xff09;采用转速环、电流环 双闭环控制算法&#xff1b; 3&#xff09;外环-转速环&#xff0c;采用PI 比例积分控制&#xff1b;…

通信系统网络架构_3.移动通信网络架构

移动通信网为移动互联网提供了强有力的支持&#xff0c;尤其是5G网络为个人用户、垂直行业等提供了多样化的服务。以下从业务应用角度给出面向5G网络的组网方式。 1.5GS与DN互连 5GS&#xff08;5G System&#xff09;在为移动终端用户&#xff08;User Equipment&#xff0c;…

搜维尔科技:【研究】触觉手套比控制器更能带来身临其境、更安全、更高效的虚拟体验

自然交互可提高VR模拟的有效性。研究表明&#xff0c;触觉手套比控制器更能带来身临其境、更安全、更高效的虚拟体验。 以下是验证 医疗培训中的触觉技术 “ 95.5%的参与者表示触摸是 XR 教育的重要组成部分&#xff0c;90.9% 的参与者表示 XR 触觉将提供一个安全的学习场所。…

eNSP中ACL访问控制表的配置和使用

一、拓扑图 1.新建拓扑图 2.PC端配置 PC1: PC2: PC3: 二、基本命令配置 1.S1配置 <Huawei>system-view [Huawei]sysname S1 [S1]vlan 10 [S1-vlan10]vlan 20 [S1-vlan20]vlan 30 [S1-vlan30]quit [S1]interface Vlanif 10 [S1-Vlanif10]ip address 192.168.10…

黑马点评06短信登录-用户请求和会话管理过程

用户请求发送&#xff1a; 用户的浏览器向服务器发送请求&#xff08;例如&#xff0c;访问网页或提交表单&#xff09;。请求头包含之前存储在浏览器中的Cookie&#xff0c;其中包括会话ID&#xff08;Session ID&#xff09;。 服务器接收请求&#xff1a; 服务器接收到用户的…

【PythonWeb开发】Flask自定义模板路径和静态资源路径

在大型的 Flask 项目中&#xff0c;确实可能会有多个子应用&#xff08;Blueprints&#xff09;&#xff0c;每个子应用可能都有自己的静态文件和模板。为了更好地管理和组织这些资源&#xff0c;可以使用static_folder 和template_folder 属性来统一管理。必须同时设置好主应用…

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

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

鸿蒙系统最简单安装谷歌服务及软件的方法

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 近日&#xff0c;华为开发者大会在东莞松山湖召开&#xff0c;发布了盘古大模型5.0和纯血版的鸿蒙 HarmonyOS NEXT 全场景智能操作系统&#xff0c;而根据研究机构 Counterpoint Resea…

Unity关于Addressables.Release释放资源内存问题

前言 最近在编写基于Addressables的资源管理器&#xff0c;对于资源释放模块配合MemoryProfiler进行了测试&#xff0c;下面总结下测试Addressables.Release的结论。 总结 使用Addressables.Release释放资源时&#xff0c;通过MemoryProfiler检查内存信息发现加载的内容还在…

python-序列相关

序列&#xff08;squence&#xff09;是一组按顺序、紧密排列在一起的数据集。序列的作用是便于管理、方便数据操作更重要的是序列支持切片操作。 序列主要包括&#xff1a;列表、元组、字符串和字节串 内置数据结构&#xff1a; 容器&#xff1a;列表、元组、字典、集合 结构…

CVE-2020-26048(文件上传+SQL注入)

简介 CuppaCMS是一套内容管理系统&#xff08;CMS&#xff09;。 CuppaCMS 2019-11-12之前版本存在安全漏洞&#xff0c;攻击者可利用该漏洞在图像扩展内上传恶意文件&#xff0c;通过使用文件管理器提供的重命名函数的自定义请求&#xff0c;可以将图像扩展修改为PHP&#xf…

苹果笔记本双系统怎么安装

想要在mac电脑上装双系统&#xff0c;首先需要确认您的电脑是否支持。苹果电脑自带的boot camp工具可以帮助您在mac上安装windows系统&#xff0c;只需按照步骤进行操作即可。另外&#xff0c;您也可以使用虚拟机软件&#xff0c;如parallels desktop或vmware fusion&#xff0…

关于Claude3.5-Sonnet引以为傲的功能,在半年前就被某国产平台无情碾压的那档事!

前言&#xff1a; Anthropic声称其每隔几个月就会对Claude发布一次重大版本的更新。距离今年3月份Claude3发布&#xff0c;已经又过去了3个多月的时间。果不其然&#xff0c;6月21日Anthropic 在X上正式官宣发布全新大模型 Claude3.5 Sonnet&#xff0c;号称它能够碾压GPT4o&a…

海思SS928/SD3403开发笔记1——使用串口调试开发板

该板子使用串口可以调试&#xff0c;下面是win11 调试 该板子步骤 1、给板子接入鼠标、键盘、usb转串口 2、下载SecureCRT&#xff0c;并科学使用 下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11dIkZVstvHQUhE8uS1YO0Q 提取码&#xff1a;vinv 3、安装c…

导航栏设计的5种类型,新手不容忽视的重要知识!

导航栏是网页设计中不可缺少的一部分。大多数用户在浏览网页时都是从导航栏开始的。导航栏的作用相当于路标和书籍中的目录&#xff0c;其重要性不言而喻。从设计的角度来看&#xff0c;网页导航栏的设计功能大于视觉效果。因此&#xff0c;网页导航栏的设计可以分为 5 类型&am…

STM32启动流程 和 map文件的作用

一&#xff0c;启动流程 1. 复位/上电 2. 根据 BOOT0/BOOT1 确定程序从哪个存储位置执行 3. 初始化 SP 及 PC 指针 将 0X08000000 位置的栈顶地址存放在 SP 指针中 将 0x08000004 位置存放的向量地址装入 PC 程序计数器 4. 初始化系统时钟 5. 初始化用户堆栈 6. 进入main函数 二…

考研数学复习(1/9):函数与极限

目录 函数与极限 1. 函数的概念 1.1 函数的定义 1.2 函数的表示方法 1.3 函数的分类 1.4 函数的运算 2. 极限的概念 2.1 极限的定义 2.2 极限的性质 2.3 极限的计算方法 2.4 极限的应用 3. 连续函数 3.1 连续函数的定义 3.2 连续函数的性质 3.3 连续函数的分类 …