文档信息

项目 内容
例子项目 小区管家物业综合服务平台
技术栈 Qt 5/6、C++、扣子(Coze)智能体API、SSE协议

前言

随着人工智能技术的快速发展,智能对话系统在各个领域的应用越来越广泛。在物业管理领域,传统的物业服务模式往往需要用户通过电话、现场咨询等方式获取服务,效率较低且体验不佳。

为了解决这一问题,本文实现了基于扣子智能体的AI交互功能,将先进的AI对话能力集成到小区管家物业综合服务平台中。通过该功能,业主可以与AI智能管家进行自然语言对话,AI不仅能回答问题,还能通过意图识别自动跳转到缴费、报修、公告等页面,极大提升了服务效率与用户体验。

本文主要内容包括:

  • 项目涉及的相关技术知识与扣子智能体平台配置方法

  • Qt 框架下 AI 对话功能的整体架构设计与数据流向

  • HTTP 网络请求与 SSE 流式响应数据的解析处理

  • CozeAI 通信类的完整代码实现与详细注释说明

  • AI 回复文本中隐藏命令的识别机制与页面跳转实现

  • 智能体提示词的设计、意图识别规则与优化方案


一、实现效果

本项目使用Qt搭建物业平台界面并对接扣子智能体,实现AI智能对话。用户在输入框输入“小区公告”,AI经过意图识别后,自动跳转到公告页面;输入“物业费”,AI引导用户前往缴费页面。


二、相关知识介绍

2.1 扣子智能体平台

扣子(Coze)是字节推出的零代码智能体搭建平台,支持可视化配置知识库、角色设定、工具插件,一键发布在线 API 接口。开发者无需训练大模型,在平台完成智能体定制后,通过官方 HTTP/SSE 接口即可在自有程序调用 AI 能力,适配桌面软件、嵌入式设备、后端服务等多场景。

平台提供两类接口:

接口类型 特点 适用场景
普通POST同步接口 一次性返回完整回答 短文本、容忍延迟
SSE流式接口 分片实时推送回答,打字机效果 AI对话、实时推送

核心概念:

概念 说明
智能体(Bot) 具有特定人设和功能的AI对话机器人
知识库 智能体的知识来源,可上传文档作为背景知识
插件 扩展智能体能力的工具,如天气查询、新闻获取等
工作流 编排多个节点实现复杂业务逻辑
API/SDK 将智能体以API形式提供给外部系统调用

2.2 SSE(Server-Sent Events)

SSE 是服务端单向推送协议,基于 HTTP 长连接实现服务端主动向客户端持续下发数据,相比 WebSocket:仅服务端→客户端单向通信、复用 HTTP 协议无需额外握手,天然适配大模型流式输出场景。 特点:

  1. 连接建立后持续保持,服务端分段返回data:xxx\n\n格式数据;
  2. Qt 通过QNetworkAccessManager监听 readyRead 分片数据,实时解析拼接 AI 内容;
  3. 网络异常自动重连,适配大模型长文本输出。

SSE与传统HTTP的对比:

特性 传统HTTP SSE
请求次数 一请求一响应 一次请求,多次响应
用户体验 等待完整响应后显示 逐步显示,如打字效果
适用场景 文件传输、普通API 实时推送、AI对话
数据格式 任意格式 data:开头的文本流

SSE数据格式示例:

text

data: {"type":"answer","content":{"answer":"您"}}
data: {"type":"answer","content":{"answer":"好"}}
data: [DONE]

2.3 Qt 网络编程

Qt 使用QtNetwork模块完成 HTTP/SSE 网络交互,核心类:

主要类说明:

类名 作用
QNetworkAccessManager

管理网络请求,发送请求和接收响应。

全局网络管理器,统一发起 GET/POST 请求;

QNetworkRequest 封装请求头、请求地址、Authorization 鉴权
QNetworkReply

封装HTTP响应,提供数据读取接口。

接收接口返回数据流,readyRead()分片读 SSE 数据、finished()请求结束。 Qt 网络全部异步实现,不会阻塞 UI 主线程,完美适配 AI 对话界面刷新。

2.4 信号与槽机制

Qt 核心通信机制,解耦网络数据接收与 UI 界面更新:网络类收到 SSE 分片数据触发自定义信号,绑定 UI 控件的槽函数,实时追加文字到文本框,避免跨线程 UI 崩溃。 例:CozeAI类发出recvAiMsg(QString)信号 → 绑定MainWindow槽函数,在对话框实时渲染 AI 打字效果。

// 信号声明(发送方)
signals:
    void replyReceived(const QString &content);

// 槽函数连接(接收方)
connect(cozeAI, &CozeAI::replyReceived, this, [=](const QString &answer){
    // 处理接收到的AI回复
});

三,常见的问题

在开始实现之前,先解答几个关键问题,帮助理解整个技术方案。

问题1:什么是API?为什么需要API接口?每个大模型的API是否不一样?

API(应用程序编程接口) 是软件系统之间预先约定好的通信规则与数据格式,规定了程序之间 “如何发送请求、传什么参数、返回什么内容”。可以把 API 理解为软件与软件之间的通用接口和翻译官,就像电源插座一样 —— 只要符合标准,就能直接 “对接取电”,不需要关心内部如何实现。

为什么需要 API 接口?

  1. 让不同系统能够互相调用能力 本项目中,Qt 程序本身不具备 AI 对话能力,无法自己运行大模型,必须通过 API 调用云端扣子智能体的服务。

  2. 屏蔽底层复杂性,降低开发难度 大模型需要强大算力、专业框架、海量数据与复杂训练,普通开发者无法直接实现。 API 把这些复杂能力封装成一个简单的网络接口,我们只需要发送文字,就能拿到 AI 回复。

  3. 实现功能解耦,便于维护与升级 前端 Qt 界面、后端 AI 服务、业务逻辑可以独立开发、独立更新。 模型升级、服务更换时,只要 API 不变,客户端代码无需修改。

  4. 统一权限与安全管控 通过 API 可以做身份验证、调用限流、日志记录,保证接口不被滥用。

每个大模型的 API 是否不一样?

不一样,且不能直接通用。

不同厂商的大模型(如扣子 Coze、百度文心、阿里通义、科大讯飞等),在以下方面都存在差异:

  • 接口请求地址(URL)不同
  • 身份验证方式(Token/Key 格式)不同
  • 请求参数的 JSON 结构不同
  • 字段名称、层级嵌套不同
  • 返回结果的数据格式不同
  • 流式输出(SSE)的协议格式不同

但它们底层都基于 HTTP/HTTPS 协议,整体思路一致:发送 JSON → 接收 JSON / SSE 流。

在本项目中,我们直接对接扣子智能体专属 API,因此只需按照 Coze 平台规定的格式发送请求即可,无需适配其他模型接口

问题2:平台提供哪几类接口?

扣子平台主要提供两类接口:

类型 特点 适用场景
普通POST同步接口 一次性返回完整回答 短文本、容忍延迟
SSE流式接口 分片实时推送回答,打字机效果 AI对话、实时推送

本项目采用SSE流式接口,以实现AI回复逐步显示的效果。

问题3:什么是API/SDK?

术语 含义 类比
API(接口) 调用服务的标准,包括地址、参数格式、返回格式等 餐厅的菜单(你只能按菜单点菜)
SDK(软件开发工具包) 封装了API调用的现成库,通常包含语言封装、错误处理等 预制菜包(打开即用)

本项目没有使用Coze的官方SDK,而是直接调用HTTP API,因为需要跨平台且灵活控制细节(比如SSE解析)。

问题4:什么是个人访问令牌?为什么需要?什么作用?

个人访问令牌(Personal Access Token)是一串加密的字符串,相当于长期有效的“临时密码”。

为什么需要: API是公开的URL,如果不验证身份,任何人都可以调用你的智能体,消耗你的资源或获取敏感信息。

作用:

  • 鉴权:请求时放在Authorization: Bearer <token>头中,服务器验证令牌合法性

  • 权限控制:不同令牌可绑定不同的智能体、不同的数据权限

  • 统计与限流:平台通过令牌识别调用方,进行计费、限流

问题5:SSE是什么?为什么QT能解析SSE?

SSE(Server-Sent Events,服务端推送事件)是一种基于HTTP的服务器推送技术,允许服务器主动向客户端发送多次数据。

与WebSocket区别:

  • SSE是单向(服务端→客户端),协议更轻,自动重连

  • WebSocket是双向,适合聊天室、游戏等场景

SSE的本质就是分段返回的HTTP响应。Qt的QNetworkReply::readAll()会一次性读取当前收到的所有数据。因为网络是分包的,你可能收到: 第1次readyRead:data: {"type":"answer","content":{"answer":"您"}} 第2次readyRead:data: {"type":"answer","content":{"answer":"好"}} finished信号在所有数据都收到后才触发,所以我们可以在finished里处理完整数据。

问题6:为什么这个功能用到了HTTP?

  • HTTP是互联网上应用最广泛的协议,任何联网设备都能发送HTTP请求

  • SSE本身就是基于HTTP的扩展,不需要额外协议

  • Qt内置了QNetworkAccessManager,对HTTP和HTTPS支持完善,实现简单

问题7:涉及到了哪些Qt知识?为什么Qt能解析CMD命令?

知识模块 在本项目中的作用
Qt Network (QT += network) 提供QNetworkAccessManagerQNetworkRequestQNetworkReply,完成HTTP请求与响应
Qt Core (QObject, 信号槽) CozeAI继承QObject,利用信号槽异步传递网络响应给UI,避免界面卡顿
Qt JSON (QJsonDocumentQJsonObject) 构造请求体,解析服务器返回的JSON片段
Qt 正则表达式 (QRegularExpression) 用于从文本中提取隐藏命令(如【CMD:OPEN_FEE】
Qt 字符串处理 (QStringQByteArray) 处理SSE数据行、拼接回复、编码转换
  • 为什么Qt能解析CMD命令?
  • 因为AI返回的answer是一个普通的QString字符串。QString提供了各种查找、匹配的方法。QRegularExpression是Qt对正则表达式的封装,它可以在字符串中搜索匹配模式,返回匹配位置和捕获的内容。 整个过程就像在文章中查找特定格式的词语: text 输入:你好,请帮我缴费【CMD:OPEN_FEE】 ↓ 正则匹配 找到:【CMD:OPEN_FEE】 ↓ 提取 命令:OPEN_FEE ↓ 执行 跳转到缴费页面

问题8:JSON是什么?

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,类似Python字典或C++的map,使用{key: value}表示。

为什么AI API都用JSON:

1.结构灵活,支持嵌套(如本项目的content.query.prompt):

  • AI对话往往需要传递结构化的信息,而JSON可以轻松表达这种层级关系。以本项目为例,发送给AI的请求体需要包含"用户的问题"、"会话ID"、"项目ID"等多个信息,其中"用户的问题"本身又有"类型"和"内容"两个属性。

    //JSON可以用嵌套的方式清晰表达这种结构:
    
    json
    
    {
        "content": {
            "query": {
                "prompt": [
                    {
                        "type": "text",
                        "content": {
                            "text": "物业费多少钱"
                        }
                    }
                ]
            }
        },
        "type": "query",
        "session_id": "rbkdP69JpgfngtdcBejyt",
        "project_id": 7646999877276155939
    }
  • 可以看到,content里面包着queryquery里面包着prompt数组,数组里又包着具体的消息内容和类型。这种多层嵌套的结构如果用其他格式(如纯文本、CSV)来表示,会非常复杂且难以解析。而JSON天生支持对象套对象、数组套对象的写法,与AI API复杂的参数结构天然匹配。

2,人类可读,便于调试(curl返回后一眼能看出问题)

data: {"type":"answer","content":{"answer":"您"}}
data: {"type":"answer","content":{"answer":"好"}}
  • 你可以立刻看出:typeanswercontent里嵌套的answer字段的值是"您"和"好"。如果返回的是二进制格式或某种自定义格式,你必须先写解析代码才能看懂内容,大大增加调试难度。

3,几乎所有编程语言都有成熟解析库(Qt提供了QJsonDocument

  • AI服务需要被各种语言的客户端调用——可能是C++写的桌面软件,可能是Python写的后端服务,也可能是JavaScript写的前端页面。JSON作为一种语言无关的文本格式,几乎所有编程语言都提供了成熟的解析库

  • 本项目使用Qt,Qt提供了完整的JSON处理类:QJsonDocument(解析/生成JSON文档)、QJsonObject(操作对象)、QJsonArray(操作数组)、QJsonValue(操作值)。开发者只需要几行代码就能完成解析,无需自己写字符串处理逻辑。

    cpp
    
    // Qt中解析JSON只需几行
    QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8());
    QJsonObject obj = doc.object();
    QString answer = obj["content"]["answer"].toString();

问题9:什么是代理服务器?为什么我这个用到的是代理服务器?

代理服务器是客户端与目标服务器之间的中间服务。客户端不直接访问最终服务器,而是先把请求发给代理,由代理转发到目标服务,并将结果返回给客户端。在AI调用中,它可以做:

  • 隐藏真实的后端 API 地址,提高安全性;
  • 统一请求入口,方便做权限校验、日志记录、限流管控;
  • 对请求 / 响应格式进行转换,简化前端调用复杂度;
  • 实现负载均衡、缓存加速,提升接口稳定性

本项目为什么要使用代理服务器?

本项目中调用的接口地址: https://nxjxfxyw45.coze.site/stream_run 并不是扣子(Coze)官方原生 API 地址,而是一个专门封装过的代理服务器地址

使用这个代理的主要原因:

  1. 简化请求结构,降低 Qt 开发难度 扣子官方 API 的请求体结构复杂,包含多层嵌套 JSON、BotId、UserId、ConversationId 等大量参数。 代理服务器对官方接口做了一层封装,让我们只需要传入简单的文本内容即可调用,不用处理复杂的官方字段。

  2. 自动路由到对应智能体,无需配置 Bot ID 原生 Coze 接口需要明确指定 Bot ID 才能调用对应智能体。 该代理通过 Token 就能自动识别并绑定对应的智能体,Qt 端无需额外配置 Bot 相关参数。

  3. 统一接口格式,适配 SSE 流式输出 代理对返回的 SSE 数据流做了格式规整,让 Qt 端更容易解析 “data:” 分片数据,实现稳定的打字机效果。

  4. 提高安全性与可维护性 真实的 Coze 官方地址、密钥校验逻辑、权限策略都在代理后端处理,Qt 客户端只面对一个简单稳定的接口,便于后期更换模型、调整服务而不改动前端代码。

简单总结: 代理服务器把复杂、繁琐、参数多的官方 AI 接口,封装成了简单、统一、易接入的简化接口,让我们在 Qt 中可以更轻松、更稳定地实现 AI 对话功能。

问题10:如何发送和接收信息,并显示在界面上?

发送流程:

  1. 用户在QLineEdit输入文本,点击发送按钮

  2. MainWindow::on_aiSendBtn_clicked获取文本,调用cozeAI->sendMessage(txt)

  3. sendMessage构造QNetworkRequest,添加Authorization头,用QJsonDocument生成请求体,通过netManager->post发送

接收与显示流程:

  1. 服务器分片返回data: {...}\n\n格式数据,QNetworkReply触发finished信号

  2. onReplyFinished中读取所有数据,按行解析data:前缀,提取content.answer字段并拼接到fullAnswer

  3. 解析完毕后emit replyReceived(fullAnswer)

  4. MainWindow中连接的Lambda函数被调用,将fullAnswer追加到QTextEdit中显示

问题11:curl是什么?

curl是一个命令行下的HTTP客户端,可发送任意HTTP请求并打印响应。

在本项目中的作用: 在编写Qt代码前,先用curl测试API是否通畅,确认参数格式、Token是否正确。curl命令就是一份“可执行的接口说明书”。

以项目中的命令为例:

bash

curl --location --request POST "https://nxjxfxyw45.coze.site/stream_run" \
  --header "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  --header "Content-Type: application/json" \
  --data '{
      "content": {
        "query": {
          "prompt": [
            {
              "type": "text",
              "content": {
                "text": "你好"
              }
            }
          ]
        }
      },
      "type": "query",
      "session_id": "rbkdP69JpgfngtdcBejyt",
      "project_id": 7646999877276155939
    }'
参数 含义 对应Qt代码
curl 命令本身 -
--location 跟随重定向(如果服务器返回301/302,自动跳转) QNetworkRequest默认支持
--request POST 指定HTTP方法为POST netManager->post()
"https://..." 请求的URL地址 QUrl(API_URL)
--header "Authorization: Bearer xxx" 添加请求头,用于身份验证 req.setRawHeader("Authorization", ...)
--header "Content-Type: application/json" 告诉服务器请求体是JSON格式 req.setHeader(ContentTypeHeader, "application/json")
--data '{...}' 请求体内容(JSON格式) netManager->post(req, jsonData)
//curl返回结果解读
bash

data: {"type":"answer","content":{"answer":"您"}}
data: {"type":"answer","content":{"answer":"好"}}
data: {"type":"answer","content":{"answer":","}}
data: {"type":"answer","content":{"answer":"欢迎使用"}}
data: [DONE]

解读

  • 每行以 data: 开头

  • 后面跟着JSON字符串

  • [DONE] 表示所有内容发送完毕

从curl到Qt代码的转换对照表:
curl部分 Qt代码
--request POST netManager->post(req, data)
--header "Authorization: Bearer xxx" req.setRawHeader("Authorization", "Bearer xxx")
--header "Content-Type: application/json" req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json")
--data '{...}' QJsonDocument(root).toJson()
SSE响应解析 onReplyFinished中按行解析 data: 前缀

问题12:网络在这个过程中怎么设置的?

层面 设置内容 代码位置
项目配置 .pro文件中添加QT += network 项目根目录
请求参数 URL、请求头(Authorization)、请求体(JSON) CozeAI::sendMessage
代理设置(可选) 如果本机需要代理才能访问外网,可设置QNetworkProxy 本项目未使用,默认直连
SSL/TLS https地址默认启用 本项目使用https,未特殊配置

本项目中网络配置的核心是:

  • 使用代理服务器地址

  • 通过Bearer token鉴权

  • 请求体为自定义JSON格式

  • 响应解析为SSE流式

 四、系统架构设计

4.1 整体架构图

整体分为三层:

  1. 扣子智能体服务端:云端部署大模型 + 提示词逻辑,接收 API 入参,SSE 流式返回 AI 结果;
  2. Qt 中间逻辑层(CozeAI 封装类):封装网络请求、SSE 协议解析、数据拼接、异常捕获;
  3. Qt UI 表现层 (MainWindow):输入框录入用户提问、按钮触发请求、文本框实时展示流式 AI 回复。

数据流:用户输入问题→UI 层调用 CozeAI 接口→发起 SSE 请求到 Coze 云端→云端分片推送数据→CozeAI 解析 SSE 报文→信号推送文本→UI 实时刷新内容。

4.2 数据流向

1.用户在Qt界面输入提问内容,点击发送按钮;
2.MainWindow捕获输入内容,调用CozeAI::sendMessage()将用户问题传入AI通信类;
3.CozeAI组装带Bearer Token鉴权的请求头与JSON请求体,通过HTTP POST把请求发送至代理服务器;
4.代理服务器接收请求并转发至扣子官方API,扣子云端校验访问凭证、运行预设智能体逻辑,分段生成回答内容,以SSE数据流格式逐层原路下发;
5.CozeAI依靠QNetworkReply接收全部SSE原始数据,按行过滤data:报文前缀、剔除无效数据,循环拼接所有AI回复片段;
6.识别到SSE末尾[DONE]结束标记后停止解析,将拼接完整的AI文本通过replyReceived信号发送至MainWindow;
7.主窗口接收信号数据,把AI回复内容一次性渲染到聊天输入框,本次SSE连接生命周期结束。

五、扣子平台配置步骤

5.1 需要获取的内容

参数 说明
API地址 代理服务器地址
Token 个人访问令牌
session_id 会话ID,保持对话上下文
project_id 项目ID

说明:本文针对的是”项目开发“中创建的智能体 。

5.2 获取方式

步骤一:创建智能体并进行部署

📷 :

步骤二:获取API接口,curl测试命令

📷 :

//在API接入页面,平台会自动生成curl测试命令,可用于验证接口是否正常。
//curl命令就是一份“可执行的接口说明书”。

curl --location --request POST "https://nxjxfxyw45.coze.site/stream_run" \
  --header "Authorization: Bearer <YOUR_TOKEN>" \
  --header "Content-Type: application/json" \
  --data '{
      "content": {
        "query": {
          "prompt": [
            {
              "type": "text",
              "content": {
                "text": "物业费多少钱"
              }
            }
          ]
        }
      },
      "type": "query",
      "session_id": "rbkdP69JpgfngtdcBejyt",
      "project_id": 7646999877276155939
    }'

步骤三:获取个人访问令牌(Token)

📷 :

六、核心代码详解

前置配置:.pro 文件需要添加模块:QT += network

📌Qt将网络功能单独放在network模块中,不添加就无法使用QNetworkAccessManagerQNetworkRequest等网络相关的类

6.1 核心参数说明

void CozeAI::sendMessage(const QString &question)
{
    // ① API地址(请求的目标地址,所有角色共用,固定不变)
    //代理服务器的固定地址,所有请求都发到这里
    const QString API_URL = "https://nxjxfxyw45.coze.site/stream_run";
    
    // ② Authorization Token(身份验证),Bearer是标准的Token认证方式,后面跟着Token字符串
    req.setRawHeader("Authorization", ("Bearer " + currentApiToken).toUtf8());
    
    // ③ session_id(会话标识),相同的ID能让AI记住之前的对话,相当于聊天记录ID
    root["session_id"] = "rbkdP69JpgfngtdcBejyt";
    
    // ④ project_id(项目标识),告诉服务器是哪个项目在调用,固定值
    root["project_id"] = 7646999877276155939;
}

6.2 CozeAI.h - 头文件

AI 功能的接口说明书它不实现功能,只负责告诉编译器这个类有什么函数、有什么变量、有什么信号

#ifndef COZEAI_H
#define COZEAI_H

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>

/**
 * @brief CozeAI 类
 * 负责与扣子智能体API进行网络通信,发送问题、接收AI回复
 */
class CozeAI : public QObject
{
    Q_OBJECT  

public:
    // 构造函数:初始化AI网络模块
    explicit CozeAI(QObject *parent = nullptr);

    // 设置API访问令牌,配置身份密钥,让服务器知道是谁在调用AI
    void setApiToken(const QString &apiToken);

    // 发送用户问题,把用户输入的文字发送给扣子智能体
    void sendMessage(const QString &question);

signals:
    // AI回复完成信号,当AI返回完整回答后,发射信号通知UI界面显示内容
    void replyReceived(const QString &content);

private slots:
    // 【核心槽函数】网络请求结束自动调用,接收服务器返回的数据,解析SSE流式响应
    void onReplyFinished(QNetworkReply *reply);

private:
    QNetworkAccessManager *netManager;  // 网络管理器:Qt官方网络工具,负责发送/接收请求
    QString currentApiToken;            // API令牌:用户身份验证密钥,接口鉴权使用

    // ========== 固定API配置(与扣子平台一一对应)==========
    const QString API_URL = "https://nxjxfxyw45.coze.site/stream_run";  // 平台接口地址
    const QString SESSION_ID = "rbkdP69JpgfngtdcBejyt";                // 对话会话ID
    const int PROJECT_ID = 7646999877276155939;                        // 项目编号
};

#endif

6.3 CozeAI.cpp - 实现文件

AI 功能的真正实现层:这是整个 AI 功能最重要的文件,所有网络请求、解析、拼接、发送信号都在这里

#include "CozeAI.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>

// 构造函数:初始化AI网络模块
CozeAI::CozeAI(QObject *parent) : QObject(parent)
{
    // 创建Qt网络管理器,负责所有HTTP请求
    netManager = new QNetworkAccessManager(this);

    // 绑定信号槽:请求完成后自动调用 onReplyFinished
    connect(netManager, &QNetworkAccessManager::finished,
            this, &CozeAI::onReplyFinished);
}

// 设置API访问令牌(身份密钥)
// 作用:保存从扣子平台获取的Token,用于接口鉴权
void CozeAI::setApiToken(const QString &apiToken)
{
    currentApiToken = apiToken;
}

// 发送用户问题给AI
void CozeAI::sendMessage(const QString &question)
{
    // 安全判断:如果没设置Token,直接返回错误
    if(currentApiToken.isEmpty()) {
        emit replyReceived("[错误]AI密钥未配置");
        return;
    }

    // 第1步:构建网络请求对象
    QUrl url(API_URL);
    QNetworkRequest req(url);

    // 第2步:设置请求头(身份验证+数据格式)
    req.setRawHeader("Authorization", ("Bearer " + currentApiToken).toUtf8());
    req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    // ======================
    // 第3步:构建符合扣子API要求的JSON格式请求体
    // ======================
    QJsonObject root;

    // 封装用户输入的文本
    QJsonObject contentTextObj;
    contentTextObj["text"] = question;

    // 封装消息类型和内容
    QJsonObject promptItem;
    promptItem["type"] = "text";
    promptItem["content"] = contentTextObj;

    // 放入消息数组
    QJsonArray promptArray;
    promptArray.append(promptItem);

    // 组装query结构
    QJsonObject queryObj;
    queryObj["prompt"] = promptArray;

    // 组装content结构
    QJsonObject contentObj;
    contentObj["query"] = queryObj;

    // 组装最终JSON根对象
    root["content"] = contentObj;
    root["type"] = "query";
    root["session_id"] = SESSION_ID;
    root["project_id"] = PROJECT_ID;

    // 将JSON转为字节流,准备发送
    QByteArray jsonData = QJsonDocument(root).toJson(QJsonDocument::Compact);

    // 第4步:发送POST请求到扣子服务器
    netManager->post(req, jsonData);
}

// 网络请求完成,解析服务器返回的SSE数据
void CozeAI::onReplyFinished(QNetworkReply *reply)
{
    // 判断网络是否出错
    if (reply->error() != QNetworkReply::NoError) {
        emit replyReceived("[网络错误]" + reply->errorString());
        reply->deleteLater();
        return;
    }

    // 读取服务器返回的全部原始数据
    QByteArray raw = reply->readAll();
    QString fullAnswer = "";  // 存储最终拼接好的AI回答

    // SSE数据按行分割,逐行解析
    QStringList lines = QString(raw).split("\n");

    for (QString line : lines) {
        line = line.trimmed();  // 去掉空格换行

        // 只处理以 data: 开头的有效行
        if (!line.startsWith("data: ")) continue;

        // 去掉前缀 "data: ",取出后面的JSON内容
        QString jsonStr = line.mid(6);

        // 如果是结束标记,停止解析
        if (jsonStr == "[DONE]") break;

        // 转为JSON对象
        QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8());
        if (!doc.isObject()) continue;

        QJsonObject obj = doc.object();

        // 判断是否是AI回复内容
        if (obj["type"].toString() == "answer") {
            QJsonValue contentVal = obj["content"];
            if (contentVal.isObject()) {
                QJsonObject contentObj = contentVal.toObject();
                // 提取answer字段,拼接到完整回答中
                if (contentObj.contains("answer")) {
                    fullAnswer += contentObj["answer"].toString();
                }
            }
        }
    }

    // 如果没有获取到有效内容,给出提示
    if (fullAnswer.isEmpty()) {
        fullAnswer = "[AI] 未获取到有效回复";
    }

    // 发送信号,把AI回复传给UI界面显示
    emit replyReceived(fullAnswer);

    // 释放网络请求资源
    reply->deleteLater();
}

6.4 MainWindow中的集成

UI 界面与 AI 功能的连接层:它不做网络通信,只负责界面显示、用户操作、调用 AI、展示结果

#include "CozeAI.h"

// AI 智能体密钥配置,不同角色(业主/物业/管理员)使用不同的 Token
// 业主角色专用 AI 访问令牌(从扣子平台获取)
const QString OWNER_AI_TOKEN = "eyJhbGciOiJSUzI1NiIs...";

// 主窗口构造函数中初始化 AI,创建 AI 通信对象
cozeAI = new CozeAI(this);

// 根据当前登录用户的角色,分配对应的 AI 密钥
if (currentUser.role == "owner") {
    // 如果是业主,设置业主专用 AI Token
    cozeAI->setApiToken(OWNER_AI_TOKEN);
}

// 绑定信号与槽:接收 AI 返回的消息,AI 回答完成后,自动显示到界面聊天框
connect(cozeAI, &CozeAI::replyReceived, this, [=](const QString &rawAnswer){
    // 将 AI 返回的原始内容追加到聊天显示框
    ui->aiTextEdit->append("[AI] " + rawAnswer);
});

// 发送按钮点击事件,获取用户输入,发送给 AI,并显示在聊天界面
void MainWindow::on_aiSendBtn_clicked()
{
    // 获取输入框内容,并去除首尾空格
    QString txt = ui->aiInputEdit->text().trimmed();
    
    // 如果输入为空,不执行发送
    if(txt.isEmpty()) return;
    
    // 将用户输入的内容显示到聊天框
    ui->aiTextEdit->append("[我] " + txt);
    
    // 清空输入框,方便下次输入
    ui->aiInputEdit->clear();
    
    // 把问题发送给 AI 服务
    cozeAI->sendMessage(txt);
}

6.5 正则解析隐藏命令

cpp
void MainWindow::parseAndExecuteCMD(const QString &answer)
{
    // 正则表达式详解:
    // 【CMD:([^】]+)】
    //  |   |      |
    //  |   |      └─ 结束符】
    //  |   └──────── 捕获组:匹配除】以外的任意字符
    //  └──────────── 固定开头【CMD:
    
    QRegularExpression regex("【CMD:([^】]+)】");
    QRegularExpressionMatchIterator it = regex.globalMatch(answer);

    while (it.hasNext()) {
        QRegularExpressionMatch match = it.next();
        QString cmd = match.captured(1);  // 捕获组的内容
        
        if (cmd == "OPEN_FEE") {
            ui->stackedWidget->setCurrentIndex(3);  // 跳转缴费页
        }
        else if (cmd == "OPEN_REPAIR") {
            ui->stackedWidget->setCurrentIndex(2);  // 跳转报修页
        }
        else if (cmd == "OPEN_NOTICE") {
            ui->stackedWidget->setCurrentIndex(1);  // 跳转公告页
        }
    }
}

七、智能体实现页面跳转的提示词设计

7.1 基础提示词

# 人设
你是一个专业的物业智能管家,名叫"小管家"。服务对象是小区的业主用户。
性格特点:热情、耐心、专业、友好。回答简洁明了,不要过于冗长。

# 系统跳转命令
当需要引导用户跳转页面时,在回复末尾加上命令标记:
- 【CMD:OPEN_NOTICE】- 打开公告页面
- 【CMD:OPEN_REPAIR】- 打开报修页面
- 【CMD:OPEN_FEE】- 打开缴费页面

------>为什么要在提示词里写规则?

------:AI大模型(如GPT、豆包)本质上是根据提示词来“理解”自己要做什么。如果你不告诉它要输出命令,它就只会输出普通文本,你的程序就识别不到【CMD:XXX】。

# 功能范围
1. 物业服务:物业费查询、报修处理、公告查看
2. 生活服务:天气查询、美食推荐、出行规划
3. 健康咨询:提供健康建议,引导就医
4. 法律参考:提供基础法律常识,引导专业咨询

7.2 回答模板示例

用户问 AI回复
物业费多少钱 您好,物业费根据您的房型面积计算,具体金额请在缴费页面查看详情。【CMD:OPEN_FEE】
家里水管漏水 请您尽快提交维修申请,我们会安排师傅上门处理。【CMD:OPEN_REPAIR】
今天天气怎么样 调用天气插件获取实时天气信息并返回

总结

该系统已成功应用于小区管家物业综合服务平台,为业主提供了便捷、智能的物业服务体验,具有良好的实用价值和扩展性。
 

扩展方向:

一、智能体能力增强

  • 切换和扩展智能体,使业主、物业、管理员等不同角色获得定制化服务体验
  • 针对缴费、报修、投诉等场景设置专用智能体,实现垂直领域回答更专业精准,并根据任务复杂度在不同大模型间动态切换以优化性能与成本。
  • 同时添加插件能力,使AI管家可调用天气插件提供出行建议、日历插件实现缴费提醒、地图插件查询周边设施、知识库插件自动回答物业政策,将AI从对话机器人升级为具备行动能力的智能助理。
  • 优化提示词,通过增加命令类型(OPEN_FEE、OPEN_REPAIR等)实现更多页面跳转控制,丰富人设设定使回复更自然亲切,添加上下文记忆规则提供个性化服务。

二、对话历史记录功能

  • 添加对话历史记录功能,将用户与AI的问答持久化存储到数据库,设计会话表、消息表和反馈表。
  • 基于历史记录,系统可实现多轮对话上下文记忆——再次提问时自动加载历史对话,使AI理解指代关系、回答更连贯自然。
  • 实现用户行为分析,提供历史查询回溯界面供用户查看交流记录,支持管理员导出数据进行分析,了解业主高频诉求,为服务优化提供数据支撑。

三、语音识别集成

  • 集成语音识别功能,实现从纯文字到“语音输入+语音输出”的全方位语音对话体验。
  • 进一步增强包括语音唤醒(关键词免点击唤醒)、连续对话模式、方言支持及情绪分析(识别愤怒语气时优先转人工),真正实现“说话就能办事”的无障碍使用体验。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐