


























最近开发 「Verbiverse」这个工具,学了些 QT pyside6 与 LangChain 相关的知识与使用技巧,这篇文章主要会以项目解析为主,同时总结下 pyside6 与 LangChain 使用技巧。
如果你想参与 Verbiverse 一起开发新功能或是打算利用 Pyside6 + LangChain 构建自己的大语言模型应用,这篇文章可以对你有所帮助。
Verbiverse 工具介绍:「利用大模型,我就是想一边刷美剧,一边把英语学好!「Verbiverse V1.0」」
主要使用 python 开发,我是用 poetry 作为包管理器,基本使用如下:
poetry new <project name>poetry env infopyenv local <version>; poetry env use <python-path>poetry shellexit会在项目根目录生成 pyproject.toml 文件,记录项目信息与依赖库,可以使用命令或者手动修改,修改完后记得 poetry lock 并 update 下更新依赖。
poetry 同样可以配合 conda 使用,进入 conda 虚拟环境后,查看 env info 如下:

Verbiverse 项目 环境搭建:
git clone https://github.com/HATTER-LONG/Verbiverse.gitconda create -n Verbiverse python=3.11conda activate Verbiverse;python3 -m venv ./.venv;source ./venv/bin/activate;pip install -U pip setuptools;pip install poetry;poetry install:pyproject.toml 中 [[tool.poetry.source]] 相关注释,然后重新 poetry lock --no-update;python3 main.py.
├── LICENSE
├── PDF_js # 开源 PDF js 源码,加了一个 qt web 的钩子用来订阅 pdf 页数变化
├── PIC
├── README.md
├── RoadMap.md
├── build.py # 辅助项目开发的脚本,支持 ui -> python、国际化、生成 qrc、优化 prompt 等功能
├── command_alias # alias 开发脚本命令
├── icons
├── main.py # 程序入口
├── main.spec # pyinstaller 打包脚本
├── poetry.lock
├── pyproject.toml # poetry 版本控制
├── tests # 测试代码
├── tools # 工具代码,主要用来优化 prompt
└── verbiverse # 项目主要源码
├── CustomWidgets # 自定义子控件目录
├── Functions # 通用的函数方法目录
├── LLM # 基于 LangChain 相关 LLM 实现目录
├── MainWindow.py # 主界面框架代码
├── UI # 主要 UI 界面目录
├── __init__.py
└── resources # 资源文件、翻译、图片、提示词等
看过 Verbiverse 界面稍微了解 QT 的同学就会发现这个 UI 和原生界面的区别,我这里用了 PyQt-Fluent-Widgets ,一个基于 Qt 的 Fluent 设计风格组件库,可以方便我们快速构建出一个 像模像样 的 UI 界面,而不用细扣 qss。
接下来以 Verbiverse 工具的视频播放界面举例如何做的:

verbiverse/MainWindow.py 是主界面框架代码,其控制如何添加子页面,我们先简单添加个测试页面:
测试页面代码,很简单,只是在页面正中间显示一个 SubtitleLabel,这个 SubtitleLabel 看起来很陌生因为它是 qfluentwidgets 框架提供的:
class Widget(QFrame):
def __init__(self, text: str, parent=None):
super().__init__(parent=parent)
self.label = SubtitleLabel(text, self)
self.hBoxLayout = QHBoxLayout(self)
setFont(self.label, 24)
self.label.setAlignment(Qt.AlignCenter)
self.hBoxLayout.addWidget(self.label, 1, Qt.AlignCenter)
self.setObjectName(text.replace(" ", "-"))
# !IMPORTANT: leave some space for title bar
self.hBoxLayout.setContentsMargins(0, 32, 0, 0)如下图修改,FluentWindow 同样是 qfluentwidgets 提供的框架页面,因此想要添加子页面很简单:

效果如下图,可以看到我们成功添加了一个 Test 子页面:

接下来我们使用 Qt Designer 工具创建我们基本的页面结构:
pyside6-designer &;
pyside6-uic verbiverse/UI/test.ui -o verbiverse/UI/test_ui.py
from PySide6.QtWidgets import QWidget
from test_ui import Ui_Form
class CTest(QWidget, Ui_Form):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)

verbiverse/MainWindow.py 中使用 CTest 新增子页面:
最终我们成功添加了自己设计的页面,虽说这个界面大部分还是空的 QWidget,但接下来会一步步完善:

视频播放区我们可以直接使用 qfluentwidgets 提供的 VideoWidget:
先选中 Video 部分占位的 QWidget 控件,鼠标右键提升为 qfluentwidgets.multimedia 的 VideoWidget:



再使用 build 或者 pyside6-uic 命令重新生成 python 代码,就可以看到正常使用 video_widget 控件了:

最终效果如下:

子菜单很好加,如下代码即可:
from PySide6.QtCore import QPoint, Qt, Slot
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QWidget
from qfluentwidgets import FluentIcon as FIF
from qfluentwidgets import (
RoundMenu,
)
from test_ui import Ui_Form
class CTest(QWidget, Ui_Form):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
# 设置自定义菜单
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._onContextMenuRequested)
# 子菜单回调
@Slot(QPoint)
def _onContextMenuRequested(self, event: QPoint) -> None:
menu = RoundMenu(parent=self)
# 增加菜单项
menu.addAction(
QAction(
FIF.ADD_TO.icon(),
self.tr("test"),
self,
triggered=lambda: print("test"), # 回调函数
)
)
menu.exec(self.mapToGlobal(event))最终效果如下:

字幕功能区域,是页面的核心功能区,这可就没有现成的插件可以供我们用了,这时就需要 Custom Widgets:
在 verbiverse/CustomWidgets 目录中新建控件代码文件:

与前面章节类似,提升字幕区域占位的 QWidget 为我们自己创建的控件:


重新 build 将 ui 生成为 python 代码后,在我们的子页面可以测试控件是否正常:

最终效果如下:

最终右侧的字幕列表与视频列表是通过 Tab 标签业实现,其实与自定义的字幕控件流程差别不大,这里就不再细说,简单说下用到的控件:
from PySide6.QtWidgets import (
QStackedWidget,
QVBoxLayout,
QWidget,
)
from qfluentwidgets import ListWidget, SegmentedWidget
class CTabWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.pivot = SegmentedWidget(self)
self.stackedWidget = QStackedWidget(self)
self.vBoxLayout = QVBoxLayout(self)
self.subtitle = ListWidget(self)
self.file_list = ListWidget(self)
# add items to pivot
self.addSubInterface(self.subtitle, "SubTitleInterface", self.tr("SubTitle"))
self.addSubInterface(
self.file_list, "videoFileInterface", self.tr("Video List")
)
self.vBoxLayout.addWidget(self.pivot)
self.vBoxLayout.addWidget(self.stackedWidget)
self.vBoxLayout.setContentsMargins(15, 10, 15, 30)
self.stackedWidget.setCurrentWidget(self.subtitle)
self.pivot.setCurrentItem(self.subtitle.objectName())
self.pivot.currentItemChanged.connect(
lambda k: self.stackedWidget.setCurrentWidget(self.findChild(QWidget, k))
)
def addSubInterface(self, widget: QWidget, objectName, text):
widget.setObjectName(objectName)
self.stackedWidget.addWidget(widget)
self.pivot.addItem(routeKey=objectName, text=text)
依旧提升控件为我们自定义的即可,子页面中我们增加测试代码:
class CTest(QWidget, Ui_Form):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._onContextMenuRequested)
self.subtitel_browser.setText("test subtitle")
# 添加列表数据
widget = QListWidgetItem("test list")
self.tab_widget.subtitle.addItem(widget)最终效果:

我们最开始添加子页面时,是固定写死了页面名 Test,如果想要支持多语言翻译,则需要使用 self.tr 函数:
TL;DR 使用 self.tr 函数包裹翻译字符串后,项目配套的脚本直接使用 build 会自动生成对应 ts 文件,手动修改完翻译后,再次 build 即可生成最终的资源文件:
- 不太理解这个操作的话,可以看下文手动操作流程。

手动操作步骤如下:
verbiverse/MainWindow.py 中使用 self.tr 函数,然后生成 ts 翻译文件:

将分散的文件合并到统一的 ts 中,使用命令转为 qm 格式文件:

生成资源文件:

代码中加载对应文件翻译即可:

最终效果如下:

LangChain 作为流行的开源框架,旨在帮助开发快速创建基于大语言模型的应用程序。
一个基于 LLM 模型的对话工具一般由以下几个部分:
基本的对话系统如下:
from langchain.schema import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
# 配置信息
api_key = "lm-studio"
api_url = "http://localhost:1234/v1"
model = "Qwen/Qwen1.5-7B-Chat-GGUF/qwen1_5-7b-chat-q6_k.gguf"
# 创建 OpenAI Chat
chat = ChatOpenAI(
model_name=model,
openai_api_key=api_key,
openai_api_base=api_url,
temperature=0.7,
)
# 创建一个对话 prompt 模板,标记了 system 的信息
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant. Answer all questions to the best of your ability.",
),
# 用户信息占位符,后续用户输入的信息,可以通过 variable_name 进行链接
MessagesPlaceholder(variable_name="messages"),
]
)
msg = {
# 标记为 messages 信息,与 prompt 链接
"messages": [
HumanMessage(
content="Translate this sentence from English to Chinese: I love programming."
),
AIMessage(content="我喜欢编程。"),
HumanMessage(content="What did you just say?"),
]
}
# 通过 | 管道符号链接 prompt 与 chat
mchain = prompt | chat
for chunk in mchain.stream(msg):
print(chunk.content, end="", flush=True)官方教程已经很详细了:How to add chat history | 🦜️🔗 LangChain
主要流程如下图,当用户输入请求后会使用 LLM 对请求进行向量化,经由向量数据库检索最接近的内容,将这些内容与输入一起组合发给 LLM 得到最终的答案:

Verbiverse 有关 LLM 相关的代码均在 verbiverse/LLM 目录下,相关提示词在 verbiverse/resources/prompt/ 资源目录下:
❯ tree ./LLM -L 1
./LLM
├── ChatChain.py # 基础对话类,支持记录最近 10 条对话记录,之前老版本用于阅读界面对话
├── ChatRAGChain.py # 支持传入 PDF 数据作为数据嵌入的 RAG 问答类,当前 V1.0 支持嵌入后使用此类
├── ChatWithCustomHistoryChain.py # 支持传入指定历史对话的对话类,用于输入检查功能
├── ChatWorkerThread.py # 对话多线程异步请求类
├── ExplainWorkerThread.py # 解释多线程异步请求类
├── LLMServerInfo.py # 根据配置 LLM 信息获取,包括提示词、使用的模型等
├── ModuleLogger.py
├── OpenAI.py # OpenAI 接口适配
├── TongYiQWen.py # 通义千问接口适配
└── __init__.py结构图如下:

创建一个基本的对话系统很简单,使用 tests/test_ChatChain.py 的代码即可,前提是先配置好了相关模型信息:
import verbiverse.resources.resources_rc # noqa: F401
from verbiverse.LLM.ChatChain import ChatChain
if __name__ == "__main__":
chat = ChatChain()
while True:
input_string = input("> ")
for chunk in chat.stream(input_string):
print(chunk.content, end="", flush=True)
print("\n")
本来想要将 LangChain 写的详细些,但是发现文章有些太长了而且都是在复制官方的教程,实在没什么营养 🤣 !
最终决定主要还是围绕解析项目源码结构为主,也方便想要参与项目的同学快速上手。
觉得文章写的不错对 Verbiverse 工具感兴趣的话,快来给项目个 star 吧 🙏 🥺 !
项目源码路径:HATTER-LONG/Verbiverse: 利用 LLM 大模型辅助阅读 PDF 与观看视频,用以提升语言能力。 (github.com)
文章中的演示代码分支:HATTER-LONG/Verbiverse at train (github.com)
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。