基于LLM Agent与Godot引擎的智能桌面宠物开发实践
1. 项目概述:一个会思考、有记忆的智能桌宠
最近在捣鼓一个挺有意思的玩意儿,我把它叫做Agentic-Desktop-Pet。简单来说,这是一个运行在你电脑桌面上的“电子宠物”,但它和你小时候玩的拓麻歌子或者电子鸡可完全不是一个概念。这家伙的“内核”是一个由大语言模型驱动的智能体,它不仅能和你聊天,帮你处理文件、写代码、管理任务,更重要的是,它拥有记忆、情感和一套类似角色扮演游戏的成长系统。
想象一下,你桌面上有个小角色,它记得你们昨天聊过什么,会根据你的互动变得开心或无聊,甚至还能通过“对话”和“完成任务”来升级自己的“智力”或“魅力”属性。这听起来是不是有点像把《Her》电影里的萨曼莎或者《钢铁侠》里的贾维斯,做成了一个可以放在桌面角落、随时互动的卡通伙伴?这正是这个项目想尝试的方向:将前沿的LLM Agent能力与轻量、有趣的桌面应用形态结合,创造一个更具人格化和实用性的个人AI伴侣。
这个项目的核心价值在于,它试图突破当前大多数AI助手“一问一答、过目即忘”的交互模式。通过集成Cognee记忆系统,它能将对话内容组织成语义知识图谱,实现长期记忆和关联检索。这意味着你和它的每一次交流,都在共同构建一个专属于你们的记忆库。同时,其动态情感系统和RPG属性系统,让AI的回应不再是千篇一律的模板,而是会随着“心情”和“能力成长”发生变化,极大地增强了交互的沉浸感和趣味性。对于开发者、学生或者任何需要一位“数字伙伴”来协助日常工作学习的人来说,这不仅仅是一个工具,更是一个能伴随你成长、有“温度”的桌面助手。
2. 核心架构与设计思路拆解
要理解这个桌宠是如何工作的,我们需要深入到它的技术架构里看看。整个项目采用了经典的前后端分离设计,但前后端的技术选型都颇具特色,这也是项目能实现复杂功能的基础。
2.1 后端:基于FastAPI的智能体引擎
后端是整个桌宠的“大脑”,负责所有的逻辑处理、记忆存储和与LLM的交互。它使用Python和FastAPI框架构建,这是一个高性能的现代Web框架,非常适合构建这种需要处理异步请求的API服务。
为什么选择FastAPI?首先,FastAPI天生支持异步(async/await),这对于需要频繁调用外部LLM API(可能有网络延迟)的场景至关重要,能保证服务的高响应性。其次,它自动生成交互式API文档(Swagger UI),极大方便了前后端联调和后续的功能扩展。最后,它的性能在Python Web框架中属于第一梯队,能轻松应对桌面应用这种轻量但实时的请求。
后端代码的核心模块组织得非常清晰:
learn_agent/: 这是智能体的核心实现区,借鉴了类似Claude Code的设计思路。agent/: 定义了主Agent类,它是所有功能的调度中心。memory/: 这里集成了项目的灵魂——Cognee记忆系统。cognee_manager.py负责与Cognee服务交互,处理记忆的存储、检索和丰富化。emotion/: 情感引擎所在。emotion_engine.py里定义了一套规则(或轻量模型)来计算和更新桌宠的情感状态,比如“开心值”、“无聊值”如何随时间衰减,又如何因用户互动而增强。rpg/: 角色属性系统。character.py里定义了智力、魅力等属性,以及经验值、等级、技能和好感度的增长逻辑。llm/: 抽象了与不同大模型(OpenAI GPT, Anthropic Claude, DeepSeek等)的通信接口,方便切换模型提供商。tool/: 这是Agent的“手”和“脚”。包含了file_tool.py(文件读写)、code_tool.py(执行代码)、todo_tool.py(管理待办事项)等具体工具。Agent在收到用户指令后,会规划是否需要以及如何使用这些工具。
注意:工具的执行涉及到本地系统的文件操作和命令执行,这是安全的重中之重。在实现时,必须进行严格的权限控制和输入净化,例如限制可访问的目录范围,禁止执行危险系统命令。一个好的实践是采用“沙箱”或“许可列表”机制。
2.2 前端:Godot引擎驱动的灵动窗口
前端,也就是你在桌面上看到的那个卡通形象和交互界面,是用Godot 4.3游戏引擎开发的。这可能让很多习惯了Electron、Qt或Tkinter的开发者感到意外。
为什么选择Godot而不是传统桌面框架?
- 极致轻量与高性能:Godot编译出的可执行文件非常小,运行时资源占用远低于Electron等基于Chromium的方案。对于一个需要常驻后台、实时渲染动画的桌宠来说,这点至关重要。
- 强大的2D渲染与动画系统:Godot专为游戏设计,其场景(Scene)、节点(Node)系统和动画播放器(AnimationPlayer)使得实现一个具有丰富表情、动作和状态切换的卡通角色变得异常简单和高效。你可以轻松地让桌宠在“开心”时跳跃,在“无聊”时打哈欠。
- 跨平台一键导出:Godot支持将项目一键导出到Windows、macOS、Linux,甚至Web,极大地简化了跨平台部署的复杂度。
- 透明窗口与点击穿透:通过社区插件(如项目用到的
godot-click-through-transparent-window),可以轻松实现不规则形状的透明窗口,并且让鼠标事件可以穿透桌宠的非交互区域,这样它就不会干扰你正常操作其他窗口,真正成为一个“背景式”的伴侣。
前端的核心职责是:
- 渲染与动画:根据后端传来的情感状态(如“兴奋”),播放对应的精灵动画。
- 用户交互:接收鼠标点击、拖拽(移动桌宠位置)、右键菜单(打开对话输入框)等操作。
- 通信:通过HTTP或WebSocket与后端FastAPI服务进行实时通信,发送用户指令并接收Agent的回复和状态更新。
这种“游戏引擎做UI,专业后端处理逻辑”的架构,兼顾了表现力的丰富性和业务逻辑的复杂性,是一个非常巧妙且实用的组合。
2.3 数据流与核心交互流程
当用户与桌宠互动时,一次完整的请求流程是这样的:
- 用户输入:你在Godot前端弹出的对话框中输入“帮我看看
project.py里第三行的内容”。 - 前端发送:Godot前端将这个文本指令,连同当前的会话上下文(可能包含之前的对话ID),通过HTTP POST请求发送到后端的某个API端点(例如
/api/chat)。 - Agent处理:后端
main.py接收到请求,路由到主Agent(agent.py)。 - 记忆检索:Agent首先调用
memory.py中的记忆管理器,使用Cognee对当前指令进行语义搜索,查找相关的历史对话或知识片段。例如,它可能找到你昨天说过“project.py是我正在写的一个爬虫”。 - 情感与状态注入:Agent从
emotion_engine.py和character.py中获取当前桌宠的情感状态和角色属性。比如,如果当前“无聊值”很高,它可能会在思考过程中加入“想找点乐子”的倾向。 - LLM推理与规划:Agent将用户指令 + 相关记忆 + 当前情感/属性状态组合成一个精心设计的提示词(Prompt),发送给配置的LLM(如GPT-4)。LLM会分析指令,判断是否需要调用工具,并生成一个行动计划。例如:“用户想查看文件内容。我需要使用
file_tool.read_file工具,参数是project.py。” - 工具执行:Agent根据LLM的规划,调用
tool/file_tool.py中的read_file函数,安全地读取指定文件内容。 - 生成回复:Agent将工具执行的结果(文件内容)再次结合上下文,发送给LLM,让其生成一段拟人化的、符合当前情感状态的回复。例如:“主人,我看了你的
project.py,第三行是import requests。看起来你在准备发起网络请求呢!需要我帮你写后面的部分吗?(●'◡'●)” - 更新状态:根据此次交互的内容和结果,情感引擎和RPG系统会更新状态。比如,成功帮助用户可能增加“开心值”和少量“经验值”。
- 返回响应:后端将最终的回复文本、以及更新后的情感值、属性值等数据,打包成JSON返回给Godot前端。
- 前端呈现:Godot前端解析响应,在对话框显示回复文字,同时根据新的情感状态,切换桌宠的动画为“开心地摇晃”。
这个流程清晰地展示了记忆、情感、工具能力是如何有机融合在一次智能交互中的。
3. 核心模块深度解析与实现要点
了解了整体架构,我们来深入看看几个最核心的模块是如何实现的,以及在开发中会遇到哪些坑。
3.1 记忆系统:从对话到知识图谱
记忆是让AI产生“连续性”和“个性”的关键。本项目没有使用简单的对话历史列表,而是引入了Cognee这套专门为AI Agent设计的记忆系统。
Cognee的核心工作流程:
- 记忆存储:每次有意义的对话结束后,Agent会将对话文本发送给Cognee。
- 信息提取与图谱构建:Cognee会利用其内部的NLP模型,自动从文本中提取实体(如“Python”、“用户”、“牛奶”)、关系(如“用户喜欢Python”、“待办事项是买牛奶”)和关键概念。
- 图谱存储与向量化:这些实体和关系被存储在一个图数据库中,形成一张语义网络。同时,这段记忆的文本摘要会被编码成向量,存入向量数据库。
- 混合检索:当新的用户查询到来时,Cognee会同时进行两种搜索:
- 向量搜索:计算查询语句的向量,在向量数据库中寻找语义最相似的过往记忆片段。这擅长处理“意思相近但表述不同”的查询。
- 图谱搜索:在知识图谱中,沿着实体和关系的路径进行遍历搜索。这擅长处理涉及多跳推理的查询,比如“我之前提过的那个关于Python的项目怎么样了?”(需要关联“用户”-“提及”-“项目”-“语言”-“Python”)。
实操要点与避坑指南:
- 记忆的粒度:不是每一句对话都值得记忆。通常,只存储包含实质性信息、任务或重要情感的对话轮次。可以在Agent逻辑里设置过滤条件,比如当LLM判断该轮对话“信息密度高”或“包含任务指令”时,才触发记忆存储。
- 记忆的关联:在存储记忆时,手动或自动地为其打上标签(如
#工作、#生活、#编程问题),并关联到当前会话ID或用户ID,这能极大提升后续检索的准确性。 - 隐私与成本:所有对话内容都会发送到Cognee服务(可能是本地部署或云端)。务必清楚其隐私政策。如果使用云端版,频繁的存储和检索可能会产生API调用费用。对于个人项目,可以考虑简化版,用本地向量数据库(如ChromaDB)搭配简单的关键词提取来实现基础记忆。
- 记忆的“遗忘”:一个永不遗忘的AI可能也是可怕的。可以考虑为记忆设置“衰减因子”或“生命周期”。例如,很久未被触及的记忆,其检索优先级自动降低,或者定期清理过于久远且不重要的记忆。
3.2 情感与RPG系统:赋予AI“性格”
这是让桌宠变得有趣和拟人化的核心。情感系统和RPG系统通常是紧密耦合的。
情感引擎的实现:情感可以建模为一组数值变量,例如:
happiness(开心): 0-100sadness(悲伤): 0-100excitement(兴奋): 0-100boredom(无聊): 0-100
状态更新规则示例:
- 时间衰减:每过一分钟,所有情感值向中性值(如50)回归一小步。这模拟了情感的自然平复。
# 伪代码 def decay_emotions(current_emotions): for emotion, value in current_emotions.items(): neutral = 50 decay_rate = 0.1 # 每分钟衰减10%的差值 new_value = value + (neutral - value) * decay_rate current_emotions[emotion] = clamp(new_value, 0, 100) - 互动影响:当用户与桌宠进行正向互动(如聊天、完成任务),增加
happiness和excitement,减少boredom。如果用户长时间不理会,则boredom持续增加。 - 事件影响:工具执行成功或失败,也会影响情感。例如,成功运行一段代码可能大幅增加
excitement,而读取一个不存在的文件可能导致短暂的sadness增加。
RPG属性系统的设计:属性系统更偏向于长期、稳定的“能力”成长。
- 基础属性:
intelligence(智力,影响代码生成质量)、charisma(魅力,影响回复的友好度)、dexterity(敏捷,影响多任务处理速度?这里可以创意设计)、stamina(体力,影响连续工作时长)。 - 经验与升级:每次完成一个用户任务(如成功编辑文件、解答一个问题),获得经验值。经验值累积到一定数值后升级,升级后可以获得属性点,由用户或系统自动分配到基础属性上。
- 技能系统:当某个属性达到一定阈值,解锁特定技能。例如,
intelligence > 30解锁“代码调试建议”技能;charisma > 50解锁“讲笑话”技能。技能可以表现为Agent可用的新工具,或者在回复中展现的新能力。 - 好感度:这是一个单独的系统,衡量用户与桌宠的长期关系。基于互动频率、正向反馈等计算。高好感度可能解锁特殊对话、外观皮肤或者更积极的帮助意愿。
如何影响LLM行为?情感和属性本身不直接修改LLM的权重,而是通过系统提示词(System Prompt)来影响。在每次调用LLM的提示词中,动态插入当前状态:
你是一个桌面宠物助手。你当前的心情是:[开心,兴奋]。你的智力属性为45(中等偏上)。请用符合你当前心情和智力水平的语气和方式回应用户。如果你感到兴奋,可以使用更多感叹号和积极词汇。你的智力水平意味着你能处理中等复杂的编程问题。 用户说:{{用户输入}}通过这种方式,LLM生成的回复就会带上相应的情感色彩和能力倾向。
3.3 工具系统:Agent的“手”与安全边界
工具是Agent与真实世界交互的桥梁。本项目的工具集模仿了Claude Code,主要围绕本地操作展开。
核心工具实现解析:以file_tool.py中的读取文件工具为例:
import os from pathlib import Path from typing import Optional # 假设有一个安全工具类,用于路径检查和净化 from .security import SafePath class FileTool: def __init__(self, allowed_base_dirs: list[str]): # 定义允许访问的基准目录,例如用户的家目录、项目目录 self.allowed_base_dirs = [Path(d).resolve() for d in allowed_base_dirs] def read_file(self, file_path: str, line_range: Optional[tuple[int, int]] = None) -> dict: """ 安全地读取文件内容。 Args: file_path: 用户提供的文件路径(可能是相对或绝对路径)。 line_range: 可选,指定读取的行范围 (start, end),从1开始计数。 Returns: 包含状态、内容和错误信息的字典。 """ try: # 1. 路径安全解析与校验 safe_path = SafePath.resolve(file_path, self.allowed_base_dirs) if not safe_path.is_allowed: return {"status": "error", "message": "访问路径不在许可范围内。"} target_path = safe_path.resolved_path # 2. 检查文件是否存在且为文件 if not target_path.is_file(): return {"status": "error", "message": f"路径 '{target_path}' 不是一个文件或不存在。"} # 3. 读取文件内容 with open(target_path, 'r', encoding='utf-8') as f: content = f.read() # 4. 处理行范围请求 if line_range: lines = content.splitlines() start, end = line_range # 处理负数和越界 start = max(1, start) - 1 end = min(len(lines), end) if end > 0 else len(lines) selected_lines = lines[start:end] content = '\n'.join(selected_lines) return { "status": "success", "content": content, "file_path": str(target_path), "line_count": len(content.splitlines()) if not line_range else (end - start) } except PermissionError: return {"status": "error", "message": "没有权限读取该文件。"} except Exception as e: return {"status": "error", "message": f"读取文件时发生未知错误: {str(e)}"}关键安全考量:
- 路径遍历攻击防护:用户输入的
file_path可能是../../../etc/passwd。必须将用户提供的路径解析为绝对路径,并严格检查其是否在以allowed_base_dirs为根目录的子树下。SafePath类就是干这个的。 - 操作范围限制:绝对禁止工具直接执行
rm -rf /或format C:之类的危险系统命令。对于code_tool.py执行代码,强烈建议在沙箱环境(如Docker容器、安全子进程)中运行,并限制资源(CPU、内存、运行时间)。 - 用户确认:对于高风险操作(如删除文件、覆盖写入、安装包),工具应返回一个“需要确认”的状态,由前端弹出对话框让用户二次确认后,再执行实际操作。
- 工具暴露范围:不是所有系统能力都需要暴露给Agent。仔细评估每个工具的必要性和风险,遵循最小权限原则。
4. 从零开始的完整部署与实操指南
理论说了这么多,我们动手把它跑起来。这里我会提供一个从环境准备到前后端联调的详细步骤,并附上我踩过坑的解决方案。
4.1 后端环境搭建与配置
步骤1:安装Python与uv项目推荐使用Python 3.13+和uv包管理器。uv是新兴的、速度极快的Python包管理器和项目运行器,可以替代pip和virtualenv。
- 从Python官网安装Python 3.13或更高版本。
- 安装
uv:pip install uv或者参照其官方文档。
步骤2:克隆项目与依赖安装
# 克隆项目代码 git clone https://github.com/jihe520/Agentic-Desktop-Pet.git cd Agentic-Desktop-Pet/backend # 使用uv同步项目依赖(uv会读取pyproject.toml) uv syncuv sync命令会根据pyproject.toml文件自动创建虚拟环境并安装所有依赖。这比手动pip install -r requirements.txt更规范、更快。
步骤3:配置环境变量与API密钥后端服务需要连接LLM API(如OpenAI)和可能的Cognee服务。
# 复制环境变量示例文件 cp .env.example .env # 使用文本编辑器打开 .env 文件进行配置你需要编辑.env文件,填入必要的密钥。一个典型的.env文件内容如下:
# LLM 配置 (以OpenAI为例) OPENAI_API_KEY=sk-your-openai-api-key-here OPENAI_BASE_URL=https://api.openai.com/v1 # 如果使用官方API则无需修改 LLM_MODEL=gpt-4o-mini # 或 gpt-4-turbo, 根据自己情况选择 # Cognee 记忆系统配置 (如果使用云端版) COGNEE_API_KEY=your-cognee-api-key COGNEE_API_BASE=https://api.cognee.ai/v1 # 后端服务配置 BACKEND_HOST=127.0.0.1 BACKEND_PORT=8000 ALLOWED_ORIGINS=http://localhost:8080 # Godot前端运行的地址 # 工具安全配置 ALLOWED_BASE_DIRS=/Users/你的用户名/Desktop,/Users/你的用户名/Documents,/path/to/your/projects CODE_EXECUTION_TIMEOUT=30 # 代码执行超时时间(秒)重要提示:
ALLOWED_BASE_DIRS是你允许Agent访问的文件系统根目录,务必仔细设置,不要包含系统关键目录。多个目录用逗号分隔。
步骤4:启动后端服务
# 在backend目录下,使用uv运行主程序 uv run main.py如果一切正常,你会在终端看到类似输出:
INFO: Started server process [12345] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)此时,你可以打开浏览器访问http://127.0.0.1:8000/docs,应该能看到自动生成的FastAPI交互式文档,这证明后端服务已经成功运行。
4.2 前端Godot项目配置与运行
步骤1:安装Godot引擎从Godot官网下载并安装Godot 4.3或更高版本。注意是4.x版本,3.x版本不兼容。
步骤2:导入并打开项目
- 打开Godot引擎。
- 点击“导入”按钮,选择
Agentic-Desktop-Pet/godot文件夹下的project.godot文件。 - 项目导入后,在Godot编辑器的“文件系统”面板中,你应该能看到
scenes/、scripts/等目录。
步骤3:配置前端连接后端前端需要知道后端API的地址。这个配置通常放在一个GDScript的配置文件中或主场景的脚本里。
- 找到并打开主场景(可能是
scenes/Main.tscn)或初始化脚本。 - 查找类似
var backend_url = "http://localhost:8000"的变量,确保其值与你的后端服务地址和端口一致。
步骤4:处理主题文件(关键步骤!)根据项目README的警告,主题文件(.pck资源包)需要被正确放置。
- 在项目文件系统中,找到
themes/文件夹,里面应该有一些.pck文件(如dino_theme.pck)。 - 不要直接在Godot编辑器中运行。你需要先导出项目。
- 在Godot编辑器顶部菜单栏,选择
项目->导出...。 - 添加一个导出预设(例如“Windows Desktop”),配置好导出路径。
- 点击“导出项目”,会生成一个
.exe(Windows)或可执行文件。 - 将
themes/整个文件夹(包含里面的.pck文件)复制到你导出的.exe文件所在的同级目录下。最终目录结构应如下:你的导出目录/ ├── Agentic-Desktop-Pet.exe (导出的可执行文件) └── themes/ (你复制过来的文件夹) ├── tech_dungeon.pck ├── tiny_swords.pck └── dino_characters.pck - 运行导出的
.exe文件,桌宠应该就能正常显示并使用主题了。
步骤5:在编辑器中调试(可选)如果你不想每次都导出,也可以在编辑器中直接运行测试,但可能需要修改代码来加载本地res://themes/路径下的资源,而不是运行时从themes/文件夹加载。这需要查看项目具体的资源加载代码是如何写的。
4.3 基础功能测试与交互
当后端和前端都成功运行后,你就可以开始和你的桌宠互动了。
- 启动交互:在Godot前端窗口上,点击鼠标右键,应该会弹出一个对话框或输入框。
- 基础聊天:尝试输入“你好”,看看它如何回应。观察回复是否自然,是否符合预期。
- 测试文件操作:
- 输入:“帮我读取
C:\Users\YourName\Desktop\test.txt”(请替换为ALLOWED_BASE_DIRS中存在的真实文件路径)。它应该返回文件内容。 - 输入:“在桌面上创建一个叫
hello_world.py的文件,内容写print(\"Hello from my pet!\")”。检查桌面是否生成了该文件。
- 输入:“帮我读取
- 测试任务管理:
- 输入:“添加一个待办:下午三点开会”。
- 输入:“列出我所有的待办事项”。看看它是否能正确管理任务列表(任务数据通常存储在后端的一个简单数据库或JSON文件里)。
- 查询状态:
- 输入:“你现在心情如何?”或“显示你的属性”。前端应该会更新显示当前的情感数值和RPG属性。
如果以上测试都通过了,恭喜你,一个具备基础能力的智能桌宠已经在你的桌面上“活”过来了!
5. 开发进阶:打造你自己的专属桌宠
项目提供了强大的Mod系统,意味着你可以轻松地更换桌宠的外观、动画,甚至定义新的行为。这是该项目最有趣的部分之一。
5.1 理解Godot前端架构
要制作Mod,你需要对Godot前端项目结构有个基本了解:
scenes/:存放场景文件(.tscn)。主场景定义了桌宠的根节点,包含精灵(Sprite,显示图片)、动画播放器(AnimationPlayer)、碰撞区域(Area2D,用于点击检测)等。scripts/:存放GDScript脚本(.gd)。这些脚本控制着逻辑,比如处理用户输入、与后端通信、更新精灵动画等。themes/:这就是Mod的核心目录。每个主题都是一个.pck文件,它本质上是一个Godot引擎可以动态加载的资源包。
5.2 创建一个简单的自定义主题
步骤1:准备素材你需要一套角色精灵图(Sprite Sheet)和对应的动画。可以从itch.io等网站寻找免费的像素艺术素材,或者自己绘制。确保素材是透明背景的PNG序列帧。
步骤2:在Godot中创建新项目(用于制作主题包)
- 新建一个Godot项目。
- 将你的精灵图导入项目。在“文件系统”面板中创建合理的目录,如
sprites/。 - 创建场景:创建一个
Character场景,添加一个Sprite2D节点,将纹理设置为你的一帧精灵图。 - 创建动画:添加一个
AnimationPlayer节点。选中它,在动画编辑器中创建新动画(如idle、happy、sad)。通过在不同时间点改变Sprite2D的frame属性(如果是SpriteFrames)或切换纹理,来制作帧动画。 - 保存场景为
character.tscn。
步骤3:编写主题配置文件通常,主题包需要一个配置文件来告诉主程序如何加载和使用它。这个配置文件可能是一个JSON或GDScript文件。你需要参考原项目的Wiki或现有主题的结构。假设它需要一个theme_config.gd文件:
# theme_config.gd extends Resource class_name ThemeConfig @export var theme_name: String = "My Custom Pet" @export var description: String = "A cute pet I made myself." @export var author: String = "YourName" # 指向你的角色场景 @export var character_scene: PackedScene = preload("res://character.tscn") # 定义情感状态到动画名的映射 @export var emotion_to_animation: Dictionary = { "happy": "happy_jump", "sad": "sad_cry", "idle": "idle_blink", # ... 其他情感 }步骤4:打包为.pck文件在Godot编辑器中,选择项目->工具->导出PCK/ZIP...。在对话框中:
- 确保“模式”选择“PCK”。
- 在“资源”标签页,你可以选择要包含的文件。通常全选你的主题项目文件即可。
- 点击“导出PCK”,将其命名为
my_custom_theme.pck。
步骤5:使用你的主题将导出的my_custom_theme.pck文件,放入你已部署的桌宠程序的themes/文件夹内。重启桌宠程序,如果程序支持动态切换主题,你应该能在设置或菜单中找到并切换到你的新主题。
5.3 扩展后端功能:添加一个新工具
假设你想让桌宠具备“查询天气”的能力。
步骤1:创建新工具文件在backend/learn_agent/tool/目录下,新建一个weather_tool.py。
# weather_tool.py import requests from typing import Dict, Any class WeatherTool: def __init__(self, api_key: str): self.api_key = api_key self.base_url = "https://api.weatherapi.com/v1" # 示例,使用免费天气API def get_current_weather(self, city: str) -> Dict[str, Any]: """获取指定城市的当前天气""" try: # 构建请求URL,这里以WeatherAPI为例 url = f"{self.base_url}/current.json" params = { 'key': self.api_key, 'q': city, 'aqi': 'no' } response = requests.get(url, params=params, timeout=10) response.raise_for_status() # 检查HTTP错误 data = response.json() # 解析并格式化返回数据 location = data['location']['name'] temp_c = data['current']['temp_c'] condition = data['current']['condition']['text'] return { "status": "success", "location": location, "temperature_c": temp_c, "condition": condition, "full_data": data # 可选,返回完整数据供LLM发挥 } except requests.exceptions.RequestException as e: return {"status": "error", "message": f"网络请求失败: {str(e)}"} except KeyError as e: return {"status": "error", "message": f"解析天气数据失败,缺少键: {str(e)}"}步骤2:在主Agent中注册新工具打开backend/learn_agent/agent/agent.py,找到工具初始化的地方(可能在__init__方法或一个_setup_tools方法)。
# 在agent.py中 from .tool.weather_tool import WeatherTool class DesktopPetAgent: def __init__(self, ...): # ... 其他初始化 ... self.weather_tool = WeatherTool(api_key=os.getenv("WEATHER_API_KEY")) self.tools = { "read_file": self.file_tool.read_file, "write_file": self.file_tool.write_file, "execute_code": self.code_tool.execute, "get_weather": self.weather_tool.get_current_weather, # 注册新工具 # ... 其他工具 }同时,记得在.env文件中添加你的天气API密钥:WEATHER_API_KEY=your_weather_api_key。
步骤3:更新工具描述供LLM理解LLM需要知道这个工具是干什么的、怎么用。这通常通过一个工具描述列表来实现,这个列表会被放入给LLM的提示词中。
# 在agent.py中,可能有一个_get_tool_descriptions方法 def _get_tool_descriptions(self): tool_descriptions = [ { "name": "get_weather", "description": "获取指定城市的当前天气信息。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,例如 '北京' 或 'New York'。" } }, "required": ["city"] } }, # ... 其他工具描述 ] return tool_descriptions现在,当你问桌宠“今天北京天气怎么样?”,LLM就会规划调用get_weather工具,并传入city=“北京”,然后将工具返回的天气数据组织成一段友好的回复告诉你。
6. 常见问题排查与性能优化实录
在实际部署和开发过程中,你几乎一定会遇到下面这些问题。这里是我总结的“避坑指南”。
6.1 连接与通信问题
问题1:Godot前端报错“无法连接到后端”或“网络错误”。
- 检查后端是否运行:确认
uv run main.py正在运行,且没有报错退出。终端应显示运行在http://127.0.0.1:8000。 - 检查端口与地址:确认Godot前端脚本中配置的
backend_url(如http://localhost:8000)与后端服务地址完全一致。localhost和127.0.0.1在大多数情况下等价,但某些配置下可能有区别,可以都试试。 - 检查CORS设置:浏览器和Godot(通过HTTPClient)都会受到CORS策略限制。确保后端FastAPI应用配置了正确的CORS中间件,允许前端的Origin。在
main.py中应该有类似如下代码:from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:8080"], # 你的Godot编辑器或导出后运行的地址 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) - 防火墙拦截:临时关闭Windows/macOS的防火墙,或添加规则允许Python和Godot可执行文件通过防火墙。
问题2:后端服务启动失败,提示模块导入错误或依赖问题。
- 确保使用uv虚拟环境:在
backend目录下执行uv sync后,确保你是在该虚拟环境下运行uv run main.py。你可以通过uv run python -c "import sys; print(sys.executable)"来检查当前Python解释器路径是否在.venv目录下。 - 检查Python版本:运行
python --version确认是3.13+。如果不是,可能需要使用uv run python3.13 main.py来指定解释器。 - 手动安装缺失包:如果
uv sync后仍有特定包找不到,尝试用uv add package_name手动安装。
6.2 功能异常问题
问题3:文件操作工具返回“访问路径不在许可范围内”。
- 检查
.env配置:确认ALLOWED_BASE_DIRS配置的路径存在,并且格式正确(绝对路径,用逗号分隔,无多余空格)。 - 路径格式:在Windows上,路径应为
C:\Users\Name\Documents格式,但在.env文件中,反斜杠可能需要转义或使用正斜杠,如C:/Users/Name/Documents。最稳妥的方式是在代码中打印出解析后的allowed_base_dirs列表,看是否与预期一致。 - 用户输入路径处理:用户输入的路径可能是相对路径(如
./test.txt)。你的SafePath.resolve函数需要能正确地将相对路径基于某个基准目录(如用户家目录或当前工作目录)解析为绝对路径,再进行安全检查。
问题4:桌宠没有表情变化,或者情感状态不更新。
- 检查情感引擎是否被触发:在后端代码中,搜索对
emotion_engine.update()或类似函数的调用。确保它在每次Agent处理完用户请求后被调用。 - 检查前端是否请求并接收了情感数据:使用浏览器的开发者工具(F12)查看Godot前端与后端通信的网络请求。查看发送到
/api/chat或类似端点的响应JSON中,是否包含了emotion或status字段。如果没有,需要修改后端API的响应格式。 - 检查Godot前端解析和动画映射:在Godot的GDScript中,确认它正确解析了后端返回的情感数据,并根据
emotion_to_animation字典,正确地调用了AnimationPlayer.play(“animation_name”)。
问题5:LLM回复慢,或者经常超时。
- 模型选择:如果你使用的是
gpt-4或gpt-4-turbo,速度本身会比gpt-3.5-turbo慢。对于桌面宠物这种需要较快响应的场景,可以考虑使用更快的模型,如gpt-4o-mini或DeepSeek的API。 - 提示词优化:过长的系统提示词或对话历史会消耗更多token,增加延迟和成本。考虑:
- 精简系统提示词,只保留核心指令。
- 使用更高效的记忆检索,只注入最相关的几条历史记录,而不是全部。
- 设置LLM调用的超时时间(如30秒),并在前端显示“思考中...”的加载状态。
- 异步处理:确保后端的LLM调用是异步的(
async/await),这样不会阻塞整个API服务。FastAPI配合httpx或aiohttp库可以很好地实现。
6.3 性能与资源优化
优化1:减少Godot前端的内存与CPU占用。
- 精灵图优化:确保使用的精灵图尺寸适中,不要使用超大的高清图片。对于桌宠这种小窗口,256x256或512x512像素的图集通常就够了。
- 动画帧率:
AnimationPlayer的动画帧率不需要太高,15-24 FPS对于桌宠动画已经非常流畅。过高的帧率会浪费CPU。 - 非活动时休眠:当桌宠窗口失去焦点或用户一段时间无交互时,可以让Godot脚本降低更新频率(如从60FPS降到10FPS),或暂停不必要的动画和逻辑计算。
优化2:提升后端响应速度。
- 记忆检索缓存:对频繁查询的语义搜索结果进行短期缓存(例如使用
functools.lru_cache),避免对Cognee或向量数据库的重复查询。 - LLM响应流式传输:对于较长的回复,可以考虑使用Server-Sent Events (SSE) 或WebSocket将LLM生成的token流式传输到前端,让用户能更快地看到回复的开头,提升体验感。
- 数据库连接池:如果使用了数据库(如存储待办事项),确保使用连接池,避免频繁建立和关闭连接。
优化3:降低LLM API调用成本。
- 设置使用上限:在代码中设置每日或每月的最大token消耗上限,防止意外滥用。
- 选择性价比模型:根据任务复杂度选择模型。简单的闲聊可以用小模型(如
gpt-3.5-turbo),复杂的代码生成或推理再用大模型。 - 本地模型替代:如果对隐私和成本有极高要求,可以探索使用本地部署的小型开源模型(如Qwen2.5-7B-Instruct, Llama 3.2-3B等),通过Ollama或LM Studio等工具提供本地API。虽然能力可能稍弱,但对于很多桌面助手的场景已经足够,且实现了完全离线。
开发这样一个融合了多种技术的项目,就像在搭一个精致的数字生态。每一个模块——记忆、情感、工具、交互——都需要精心调校才能和谐共处。过程中最深的体会是,平衡“智能”与“可控”是关键。给AI太多能力(比如无限制的文件访问),你会不安;给得太少,它又显得笨拙。通过清晰的架构设计、严格的安全边界和持续迭代的提示词工程,我们才能逐步逼近那个理想中既聪明能干又安全可靠的桌面伙伴。这个项目是一个绝佳的起点,它搭建了一个可扩展的框架,剩下的想象力,就交给你了。
