首页 » 软件开发 » ChatUI:使用Gradio.NET为LLamaWorker快速创建大模型演示界面(模型界面服务获取输入)

ChatUI:使用Gradio.NET为LLamaWorker快速创建大模型演示界面(模型界面服务获取输入)

admin 2024-07-24 00:13:03 0

扫一扫用手机浏览

文章目录 [+]

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

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

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

ChatUI:使用Gradio.NET为LLamaWorker快速创建大模型演示界面(模型界面服务获取输入) 软件开发
(图片来自网络侵删)
4. 为 LLamaWorker 创建演示界面

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

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

4.1. 服务设置

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

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

```csgr.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.IsOrWhiteSpace(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 == || 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 == ) { 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\", ); // 请求失败 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.IsOrWhiteSpace(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.IsOrEmpty(text)) { continue; } chatHistory[^1].AiMessage.TextMessage += text; yield return gr.Output(\"\", chatHistory); } } }}

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

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

5. 效果

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

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

qwen-long

6. 总结

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

References

[1] LLamaWorker: https://github.com/sangyuxiaowu/LLamaWorker?wt.mc_id=DT-MVP-5005195[2] Gradio.NET: https://github.com/feiyun0112/Gradio.Net?wt.mc_id=DT-MVP-5005195

标签:

相关文章

工程监理,守护工程质量的生命线

工程监理,作为我国工程建设中不可或缺的一环,承担着确保工程质量、保障安全生产、维护各方利益的重要职责。在工程建设过程中,监理工作贯...

软件开发 2024-12-30 阅读1 评论0

拥抱数字时代,提升个人竞争力

在数字化浪潮席卷全球的今天,个人竞争力成为了我们在职场和生活中立足的关键。如何在这个充满变革的时代提升个人竞争力呢?本文将从以下几...

软件开发 2024-12-30 阅读0 评论0

探索DimC语言的魅力与应用前景

随着互联网的飞速发展,编程语言在各个领域得到了广泛应用。DimC语言作为一门新兴的编程语言,以其独特的魅力和丰富的应用前景,逐渐受...

软件开发 2024-12-30 阅读1 评论0

探索CMS龟仙屋,传承与创新中的中医药瑰宝

自古以来,中医药作为中华民族的瑰宝,承载着中华民族的智慧与精神。在众多中医药店铺中,CMS龟仙屋以其独特的经营理念、丰富的药材资源...

软件开发 2024-12-30 阅读0 评论0