从零到一:纯代码联合PyQt5、Ollama、Deepseek打造简易版智能聊天助手

如何用Python调用本地模型实现DeepSeek提示词模板:一步步教你高效解决13种应用场景
从零到一:纯代码联合PyQt5、Ollama、Deepseek打造简易版智能聊天助手
用外接知识库武装大模型:基于Deepseek、Ollama、LangChain的RAG实战解析
纯代码实战–用Deepseek+SQLite+Ollama搭建数据库助手

概要

本文介绍基于PyQt5框架与Ollama大语言模型搭建的智能对话系统。通过QT实现简易的GUI界面,结合多线程技术实现AI模型的异步调用,支持流式响应输出。项目实现了类ChatGPT的交互体验,具有实时对话、流式响应、错误处理等核心功能。

提示:完整代码看最后,复制后可直接运行

整体架构流程

  1. 界面层:采用PyQt5构建聊天窗口框架
  2. 逻辑层:通过QThread实现异步调用
  3. 模型层:对接Ollama本地大模型服务
  4. 数据流:用户输入→界面展示→后台处理→流式输出

代码模块详解

AIWorker: 模型调用与流式处理
ChatWindow:界面管理与用户交互
StyleSheet:界面美化配置

  1. 导入模块
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTextEdit, QPushButton
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from ollama import chat

sys 用于处理程序的命令行参数。
PyQt5.QtWidgets 提供了图形用户界面组件,如 QApplication、QWidget、QVBoxLayout、QTextEdit、QPushButton 等。
PyQt5.QtCore 提供了核心功能,如 QThread(用于后台线程)和 pyqtSignal(用于信号与槽机制)。
ollama.chat 用于与AI模型进行交互。

2、AIWorker 类

class AIWorker(QThread):
    update_signal = pyqtSignal(str)

AIWorker 继承自 QThread,用于在后台线程中运行与AI模型的对话。
update_signal 是一个自定义信号,用于将AI模型的响应传递回主线程。

    def __init__(self, user_input):
        super().__init__()
        self.user_input = user_input

构造函数接受用户输入并初始化线程。

    def run(self):
        try:
            stream = chat(
                model='deepseek-r1:7b',
                messages=[{'role': 'user', 'content': self.user_input}],
                stream=True,
            )
            for chunk in stream:
                content = chunk['message']['content']
                self.update_signal.emit(content)
        except Exception as e:
            self.update_signal.emit(f"\n[Error] {str(e)}")

run 方法在后台线程中执行,发送用户输入到AI模型并接收模型的流式输出。
stream 为一个迭代器,逐步接收AI模型的响应。
如果有异常,错误消息将通过信号发送回主线程。

3、ChatWindow 类

class ChatWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.ai_thread = None

ChatWindow 类继承自 QWidget,表示聊天窗口的主界面。
ai_thread 用于存储后台执行的AI线程。
3.1界面初始化

    def init_ui(self):
        self.setWindowTitle("AI Chat")
        self.setGeometry(100, 100, 600, 500)

        layout = QVBoxLayout()

设置窗口标题和大小。
使用 QVBoxLayout 垂直布局组件。

        self.chat_display = QTextEdit()
        self.chat_display.setReadOnly(True)
        self.chat_display.setStyleSheet("""
            background-color: #f0f0f0;
            font-size: 14px;
            padding: 10px;
        """)

chat_display 用于显示聊天内容,设为只读模式。
使用CSS样式来定制文本框的外观。

        self.input_area = QTextEdit()
        self.input_area.setMaximumHeight(100)
        self.input_area.setPlaceholderText("输入您的问题...")
        self.input_area.setStyleSheet("""
            background-color: white;
            font-size: 14px;
            padding: 10px;
        """)

input_area 用于用户输入消息。
设置最大高度并添加占位符文本。

        self.send_btn = QPushButton("提问")
        self.send_btn.setStyleSheet("""
            QPushButton {
                background-color: #4CAF50;
                color: white;
                border: none;
                padding: 10px;
                font-size: 14px;
            }
            QPushButton:hover { background-color: #45a049; }
        """)
        self.send_btn.clicked.connect(self.send_message)

send_btn 是一个按钮,点击时会调用 send_message 方法发送消息。
使用CSS样式定制按钮外观。

        layout.addWidget(self.chat_display)
        layout.addWidget(self.input_area)
        layout.addWidget(self.send_btn)
        self.setLayout(layout)

添加聊天框、输入框和按钮到布局中,并将布局设置为窗口的布局。

3.2 发送消息

    def send_message(self):
        user_input = self.input_area.toPlainText().strip()
        if not user_input:
            return

获取用户输入的文本,如果输入为空,则直接返回。

        self._append_message("You", user_input)
        self.input_area.clear()

将用户输入的消息添加到聊天显示框中,并清空输入框。

        self._append_message("Bot", "思考中...", is_streaming=True)

在聊天显示框中显示“思考中…”以提示用户AI正在生成回应。

        self.ai_thread = AIWorker(user_input)
        self.ai_thread.update_signal.connect(self.update_bot_response)
        self.ai_thread.finished.connect(self.finalize_response)
        self.ai_thread.start()

创建并启动一个 AIWorker 线程处理AI响应。
update_signal 连接到 update_bot_response 方法,用于更新聊天框内容。
finished 信号连接到 finalize_response 方法,确保在响应完成后进行处理。

3.3 更新聊天显示

    def _append_message(self, sender, message, is_streaming=False):
        cursor = self.chat_display.textCursor()
        cursor.movePosition(cursor.End)

_append_message 方法用于将消息插入到 chat_display 中。
设置光标位置到文本末尾,确保新消息被添加到显示区的底部。

        if sender == "You":
            prefix = "\nYou: "
            color = "#333333"
        else:
            prefix = "\nBot: "
            color = "#2196F3" if not is_streaming else "#888888"

为不同发送者(用户和Bot)设置不同的颜色和前缀。

        cursor.insertText(prefix)
        cursor.insertHtml(f'<span style="color:{color}">{message}</span>')
        self.chat_display.ensureCursorVisible()

将消息插入到文本框中,并确保光标可见。

3.4 更新Bot的响应

    def update_bot_response(self, content):
        current_text = self.chat_display.toPlainText()
        if current_text.endswith("思考中..."):
            self.chat_display.setPlainText(current_text[:-4] + content)
        else:
            cursor = self.chat_display.textCursor()
            cursor.movePosition(cursor.End)
            cursor.insertText(content)
        self.chat_display.ensureCursorVisible()

当接收到Bot的流式内容时,更新显示区。
如果当前显示的是“思考中…”,则将其替换为实际响应。

3.5 完成响应

    def finalize_response(self):
        current = self.chat_display.toPlainText()
        if not current.endswith("\n"):
            self.chat_display.append("")

确保聊天显示区的最后一行有换行符,避免显示不完整。

搭建后示例如:
在这里插入图片描述

待改进

存在问题
透明字体不清晰
点击提问后聊天窗口跳转至首行
历史记录保存
多模型切换

完整代码

提示:pyQt5需通过pip自行安装 Deepseek预训练模型下载见本人上篇博客:https://blog.youkuaiyun.com/qq_36112576/article/details/145510585?spm=1001.2014.3001.5502 一、2模型下载部分
代码中model='deepseek-r1:7b’根据个人情况自行替换

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTextEdit, QPushButton
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from ollama import chat


# AIWorker类,继承自QThread,用于在后台线程中处理与AI的交互
class AIWorker(QThread):
    # 自定义信号,用于更新UI上的聊天内容
    update_signal = pyqtSignal(str)

    def __init__(self, user_input):
        super().__init__()
        # 接收用户输入,准备与AI进行对话
        self.user_input = user_input

    def run(self):
        try:
            # 使用Ollama API与AI模型进行对话,启动流式响应
            stream = chat(
                model='deepseek-r1:7b',
                messages=[{'role': 'user', 'content': self.user_input}],
                stream=True,
            )

            # 遍历返回的流式数据,逐块接收消息
            for chunk in stream:
                content = chunk['message']['content']
                # 将接收到的内容通过信号发射到UI
                self.update_signal.emit(content)
        except Exception as e:
            # 如果出现异常,将错误信息通过信号发射
            self.update_signal.emit(f"\n[Error] {str(e)}")


# 主聊天窗口类
class ChatWindow(QWidget):
    def __init__(self):
        super().__init__()
        # 初始化UI界面
        self.init_ui()
        self.ai_thread = None

    def init_ui(self):
        self.setWindowTitle("AI Chat")  # 设置窗口标题
        self.setGeometry(100, 100, 600, 500)  # 设置窗口初始大小与位置

        layout = QVBoxLayout()  # 创建垂直布局

        # 创建聊天显示框,设置为只读,方便显示聊天内容
        self.chat_display = QTextEdit()
        self.chat_display.setReadOnly(True)
        self.chat_display.setStyleSheet("""
            background-color: #f0f0f0;
            font-size: 14px;
            padding: 10px;
        """)

        # 创建输入框,用户输入消息
        self.input_area = QTextEdit()
        self.input_area.setMaximumHeight(100)
        self.input_area.setPlaceholderText("输入您的问题...")
        self.input_area.setStyleSheet("""
            background-color: white;
            font-size: 14px;
            padding: 10px;
        """)

        # 创建发送按钮,点击时发送消息
        self.send_btn = QPushButton("提问")
        self.send_btn.setStyleSheet("""
            QPushButton {
                background-color: #4CAF50;
                color: white;
                border: none;
                padding: 10px;
                font-size: 14px;
            }
            QPushButton:hover { background-color: #45a049; }
        """)
        self.send_btn.clicked.connect(self.send_message)  # 点击按钮时调用send_message

        # 将组件添加到布局中
        layout.addWidget(self.chat_display)
        layout.addWidget(self.input_area)
        layout.addWidget(self.send_btn)
        self.setLayout(layout)

    def send_message(self):
        # 获取用户输入的文本并去掉空白
        user_input = self.input_area.toPlainText().strip()
        if not user_input:
            return  # 如果输入为空,返回

        # 在聊天窗口中显示用户输入的消息
        self._append_message("You", user_input)
        self.input_area.clear()  # 清空输入框

        # 在聊天窗口中显示“思考中...”提示
        self._append_message("Bot", "思考中...", is_streaming=True)

        # 启动后台线程,处理与AI的对话
        self.ai_thread = AIWorker(user_input)
        # 连接信号和槽函数,更新聊天内容
        self.ai_thread.update_signal.connect(self.update_bot_response)
        # 连接线程完成后的槽函数
        self.ai_thread.finished.connect(self.finalize_response)
        # 启动线程
        self.ai_thread.start()

    def _append_message(self, sender, message, is_streaming=False):
        # 获取文本光标,确保内容插入到聊天窗口的末尾
        cursor = self.chat_display.textCursor()
        cursor.movePosition(cursor.End)

        # 设置消息的前缀和颜色,用户消息和机器人消息区分开
        if sender == "You":
            prefix = "\nYou: "
            color = "#333333"  # 用户消息颜色
        else:
            prefix = "\nBot: "
            color = "#2196F3" if not is_streaming else "#888888"  # 机器人消息颜色,正在处理时颜色较灰

        # 插入消息
        cursor.insertText(prefix)
        cursor.insertHtml(f'<span style="color:{color}">{message}</span>')
        # 确保文本框显示最新内容
        self.chat_display.ensureCursorVisible()

    def update_bot_response(self, content):
        # 如果聊天框正在显示“思考中...”提示,则替换为实际响应内容
        current_text = self.chat_display.toPlainText()
        if current_text.endswith("思考中..."):
            self.chat_display.setPlainText(current_text[:-4] + content)
        else:
            # 否则直接插入新的消息
            cursor = self.chat_display.textCursor()
            cursor.movePosition(cursor.End)  # 确保插入文本到末尾
            cursor.insertText(content)  # 插入文本
        self.chat_display.ensureCursorVisible()

    def finalize_response(self):
        # 确保最后有换行符,使聊天内容整洁
        current = self.chat_display.toPlainText()
        if not current.endswith("\n"):
            self.chat_display.append("")


# 主程序入口
if __name__ == "__main__":
    app = QApplication(sys.argv)  # 创建应用程序
    window = ChatWindow()  # 创建聊天窗口实例
    window.show()  # 显示窗口
    sys.exit(app.exec_())  # 进入应用的主循环

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值