使用C++在Qt框架下调用DeepSeek的API接口实现自己的简易桌面小助手

news2025/3/26 20:18:57

项目背景

随着DeepSeek的爆火,最近的DeepSeek也进行了新一轮技术的更新,为了拥抱新时代,我们也要不断学习新的知识,难的底层原理我们接触不到,简单的调用还能难住我们?

因为在网络上搜集到的资源都是用Python语言来进行调用,提供C++调用接口的教程较少,所以我就打算出一节关于整个流程的分析,供大家闲暇之余整来玩玩。废话不多说,我们直接开始:

首先:创建一个Qt项目,为项目命名,此时自动生成五个文件。

main.cpp、mainwindow.h、mainwindow.cpp、mainwindow.ui、还有一个.pro项目文件。

此外,我们再额外建一个类:DeepSeekAPI,就需要创建对应的.h和.cpp文件。

完事之后,此时的文件包括:

交互逻辑

我们先来完成页面逻辑的问题,暂不考虑接口调用的事情

在ui文件中,只需要有三个控件即可:QPlainTextEdit、QLineEdit、QPushButton。【我分别命名为:ChatBox、inputBox、btnSend】

ui设计

当然,在这块你怎么喜欢怎么设计,包括设置背景图、改变调色板、大小、布局等的自定义安排。

这不重要,重要的是每个控件的使用,但不要违背事实或习惯。

在MainWindow的主页面初始化时(构造函数中),我们应该将chatBox设置为只读状态。

ui->ChatBox->setReadOnly(true);//对话框只读

点击按钮

分析:点击按钮-》输入框文本清除-》对话框出现我的对话-》AI推理(同时屏蔽按钮的可用属性)-》推理结果显示到对话框上。OK,步骤清晰了,那么剩下的就是完善代码了:

    //按钮-发送请求
    connect(ui->btnSend,&QPushButton::clicked,this,[&](){
        QString userInput = ui->inputBox->text();
        if(userInput.isEmpty()) return;

        ui->inputBox->clear();//输入框清空
        ui->btnSend->setEnabled(false);//禁用发送按钮,放置频繁发送请求

        
        ui->ChatBox->appendPlainText("User: " + userInput);//加入对话框
        startInference();//开始推理-》推理完成后的任务也交给它了
        
    });

主窗体类:

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    void startInference();
private:
    Ui::MainWindow *ui;
private:
    DeepSeekAPI *apiClient = new DeepSeekAPI();// API客户端
};

我们只需要在主窗体添加一个startInference-开始推理的方法,然后添加一个成员:DeepSeekAPI对象指针。然后实现推理方法的逻辑就可以了:

推理逻辑

前端逻辑

推理逻辑的步骤:①采用异步通信方式,调用API的推理接口:inferMessage,将输入框的内容添加进去(如果这块使用控件文本,上面就不要将文本框清空,不然这块就获取不到内容了,不过后面不用这个东西这块知道逻辑就可以了)②异步通信时,使用同类型的监视器监视异步线程,使用setFuture(future)进行绑定监视。③建立信号槽,本次连接是将异步推理线程结束的信号与处理逻辑连接上:

接收异步线程传输的结果-》将结果添加到对话框-》发送按钮重新设置为可使用-》删除监视器

void MainWindow::startInference(){
    //异步推理
    QFuture<QString> future = apiClient->inferMessage(ui->inputBox->text());
    QFutureWatcher<QString> *futureWatcher = new QFutureWatcher<QString>();
    futureWatcher->setFuture(future);

    connect(futureWatcher,&QFutureWatcher<QString>::finished,this,[=](){
        QString result = futureWatcher->result();

        ui->ChatBox->appendPlainText("AI: "+result);
        
        ui->btnSend->setEnabled(true);
        futureWatcher->deleteLater();
    });
}

后端逻辑

前面都是从用户与客户端交互的角度完成的逻辑代码,但是真正的推理是后端使用网络通信进行访问DeepSeek官方的API的。

DeepSeekAPI类的定义:
 

class DeepSeekAPI :QObject {
    QString apiKey_ = "sk-**********";//这里是API_KEY,需要从DeepSeek创建获取,后面会介绍具体方法
public:
    //Qt提供的网络管理器,方便我们进行网络通信
    QNetworkAccessManager* networkManager_ = new QNetworkAccessManager();

    DeepSeekAPI(QObject *parent = nullptr);
    ~DeepSeekAPI();

    //对话推理
    QFuture<QString> inferMessage(const QJsonArray& messages);
};

我们看到,这里的对话推理的逻辑中传入的并不是一个单纯的文本信息,而是Json格式的信息的一个数组。那么我们回过头去把这个数组构建出来再使用。

补充一:MainWindow类内添加成员:

//对话信息格式
struct ChatMessage {
    QString role;  // "user" 或 "assistant"
    QString content;
};


class MainWindow{
    ...
private:
    QList<ChatMessage> chatHistory; // 存储所有对话记录    
};

补充二:点击按钮后的操作:

connect(... , 

    ui->ChatBox->appendPlainText("User: " + userInput);
    chatHistory.append({"user",userInput});//在推理前 新加的内容-{role = "user" , content="...."}
    startInference();

);

补充三:在异步推理之前进行上下文处理:

    //上下文处理
    QJsonArray messageArray;
    for(const ChatMessage &msg:chatHistory){
        QJsonObject messageObj;//创建对象

        messageObj["role"]=msg.role;//设置键值对
        messageObj["content"]=msg.content;//设置键值对

        messageArray.append(messageObj);//添加对象
    }
    //保留近5轮对话
    if(chatHistory.size()>20){
        //由于Token限制,为避免溢出,采用截断旧消息的方式
        chatHistory = chatHistory.mid(chatHistory.size() - 10);
    }

    //异步推理--这时候就可以传入消息数组了
    QFuture<QString> future = apiClient->inferMessage(messageArray);

这时候,我们就可以进行真正的推理了:

第一步构建 请求体:

    //构建请求体
    QJsonObject content;
    content["model"] = "deepseek-chat";//选取通用模型
    //content["model"] = "deepseek-reasoner";//R1模型
    content["messages"] = messages;
    QJsonDocument jmsg = QJsonDocument(content);//转换格式

第二步:创建网络请求头:

    //异步通信接口
    QFutureInterface<QString> interface;
    interface.reportStarted(); // 标记future已开始
    
    // 确保networkManager_在主线程创建
    QNetworkRequest request;
    request.setUrl(QUrl("https://api.deepseek.com/v1/chat/completions"));   //url路由
    request.setRawHeader("Authorization", ("Bearer " + apiKey_).toUtf8());  //apikey
    request.setRawHeader("Content-Type", "application/json");               //数据格式 json
    request.setRawHeader("max_tokens","500");   //生成最大长度

request.setRawHeader("stream","false");

request.setRawHeader("temperature","1.3");  //控制多样性(0-2)越大越随机,通用对话1.3
request.setRawHeader("top_p","0.9"); /控制生成范围(0-1)不建议与temperature一块使用
request.setRawHeader("stop","\n\n");//连续空行时终止

第三步:从DeepSeek的服务器端接收请求后的反应:

QNetworkReply* reply = networkManager_->post(request, jmsg.toJson());
QObject::connect(reply, &QNetworkReply::finished, ...);
return interface.future();//返回异步通信结果

最后实现接收到请求后的逻辑:

    // 连接完成信号
    QObject::connect(reply, &QNetworkReply::finished, [interface, reply]() mutable {
        if (reply->error() == QNetworkReply::NoError) {
            QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
            QString result = doc.object()["choices"].toArray().first().toObject()["message"].toObject()["content"].toString();
            interface.reportResult(result); // 设置结果
        }
        reply->deleteLater();
        interface.reportFinished(); // 标记future完成
    });

如果有错误,可以使用代码获取:

 else {
            // 错误处理
            qDebug() << "错误:" << reply->errorString();
            qDebug() << "HTTP 状态码:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
            qDebug() << "错误详情:" << reply->readAll();
            interface.reportResult(QString()); // 返回空字符串表示错误
 }

至此,代码模块就完成了,非常简单吧,那么剩下的就是如何获取你的APIKEY:

获取APIKEY

切记:在下面获取玩APIKey之后,必须想办法保存下来,不然后续将无法再次复制。所以复制完就直接保存到安全的位置就可以了。 

 

然后充一块钱试试水。

效果展示


感谢大家!

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

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

相关文章

【信息系统项目管理师】【高分范文】【历年真题】​论信息系统项目的风险管理

【手机端浏览】☞【信息系统项目管理师】【高分范文】【历年真题】​论信息系统项目的风险管理 2023年上半年考题 【题目】 论信息系统项目的风险管理 项目风险管理旨在识别和管理未被项目计划及其他过程所管理的风险&#xff0c;如果不妥善管理&#xff0c;这些风险可能导致项…

Debain-12.9使用vllm部署内嵌模型/embedding

Debain-12.9使用vllm部署内嵌模型/embedding 基础环境准备下载模型部署模型注册dify模型 基础环境准备 基础环境安装 下载模型 modelscope download --model BAAI/bge-m3 --local_dir BAAI/bge-m3部署模型 vllm serve ~/ollama/BAAI/bge-m3 --served-model-name bge-m3 --t…

Milvus学习整理

Milvus学习整理 一、度量类型(metric_type) 二、向量字段和适用场景介绍 三、索引字段介绍 &#xff08;一&#xff09;、概述总结 &#xff08;二&#xff09;、详细说明 四、简单代码示例 &#xff08;一&#xff09;、建立集合和索引示例 &#xff08;二&#xff09…

MySQL事务全解析:从概念到实战

在数据库操作中&#xff0c;事务是一个至关重要的概念&#xff0c;它确保了数据的完整性和一致性。今天&#xff0c;就让我们深入探讨MySQL事务的方方面面&#xff0c;从基础概念到实际应用&#xff0c;全面掌握这一技能。 一、为什么需要事务 假设张三要给李四转账100元&…

手机怎么换网络IP有什么用?操作指南与场景应用‌

在数字化时代&#xff0c;手机已经成为我们日常生活中不可或缺的一部分&#xff0c;无论是工作、学习还是娱乐&#xff0c;手机都扮演着至关重要的角色。而在手机的使用过程中&#xff0c;网络IP地址作为设备在互联网上的唯一标识符&#xff0c;其重要性和作用不容忽视。本文将…

科技赋能安全:慧通测控的安全带全静态性能测试

汽车的广泛普及给人们的出行带来了极大便利&#xff0c;但交通事故频发也成为严重的社会问题。据世界卫生组织统计&#xff0c;全球每年约有 135 万人死于道路交通事故&#xff0c;而安全带在减少事故伤亡方面起着不可替代的作用。正确使用安全带可使前排驾乘人员的死亡风险降低…

记录修复一个推拉门滑轮

推拉门有个滑轮的固定螺丝不知什么时候掉了&#xff0c;也找不到&#xff0c;这就导致推拉门卡在轨道上。 这种滑轮在夕夕上很便宜&#xff0c;比哈罗单车还划算&#xff0c;但是现在缺的只是螺丝&#xff0c;如果买就会多出来一个轮… 这种螺丝比较长&#xff0c;大概是m4的…

压缩壳学习

壳是什么 壳就是软件的一个保护套&#xff0c;防止软件被进行反编译或被轻易地修改。 其作用就是为了保护软件。 常见的大类壳有压缩壳、加密壳、VM 壳的分类。 压缩壳顾名思义就是用来减小软件的文件大小的&#xff1b;加密壳&#xff0c;通过加密软件来保护软件&#xff…

深入理解 Linux ALSA 音频架构:从入门到驱动开发

文章目录 一、什么是 ALSA?二、ALSA 系统架构全景图核心组件详解:三、用户空间开发实战1. PCM 音频流操作流程2. 高级配置(asound.conf)四、内核驱动开发指南1. 驱动初始化模板2. DMA 缓冲区管理五、高级主题1. 插件系统原理2. 调试技巧3. 实时音频优化六、现代 ALSA 发展七…

CCF-CSP认证 202206-2寻宝!大冒险!

题目描述 思路 有一张绿化图和藏宝图&#xff0c;其中绿化图很大&#xff08;二维数组在限定的空间内无法存储&#xff09;&#xff0c;而藏宝图是绿化图中的一部分&#xff0c;对于绿化图和藏宝图&#xff0c;左下角的坐标为(0, 0)&#xff0c;右上角的坐标是(L, L)、(S, S)&…

仿函数 VS 函数指针实现回调

前提&#xff1a; 本博客对比 函数指针实现回调 和 仿函数 &#xff0c;突出仿函数的优势。 目的&#xff1a; 一个类要能够灵活的调用两个函数&#xff0c;essfc 和 greaterfc&#xff0c;分别用于比较两个整数的大小&#xff1a; ①&#xff1a;lessfc&#xff1a;判断 x …

MQTT的安装和使用

MQTT的安装和使用 在物联网开发中&#xff0c;mqtt几乎已经成为了广大程序猿必须掌握的技术&#xff0c;这里小编和大家一起学习并记录一下~~ 一、安装 方式1、docker安装 官网地址 https://www.emqx.com/zh/downloads-and-install/broker获取 Docker 镜像 docker pull e…

PRODIGY: “不折腾人”的蛋白-蛋白/蛋白-小分子结合能计算工具

PRODIGY&#xff08;全称为 PROtein binDIng enerGY prediction&#xff09;是一种蛋白质结合能预测工具&#xff0c;可利用蛋白质-蛋白质复合物的三维结构来预测其结合亲和力。PRODIGY 利用一种高效的基于接触的方法&#xff0c;在估计结合自由能和解离常数的同时&#xff0c;…

C++之 【模板初阶(函数模板与类模板)】

目录 1.泛型编程 2.模板 3函数模板 3.1函数模板的概念 3.2函数模板的格式 3.3函数模板的原理 3.4函数模板的实例化 3.4.1隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型 3.4.2显示实例化&#xff1a;在函数名后的<>中指定模板参数的实际类型 3.…

在线教育网站项目第四步:deepseek骗我, WSL2不能创建两个独立的Ubuntu,但我们能实现实例互访及外部访问

一、说明 上一章折腾了半天&#xff0c;搞出不少问题&#xff0c;今天我们在deepseek的帮助下&#xff0c;完成多个独立ubuntu24.04实例的安装&#xff0c;并完成固定ip&#xff0c;实践证明&#xff0c;deepseek不靠谱&#xff0c;浪费我2个小时时间&#xff0c;我们将在下面实…

在刀刃上发力:如何精准把握计划关键节点

关键路径分析是项目管理中的一种重要方法&#xff0c;它通过在甘特图中识别出项目中最长、最关键的路径&#xff0c;来确定项目的最短完成时间。 关键路径上的任务都是项目成功的关键因素&#xff0c;任何延误都可能导致整个项目的延期。关键路径分析对于项目管理者来说至关重要…

组合总和||

1.给定一个数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用一次。 #include <bits/stdc.h> using namespace std; vector<vector<int>> result; vec…

OpenCV图像拼接(2)基于羽化(feathering)技术的图像融合算法拼接类cv::detail::FeatherBlender

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::detail::FeatherBlender 是 OpenCV 中用于图像拼接的一个类&#xff0c;它属于 stitching 模块的一部分。这个类实现了基于羽化&#xff08;…

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例9,TableView15_09带排序的导出表格示例

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

spring boot 登入权限RBAC模式

首先准备好5张表 user_info表&#xff0c;用户的信息表 role表&#xff0c;角色表&#xff08;比如超级管理员、管理员、审核员、采购......&#xff09; 创建user_role表&#xff0c;user_info表&#xff0c;role表的中间表 注意了&#xff0c;role_id和user_id是 u…