Phi-3 Forest Laboratory 结合Qt框架:开发跨平台AI桌面应用

张开发
2026/4/13 6:54:04 15 分钟阅读

分享文章

Phi-3 Forest Laboratory 结合Qt框架:开发跨平台AI桌面应用
Phi-3 Forest Laboratory 结合Qt框架开发跨平台AI桌面应用不知道你有没有过这样的想法要是能把一个强大的AI模型像ChatGPT那样的直接装进自己电脑里变成一个随时能打开的桌面软件那该多方便。不用联网不用担心隐私想聊就聊想算就算。今天我们就来把这个想法变成现实。我们将用Qt这个老牌且强大的C框架把轻量级但能力不俗的Phi-3 Forest Laboratory模型“打包”成一个真正的桌面应用。这个应用不仅能通过漂亮的图形界面和你聊天还能记住你们的对话历史甚至在你没有网络的时候也能正常工作。整个过程我们会一步步来从设计窗口到连接AI再到让文字像打字一样流出来保证你能跟着做出来。1. 为什么选择Qt和Phi-3 Forest Laboratory在开始敲代码之前我们先聊聊为什么是这两个组合。这就像盖房子前选材料和工具选对了后面事半功倍。Qt框架就像一个万能工具箱。用它写的软件在Windows、macOS、Linux上都能运行一次开发到处使用。它提供了从按钮、文本框到复杂图表的一切界面组件而且用C写成性能非常好。对于需要和本地系统深度交互、追求响应速度的AI桌面应用来说Qt是个非常扎实的选择。Phi-3 Forest Laboratory可以理解为一个“开箱即用”的轻量级AI模型服务。它把微软的Phi-3系列小模型比如Phi-3-mini封装好了你只需要下载、运行它就会在本地启动一个服务等着你的程序去调用。它的优点是小巧、速度快对硬件要求相对友好非常适合在个人电脑上部署作为本地智能助手的大脑。把它们俩结合起来Qt负责打造应用的“身体”界面和交互Phi-3 Forest Laboratory则提供“大脑”智能。我们通过HTTP协议让“身体”和“大脑”对话一个完整的、离线的智能桌面应用就诞生了。2. 搭建开发环境与项目骨架工欲善其事必先利其器。我们先来把开发环境准备好并把项目的基础架子搭起来。首先确保你的电脑上已经安装了C编译器比如Windows上的MinGW或MSVCmacOS上的ClangXcode Command Line ToolsLinux上的G。Qt开发套件建议直接安装 Qt Online Installer选择最新的LTS版本如Qt 6.6并勾选你所用平台的编译套件如MinGW 64-bit和Qt Creator IDE。Phi-3 Forest Laboratory从它的项目发布页下载对应你操作系统的预编译包或者按照指南从源码构建。确保你能在命令行成功启动它的服务。接下来我们打开Qt Creator创建一个新项目选择Qt Widgets Application。给项目起个名字比如Phi3DesktopAssistant。在Class Information页面基类选择QMainWindow类名就用默认的MainWindow。这会为我们生成一个带菜单栏、状态栏的主窗口类。构建套件选择你安装好的那一个如Desktop Qt 6.6.0 MinGW 64-bit。创建完成后你的项目结构大概是这样Phi3DesktopAssistant/ ├── Phi3DesktopAssistant.pro # 项目配置文件 ├── main.cpp # 程序入口 ├── mainwindow.h # 主窗口头文件 ├── mainwindow.cpp # 主窗口实现文件 └── mainwindow.ui # 主窗口界面设计文件XML格式我们先在Phi3DesktopAssistant.pro文件中添加网络模块因为待会儿我们需要用HTTP和AI服务通信。在文件里找到QT core gui这一行在后面加上networkQT core gui network好了地基打好了接下来我们开始砌墙设计应用的界面。3. 设计图形用户界面双击mainwindow.ui文件Qt Creator会打开可视化的设计器。我们要设计一个简洁但功能清晰的聊天界面。布局从左侧“Widget Box”拖一个QWidget到中间的窗口上作为中心部件。然后在这个中心部件上右键选择“布局” - “垂直布局”。这样我们就有了一个可以纵向排列其他部件的容器。历史对话区域从“Widget Box”拖一个QTextEdit到垂直布局里。在右侧属性编辑器中将它的objectName改为textEditHistoryreadOnly属性勾选上因为这只是用来显示历史对话的。你可以把它拉高一些占据窗口上半部分。用户输入区域再拖一个QTextEdit放到下面。将它的objectName改为textEditInput。这个框用来输入你的问题。按钮区域从“Widget Box”拖一个QWidget放到最下面它会被自动放入垂直布局。在这个新的QWidget上右键选择“布局” - “水平布局”。然后向这个水平布局里拖入两个QPushButton。第一个按钮objectName改为pushButtonSendtext属性改为“发送”。第二个按钮objectName改为pushButtonCleartext属性改为“清空”。状态提示回到主窗口在底部的状态栏如果没看到可以在设计器里确保statusbar存在我们稍后会用代码来显示连接状态。设计完的界面大致是上面一个大框显示聊天记录中间一个框输入问题下面一排按钮。界面设计器会自动生成界面代码到ui文件里我们不需要手动修改它。现在界面有了我们需要让按钮“活”起来。4. 连接AI服务HTTP客户端与通信逻辑Phi-3 Forest Laboratory 启动后默认会在本地的某个端口比如localhost:8000提供一个HTTP API服务。我们的Qt应用需要扮演一个HTTP客户端的角色向这个地址发送请求并接收回复。首先在mainwindow.h文件中引入必要的头文件并声明我们的类和成员变量#ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow #include QNetworkAccessManager #include QNetworkReply #include QJsonObject #include QJsonDocument QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); ~MainWindow(); private slots: void on_pushButtonSend_clicked(); // 发送按钮的点击槽函数 void on_pushButtonClear_clicked(); // 清空按钮的点击槽函数 void onNetworkReplyFinished(QNetworkReply *reply); // 网络请求完成的槽函数 void onReadyRead(); // 处理流式响应的槽函数 private: Ui::MainWindow *ui; QNetworkAccessManager *networkManager; // 网络访问管理器 QString conversationHistory; // 用于存储简单的对话历史上下文 QNetworkReply *currentReply; // 当前活动的网络回复用于流式读取 }; #endif // MAINWINDOW_H接下来在mainwindow.cpp中实现构造函数和基本的初始化#include mainwindow.h #include ui_mainwindow.h #include QMessageBox MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , networkManager(new QNetworkAccessManager(this)) , currentReply(nullptr) { ui-setupUi(this); conversationHistory 你是一个有用的AI助手。\n; // 初始化系统提示 // 连接按钮的点击信号到我们定义的槽函数 connect(ui-pushButtonSend, QPushButton::clicked, this, MainWindow::on_pushButtonSend_clicked); connect(ui-pushButtonClear, QPushButton::clicked, this, MainWindow::on_pushButtonClear_clicked); // 连接网络管理器的finished信号用于处理非流式响应备用 connect(networkManager, QNetworkAccessManager::finished, this, MainWindow::onNetworkReplyFinished); ui-statusBar-showMessage(就绪请输入您的问题。); } MainWindow::~MainWindow() { if (currentReply) { currentReply-abort(); } delete ui; }核心部分来了发送请求和处理响应。我们实现on_pushButtonSend_clicked函数void MainWindow::on_pushButtonSend_clicked() { QString userInput ui-textEditInput-toPlainText().trimmed(); if (userInput.isEmpty()) { return; } // 更新界面显示用户问题清空输入框禁用发送按钮 ui-textEditHistory-append(b你:/b userInput); ui-textEditHistory-append(iAI正在思考.../i); ui-textEditInput-clear(); ui-pushButtonSend-setEnabled(false); ui-statusBar-showMessage(正在与AI通信...); // 构建请求的JSON数据 QJsonObject json; // 将历史对话和当前问题一起发送提供上下文。注意实际生产环境需注意上下文长度限制。 QString prompt conversationHistory 用户: userInput \n助手:; json[prompt] prompt; json[max_tokens] 500; // 限制生成的最大长度 json[stream] true; // 关键启用流式响应 QJsonDocument doc(json); QByteArray data doc.toJson(); // 构建HTTP请求 QNetworkRequest request(QUrl(http://localhost:8000/v1/completions)); // 假设Phi-3服务在此端点 request.setHeader(QNetworkRequest::ContentTypeHeader, application/json); // 发送POST请求 currentReply networkManager-post(request, data); // 连接流式数据可读的信号 connect(currentReply, QNetworkReply::readyRead, this, MainWindow::onReadyRead); // 连接请求结束的信号包括成功、失败、取消 connect(currentReply, QNetworkReply::finished, this, [this]() { ui-pushButtonSend-setEnabled(true); ui-statusBar-showMessage(请求完成。); currentReply-deleteLater(); currentReply nullptr; // 移除最后一行“AI正在思考...” QTextCursor cursor ui-textEditHistory-textCursor(); cursor.movePosition(QTextCursor::End); cursor.select(QTextCursor::BlockUnderCursor); cursor.removeSelectedText(); cursor.deletePreviousChar(); // 删除换行符 }); }5. 实现流式文本显示与对话历史管理流式响应能让AI的回答一个字一个字地显示出来体验更好。我们来实现onReadyRead函数void MainWindow::onReadyRead() { if (!currentReply) return; // 读取所有新到达的数据 QByteArray newData currentReply-readAll(); QString responseText QString::fromUtf8(newData); // 流式响应通常是多行JSON数据每行是一个事件 QStringList lines responseText.split(\n, Qt::SkipEmptyParts); QString assistantPartialReply; for (const QString line : lines) { if (line.startsWith(data: )) { QString jsonStr line.mid(6); // 去掉 data: 前缀 if (jsonStr [DONE]) { break; // 流结束 } QJsonDocument doc QJsonDocument::fromJson(jsonStr.toUtf8()); if (!doc.isNull()) { QJsonObject obj doc.object(); if (obj.contains(choices) obj[choices].isArray()) { QJsonArray choices obj[choices].toArray(); if (!choices.isEmpty()) { QJsonObject choice choices[0].toObject(); if (choice.contains(text)) { assistantPartialReply choice[text].toString(); } } } } } } if (!assistantPartialReply.isEmpty()) { // 更新显示先移除“AI正在思考...”再追加AI的回复 QTextCursor cursor ui-textEditHistory-textCursor(); cursor.movePosition(QTextCursor::End); cursor.select(QTextCursor::BlockUnderCursor); QString lastLine cursor.selectedText(); if (lastLine.contains(AI正在思考...)) { cursor.removeSelectedText(); cursor.insertText(b助手:/b assistantPartialReply); } else { // 如果不是“正在思考”行则可能是追加到已有回复后面用于流式更新但这里简化处理为替换 // 更完善的实现需要记录上一次更新的位置进行增量更新 cursor.movePosition(QTextCursor::End); cursor.insertText(assistantPartialReply); } // 滚动到最底部 ui-textEditHistory-ensureCursorVisible(); } }对话历史管理我们用一个简单的方法在每次成功完成一轮对话后将问答追加到conversationHistory字符串中。我们需要修改请求完成的逻辑在finished信号的lambda中或在一个新的槽函数里// 这是一个新的槽函数用于处理非流式响应或流式响应最终整合 void MainWindow::onNetworkReplyFinished(QNetworkReply *reply) { reply-deleteLater(); // 确保回复对象被清理 if (reply-error() ! QNetworkReply::NoError) { ui-textEditHistory-append(font colorred请求出错: reply-errorString() /font); ui-pushButtonSend-setEnabled(true); return; } // ... 解析非流式JSON响应更新界面和历史 ... // 注意因为我们用了流式这个函数可能不直接用于显示但仍可用于错误处理和最终历史记录 }在实际的流式处理完成后的finished信号lambda中我们可以更新历史connect(currentReply, QNetworkReply::finished, this, [this, userInput]() { // 注意捕获userInput // ... 其他清理和状态恢复代码 ... // 获取最终显示的助手回复需要从界面或变量中获取 QString fullAssistantReply ...; // 这里需要从流式累积的数据中获取完整回复 if (!fullAssistantReply.isEmpty()) { // 更新内存中的对话历史 conversationHistory 用户: userInput \n助手: fullAssistantReply \n; // 可选限制历史长度避免过长 // 可选将历史保存到本地文件 } });清空按钮的功能很简单void MainWindow::on_pushButtonClear_clicked() { ui-textEditHistory-clear(); conversationHistory 你是一个有用的AI助手。\n; // 重置为初始提示 ui-statusBar-showMessage(对话历史已清空。); }6. 完善功能与打包发布一个基本可用的应用已经完成了。但要让它在不同电脑上都能稳定运行我们还需要做一些完善工作服务检测与配置在应用启动时可以尝试连接localhost:8000检查Phi-3服务是否已运行。如果没有可以提示用户启动服务。甚至可以在Qt应用中集成一个设置对话框让用户自定义服务地址和端口。错误处理与超时为网络请求设置超时并使用QNetworkReply::error()来更细致地处理各种网络错误如连接拒绝、超时等给用户友好的提示。本地历史存储目前对话历史只在内存中。我们可以使用QSettings或QFile将conversationHistory保存到本地文件或系统注册表这样下次打开应用时对话记录还在。界面美化使用Qt的样式表QSS可以轻松改变应用的外观比如换颜色、改字体、加圆角等让它看起来更专业。打包发布使用Qt自带的windeployqtWindows、macdeployqtmacOS或linuxdeployqtLinux工具可以将你的应用和所有依赖的Qt库打包在一起生成一个可以在没有安装Qt环境的电脑上运行的独立程序。记得也要将Phi-3 Forest Laboratory的可执行文件和模型文件一起打包或者提供清晰的指引让用户自行放置。7. 实际用下来的感受把这个小应用跑起来之后感觉还是挺奇妙的。在本地电脑上一个完全离线的窗口可以和你进行有来有回的对话响应速度也取决于你本地的算力。用Qt来做界面开发体验很顺畅各种控件拖拖拽拽就能出来C的代码逻辑也清晰。当然这只是个起点。你可以基于这个骨架添加更多功能比如支持多轮对话更精细的管理、增加不同的AI角色预设、集成文本转语音让AI“说话”、甚至结合本地文件系统让AI帮你分析文档。Phi-3模型虽然轻量但完成一般的问答、总结、创意写作任务已经绰绰有余。开发过程中最关键的是理解Qt的信号与槽机制如何驱动整个应用以及如何与HTTP API进行异步通信。一旦这个流程打通了你就可以把任何提供HTTP接口的AI模型或服务都“装进”你自己的桌面应用里。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章