十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用PyQt5开发AI桌面宠物:能感知代码场景的赛博伙伴

用PyQt5开发AI桌面宠物:能感知代码场景的赛博伙伴 不知道你有没有过这种时候——深夜加班调一个bug编辑器里只有光标在一闪一闪整个房间安静得能听见风扇声。刚开始我还能靠播放白噪音扛过去时间一长那种一个人面对屏幕的孤独感是真的会发酵。后来我给自己整了一个桌面宠物不是那种只会卖萌的皮卡丘而是一个内置了AI对话能力的赛博伙伴它知道自己面前正在写的是Python还是JavaScript会在我连续敲了两小时的时候提醒我站起来也会在我抛出一段报错日志后给出排查思路。这个项目我叫它CodeWalkers简单说就是把AI助手装进一个桌面宠物的壳里让它陪你写代码。这个项目适合谁首先是长时间对着编辑器、容易进入“写代码写到自闭”状态的开发者其次是喜欢折腾桌面小工具、想学PyQt5或者想自己接一次AI API的程序员另外如果你只是想在工作时有个不打扰的小东西在旁边动一动它也能满足你。整个项目不依赖商业桌面宠物软件代码逻辑自己可控AI能力可以接云端大模型也可以接本地模型数据隐私和安全问题都能自己把握。这篇文章我会把从立项、技术选型到核心实现、踩坑记录整个流程拆开讲清楚代码结构和实现思路都会给到属于可以直接照着复现的那种。1. 这个项目到底想解决什么问题1.1 立项初衷桌面宠物不只是可爱桌面宠物这个品类不算新鲜。早在十几年前桌面宠物就已经存在了但大多数停留在“会动、会卖萌、会陪你点鼠标”的层次。它们和用户之间没有真正的信息交互既不知道你在干什么也不能对你的输入给出有意义的回应。时间一长这个宠物就变成了一个纯粹的视觉装饰甚至因为占用屏幕空间被用户关掉。CodeWalkers的出发点是把“宠物”和“AI助手”揉在一起。目标不是做一个更可爱的宠物而是做一个有场景感知能力的陪伴工具。所谓场景感知就是它至少能感知到两件事你正在用什么编辑器写什么语言以及你当前是长时间连续编码、还是刚切换了任务、还是离开了电脑。有了这两个信息宠物的行为就不再是随机触发的而是跟着你的工作节奏走的。比如检测到你长时间盯着某个报错窗口它会主动跳出来问一句“需要我帮忙看看吗”这种交互就比定时提醒有意义得多。还有一点是我个人的真实需求我希望这个东西能离线断网也能保持在桌面上活动。很多在线AI助手必须联网网络波动就直接哑火。所以从设计之初CodeWalkers就把AI能力做成可插拔的云端API和本地模型都能跑本地模型挂掉的时候宠物至少还保留表情动画和本地规则提醒的能力。1.2 核心功能拆解一个陪伴型AI助手的日常我把CodeWalkers的功能分成四层这个分层后来也被证明非常方便调试和扩展。第一层是基础宠物层包括透明背景的宠物窗口、帧动画播放、鼠标拖拽、托盘菜单、定时小动作。这一层解决的是“存在感”问题让桌面上真的有一个活物在动。第二层是上下文感知层包括前台窗口检测、代码文件类型识别、编辑器活动状态监听、连续工作时长统计。这一层解决的是“知道你在干什么”的问题是整个项目最核心的技术差异点。第三层是AI对话层包括对话上下文管理、流式输出、意图识别、Prompt模板。这一层解决的是“能和你对话”的问题让宠物在关键时刻能给出真正的技术建议而不是说废话。第四层是交互反馈层包括气泡对话框、动作触发机制、音量反馈、主动提醒策略。这一层解决的是“交互自然度”的问题让AI的输出和宠物的动作能配合起来。这个分层结构让开发节奏非常清晰。我先做第一层让宠物先在桌面上“活起来”然后做第二层让它“看懂”用户的工作场景再做第三层让它“会回答”最后做第四层把前三层串起来形成完整的体验。很多桌面宠物项目之所以烂尾就是一开始就想把所有功能揉在一起做结果动画和AI耦合严重改一个功能崩一片。分层之后每一层都有独立的配置和测试入口后期维护轻松非常多。2. 技术选型为什么是Python PyQt5 OpenAI兼容层2.1 桌面端框架对比PyQt5/PySide6/Tkinter/Electron做桌面宠物首要问题是选GUI框架。我最早试过Tkinter优点是Python自带、零安装成本但缺点也很明显透明窗口支持很差动画性能一塌糊涂想要实现高品质的RGB透明通道几乎不可能。一个桌面宠物如果连透明背景都做不好那整个质感就没了。所以Tkinter一开始就被排除了。Electron倒是能把前端动画做得很好看CSS动画、Lottie都能用CPU和内存占用实在太夸张一个宠物吃500MB内存这不是陪伴工具这是内存刺客。而且Electron启动一个Node进程再拉起一个浏览器内核鼠标穿透和全局监听也得靠C插件辅助开发配置复杂度高不符合轻量小工具的思路。最后我选了PyQt5。它在Windows、macOS、Linux三个平台都能跑QWidget支持WA_TranslucentBackground属性可以直接实现窗口透明QTimer驱动帧动画性能足够一个100帧左右的小动画跑起来CPU占用可以控制在5%以内配合QSystemTrayIcon做托盘菜单也是现成方案。虽然PyQt5的许可证是GPL但个人项目和内部工具使用没有大问题如果需要商用可以考虑PySide6API几乎一致本项目的代码可以平滑迁移。选PyQt5还有一个隐藏优势Python生态里现有的很多工具库可以直接复用。比如用psutil获取系统状态用pywin32或AppKit获取前台窗口信息用requests调用HTTP API这些库和PyQt5搭配非常顺不需要引入跨语言的胶水层。2.2 AI接入方案云端API与本地模型怎么共存AI对话模块的技术选型是另一个关键决策。CodeWalkers没有绑定任何一家云厂商而是统一走OpenAI兼容的接口格式因为目前几乎所有主流的模型服务商包括很多国产大模型对外开放的API都兼容这种格式。这样做的好处是切换模型只需要改一个base_url和密钥代码层完全不用动。如果你追求回复质量云端API是首选上下文理解能力强响应快代码审查能力也更好。我实测下来让模型分析一段有问题的Python函数云端大模型基本能一步到位指出缩进和边界条件问题这个体验是本地小模型暂时比不了的。如果你更看重隐私和离线可用本地模型也能接入。我目前用的是Ollama加载Qwen系列和CodeLlama系列量级在7B到14B之间。本地模型对显存要求比较高7B模型量化后大约需要6GB显存14B模型8GB以上。没独显的机器用CPU跑会很痛苦生成一个短回答可能要等十几秒作为对话体验不太行但纯离线环境下能用就已经是胜利了。为了保证云端和本地都能稳定工作我在AI模块里做了一个模型路由。配置文件里可以同时配置云端模型和本地模型代码会根据当前网络连通性自动选择。如果本地模型可用就优先走本地本地模型返回超时再自动切换云端。这个策略让CodeWalkers在有网没网的环境里都能保持对话能力实测切换损耗大约只有几百毫秒。2.3 整体架构与目录规划选完技术栈我建议先规划目录结构再动手。CodeWalkers目前的目录结构长这样codewalkers/ ├── main.py # 入口文件 ├── config.yaml # 全局配置 ├── requirements.txt # 依赖清单 ├── core/ │ ├── window.py # 透明窗口与事件管理 │ ├── animation.py # 帧动画播放器 │ ├── pet.py # 宠物状态机 │ ├── context.py # 前台窗口/代码场景感知 │ └── bubble.py # 气泡对话框 ├── ai/ │ ├── router.py # 模型路由 │ ├── client.py # OpenAI兼容请求客户端 │ ├── prompts.py # Prompt模板管理 │ └── memory.py # 对话记忆管理 ├── utils/ │ ├── platform.py # 跨平台系统调用 │ ├── logger.py # 日志模块 │ └── timing.py # 时间/计时工具 └── assets/ ├── idle/ # 待机动画帧 ├── happy/ # 开心动画帧 └── speak/ # 说话动画帧这样的目录有几个好处core层不依赖任何AI逻辑即使把AI模块整个删掉宠物依然能正常显示和动ai层不依赖具体的平台API换模型、换API服务商都只改配置assets目录单独拎出来方便之后换一套美术素材不影响代码逻辑。实话说很多个人项目一开始都是把脚本堆在同一个文件里Codewalkers经历过这个阶段arra函数一多就乱成一锅粥拆目录之后开发效率反而上去了。3. 核心模块实现从零搭一个“赛博伙伴”3.1 无边框透明窗口与帧动画播放桌面宠物的视觉基础就是那个透明窗口。PyQt5里实现透明窗口非常直接from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QLabel, QApplication class PetWindow(QWidget): def __init__(self): super().__init__() # 无边框 窗口置顶 不在任务栏显示 self.setWindowFlags( Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool ) # 透明背景 self.setAttribute(Qt.WA_TranslucentBackground) # 固定尺寸后续可以通过缩放控制宠物大小 self.resize(256, 256) # 用QLabel承载画面 self.label QLabel(self) self.label.setGeometry(0, 0, 256, 256)这段代码是整个项目的基石。其中Qt.Tool这个flag容易被忽略它的作用是让窗口不出现在任务栏和AltTab切换列表里桌面宠物应该像一个装饰物而不是一个应用窗口。帧动画播放器的核心是QTimer定时切换帧。我准备了一组PNG序列帧通过字典管理不同动作的帧列表# animation.py 核心逻辑 from PyQt5.QtCore import QTimer from PyQt5.QtGui import QPixmap class FramePlayer: def __init__(self, label, fps12): self.label label self.timer QTimer() self.timer.timeout.connect(self._next_frame) self.frames [] self.index 0 self.fps fps def play(self, frames): self.frames frames self.index 0 self.timer.start(int(1000 / self.fps)) def stop(self): self.timer.stop() def _next_frame(self): if not self.frames: return self.index (self.index 1) % len(self.frames) self.label.setPixmap(self.frames[self.index])这里fps我建议设置在10到15之间。小于10帧看起来卡顿超过20帧CPU占用会明显上升而且对可爱的观感增益有限。每一帧PNG建议用256x256尺寸控制单张图片在50KB以内这样可以保证内存占用处于较低水平。3.2 AI对话模块上下文记忆与流式输出AI对话模块最忌讳的是每次提问都无状态调用模型完全不知道你之前问过什么对话体验非常撕裂。我设计了一个简单但有效的上下文管理机制保留最近10轮对话内容并带有场景摘要。# memory.py 简单轮次管理 class ConversationMemory: def __init__(self, max_rounds10): self.max_rounds max_rounds self.history [] def add_user(self, content): self.history.append({role: user, content: content}) self._trim() def add_assistant(self, content): self.history.append({role: assistant, content: content}) self._trim() def _trim(self): if len(self.history) self.max_rounds * 2: self.history self.history[-(self.max_rounds * 2):] def to_messages(self, system_prompt): return [{role: system, content: system_prompt}] self.history流式输出在对话生成速度很快的云端模型上体验很好逐字往外蹦很有“活的在说话”的感觉。但如果接的是本地小模型流式反而容易因为逐字渲染造成气泡闪烁。我做了一个开关默认云端开流式、本地关流式也可以在配置里手动控制。Prompt模板的设计对桌面宠物来说特别重要。通用型AI助手可以直接说“你是一个编程助手”但桌面宠物有角色扮演属性不能太机械化。我的system prompt是这样写的你现在是一个名叫CodeWalkers的桌面宠物性格元气、话痨但不过度。 你陪伴一位开发者工作能看到他正在编写的文件类型和编辑器名称。 你可以主动提醒他休息也可以在他提问时给出简短、准确的技术建议。 注意日常闲聊回复不超过50字技术回答不超过300字。 如果用户问的是超出你能力范围的问题直接说明你不会不要硬编。注意末尾那条防止幻觉的约束。桌面宠物的场景本来就是陪伴为主如果模型一本正经地编造API用法会误导用户所以必须在Prompt里明确禁止。3.3 代码场景感知知道你在写什么这是CodeWalkers区别于普通桌面宠物的核心。它需要知道用户当前正在干什么才能做出有针对性的反馈。跨平台获取前台窗口的信息每个系统的做法都不一样Windows用pywin32的GetForegroundWindow函数拿到窗口句柄再通过GetWindowText获取标题。macOS用AppKit的NSWorkspace获取当前活跃应用再读取窗口标题。Linux用xdotool getactivewindow getwindowname命令获取或者用wmctrl -l解析。拿到窗口标题之后下一步是解析出关键信息。比如窗口标题是bot.py - Visual Studio Code可以提取出当前文件是bot.py编辑器是VS Code。文件后缀映射到语言类型这个映射表比较长但核心就一条读后缀映射语言。Python对应.pyJavaScript对应.js和.tsJava对应.javaGo对应.go等等。上下文模块每2秒轮询一次前台窗口信息存到内存里。宠物状态机在读取到文件类型变化时会触发一个“斜眼看一下”的小动作模拟真实宠物对人的关注。连续编码时间统计也放在这个模块里当超过45分钟没有切换窗口并且前台窗口始终是编辑器时宠物就会触发休息提醒。获取窗口标题有个坑后面踩坑部分会详细说这里先提个醒很多编辑器的窗口标题格式各不相同有的带文件名有的带项目路径有的带Git分支名解析正则要灵活匹配不要写死。3.4 气泡、语音与托盘交互气泡对话框是AI输出和用户见面的主要界面。我用一个无边框半透明QLabel实现定位在宠物窗口上方。气泡文本支持自动换行最大宽度控制在300像素避免长代码把气泡撑得满屏都是。短对话用QTimer设置5秒自动消失长对话滚动显示。托盘菜单的重要性一开始被我低估了。没有托盘菜单的时候想退出程序只能去任务管理器杀进程非常不优雅。后来加上QSystemTrayIcon右键菜单里放了“显示/隐藏宠物”“切换AI模型”“重置对话”“退出”四个选项。托盘菜单不仅是控制入口也是调试入口特别是切换模型这个功能能让你不用改配置文件、不用重启程序就完成云端和本地模型之间的切换实测对调参效率提升很大。语音交互我放在第二个迭代版本里。第一版先用快捷键CtrlAltC唤醒对话输入框输入问题后回车发送这样最稳。语音接入用了开源的语音识别库识别效果取决于麦克风质量和环境音属于锦上添花的功能不建议作为核心依赖。我实际用下来打字交互的成功率远高于语音交互尤其你还在敲代码的时候顺手就把问题打了语音反而显得多此一举。4. 实操过程从克隆到跑起来4.1 环境准备与依赖安装CodeWalkers依赖Python 3.9以上版本。个人项目建议用虚拟环境隔离依赖否则很容易和系统Python包冲突。python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install PyQt5 requests pyyaml psutil如果要用本地模型还需要单独安装Ollama相关依赖或者直接通过HTTP调用Ollama的API接口。我目前是直接用requests调用Ollama的/api/chat接口不装额外的Python库这样少一个依赖出错时排查也更简单。Windows平台还需要装pywin32来获取前台窗口信息macOS需要pyobjcLinux需要确保系统里有xdotool命令。这些平台相关的依赖项我都写在了requirements.txt的条件标记里避免在非目标平台安装时报错。4.2 配置文件与API Key管理配置文件我用YAML因为可读性好加注释方便。核心配置项包括AI服务商、模型名、API密钥、代理设置、宠物大小、动画速度等。ai: cloud: enabled: true base_url: https://api.example.com/v1 api_key: sk-xxxxxxxx model: your-model-name stream: true local: enabled: true base_url: http://127.0.0.1:11434/v1 api_key: ollama model: qwen2.5-coder:7b stream: false prefer: local pet: size: 256 fps: 12 resting_reminder_minutes: 45这里要强调一个安全习惯不要把真实的API Key硬编码在代码里。我自己的做法是API Key从环境变量CODEWALKERS_API_KEY读取如果环境变量不存在才回退到YAML里的占位值。这样即使你把项目打包发到GitHub也不会泄露密钥。云端API的域名和密钥都换成自己的真实配置本地模型地址默认就是127.0.0.1:11434基本不用改。4.3 启动与运行第一次见到宠物启动流程很简单python main.py程序启动后大概1秒左右桌面右下方会出现一个256x256的透明宠物窗口默认播放待机动画。如果AI模块配置正确右击托盘图标在菜单里选择“开启对话”再用CtrlAltC唤醒输入框发一条“你好”宠物就会弹出气泡回复。第一次启动最容易出的问题是透明窗口默认在屏幕边缘鼠标拖拽功能如果没实现宠物会卡在一个位置。我建议第一版先实现拖拽功能用鼠标按下记录偏移量移动时更新窗口位置十几行代码就能搞定。否则后面每调一次位置都得改代码会非常烦躁。启动后可以用系统的任务管理器或活动监视器观察一下CPU占用。正常待机状态CPU占用低于5%是合格的如果达到10%以上说明动画帧率设置过高或者事件循环里有阻塞操作需要检查代码中是否有长时间运行的同步调用。5. 踩坑记录与问题排查5.1 窗口透明与点击穿透问题透明窗口最大的坑是某些系统环境下透明背景变成纯黑。这个问题在Windows上出现过一次后来发现原因是没设置Qt.WA_TranslucentBackground之前就调用了setStyleSheet设置了背景色。解决办法是设置完窗口属性后再设置样式顺序不能反。另外如果你的代码里用了setAutoFillBackground(True)也会强制填充背景色破坏透明效果这个开关务必关掉。点击穿透是另一个让人头大的问题。桌面宠物默认会拦截它所在矩形区域内的所有鼠标事件如果正好悬浮在代码编辑器上方你想选中的代码会被宠物窗口挡住。解决思路是根据宠物的形态动态设置可点击区域# 根据当前帧的Alpha通道生成区域掩码 mask QBitmap.fromImage(currentFrame.createAlphaMask()) self.setMask(mask)这样只有宠物实际像素所在的区域能响应鼠标透明部分点击会穿透到下层窗口。这个方案实测非常有效但注意频繁设置遮罩会带来一定性能开销建议只在帧切换时同步更新而不是每帧都设置。5.2 AI响应卡顿与超时处理AI超时是使用率最高的问题。云端API偶尔会慢本地模型遇到长上下文会更慢。如果直接用requests.post同步等待界面会卡住无论动画还是拖拽都失去响应。后来我把AI请求全部放到了单独的QThread线程里请求完成后通过信号回传UI线程更新气泡这个问题才得到解决。超时时间设置也需要讲究。云端模型我设了30秒本地模型设了60秒。超时返回后宠物会弹出一个特定表情和提示文案“这个问题我还没想好你可以再问一次或换一种问法”避免用户以为程序死掉了。另外本地模型如果没启动或者显存不足会在请求时立即报连接错误这个一定要用try-except捕获住并降级到云端模型否则程序会在切换瞬间崩溃。5.3 占用内存高的优化方案第一版内存占用冲到了600MB排查后发现大头是帧动画的QPixmap对象。一套256x256的PNG序列帧100张图全部加载到内存每张解码后是256*256*4字节约250KB100张就是25MB这还不算Qt内部的像素缓冲。但600MB绝对不正常真正的问题是每次加载帧都解码出新的QPixmap旧的没有被释放造成内存碎片累积。优化方案是做一个帧缓存池只在首次使用时解码图片之后从缓存取。同时把动画帧分为常驻帧和临时帧待机动画常驻内存说话动画每次播放前加载、播放完释放。经过这两步优化最终内存稳定在180MB左右对于一个带AI能力和动画的桌面应用来说这个数字可以接受。代码里还加了一个定时清理的机制每隔10分钟清理一次长期未使用的缓存帧保证宠物连续跑上一整天内存也不会缓慢爬升。这个小细节很适合桌面常驻类程序强烈建议加上。5.4 Prompt被带跑偏与主动提醒策略桌面宠物和用户之间应该是轻量互动的关系但模型有时候会忘掉自己的人设。比如用户问了一个复杂的数据库设计问题模型开始长篇大论输出5000字方案完全不像一个宠物在说话。后来我在Prompt里加了长度约束并且在应用层再做一次强制截断气泡最长显示300字超出部分用“...展开查看”折叠只有用户点击后才显示全文。这既保住了模型输出的信息量又不会破坏宠物轻快的交互节奏。主动提醒策略也需要控制频次。刚开始休息提醒每45分钟一次没问题但AI偶尔会根据窗口标题的切换频率判断用户在摸鱼然后说一些“你好像有点走神了”之类的话日子久了有点烦。我后来加了一个条件只有连续工作时间超过45分钟且当前窗口是编辑器时才提醒休息其他情况下AI不能主动发消息只能被动回答。这个改动之后用户对宠物的好感度和容忍度都提升了。6. 想让它更聪明这些扩展方向值得试6.1 让宠物读懂git commit信息现在CodeWalkers已经能感知前台文件类型但更智能的形态是感知当前仓库的状态。我们可以监听当前工作目录是否是Git仓库读取最新一次commit的信息再有新commit产生时宠物可以做个庆祝动作。实现思路是轮询目录下的.git/HEAD和.git/refs/heads文件变化不需要执行git命令轮询成本很低。这样宠物就从一个旁观者变成了一个能感知你开发进度的伙伴那种“我提交了代码它为我开心”的体验非常奇妙。6.2 语音唤醒与实时情绪反馈语音交互的完整方案可以做两件事纯离线语音唤醒和情绪识别。离线唤醒用开源的唤醒词引擎把关键词语音特征本地化不用把语音传上云隐私风险低。情绪识别可以分析麦克风采集到的语音能量和语速检测到用户叹气或者长时间没有说话时宠物自动播放一些安慰动作或者弹出气泡说“要不要休息一下换换脑子”。这个方向比较进阶但做成功之后的归属感很强。6.3 和编译错误联动这是我接下来准备做的功能。CodeWalkers可以读取编辑器中当前终端窗口的文本内容通过正则匹配出报错行。当捕获到一条编译错误时宠物不再是傻乎乎地继续卖萌而是切换到一个“观察”状态弹出气泡建议你让AI分析这段报错。更进一步的话可以直接把报错信息发送给AI让它给出修改建议。这样桌面宠物就从纯陪伴工具升级成了开发工作流里的一个节点实用价值也上了一个台阶。CodeWalkers从最初的一堆零散脚本到现在稳定在桌面上运行的完整项目中间支撑我继续完善的核心动力其实是“反馈感”。每次写完一个模块宠物就在那里给你一个反应这种即时正反馈比写普通命令行工具要强太多了。如果你也想做一个自己的桌面小伙伴我最大的建议是不要追求功能和AI能力的一步到位先让一个简单的像素小宠物在你桌面上顺利待三天不崩再逐步叠加场景感知、AI对话这些能力。等技术链路跑通了你会发现这个陪伴型AI助手的扩展空间比想象中大得多。
返回列表