1. 项目概述在终端里养一个AI助手如果你和我一样大部分工作时间都泡在终端里那你肯定也幻想过要是能有个“副驾驶”在命令行里随时待命帮我查资料、写代码片段、整理文件甚至去Reddit上找找答案那该多省事。今天要聊的gem-assist就是我基于这个想法折腾出来的一个Python项目。本质上它是一个运行在你本地终端里的AI个人助手核心是调用大语言模型LLM的能力再给它配上各种“工具”让它不仅能聊天还能真替你干点活。这个项目最初只是我为了偷懒用Google的Gemini模型写的一个自用小脚本。后来发现光用一个模型不够灵活比如网络不好或者想用本地模型时就很麻烦。所以我引入了litellm这个库它就像一个“模型路由器”让gem-assist不仅能对接Gemini还能支持通过Ollama运行的本地模型以及一大堆其他云服务商提供的模型。这样一来它的实用性就大大增强了。你可以把它看作一个高度可定制的“终端AI助手框架”它的核心价值不在于预设了多少功能而在于它提供了一套清晰的架构让你可以很方便地教你的AI助手学会新技能真正融入你的工作流。2. 核心设计思路与架构解析2.1 为什么选择“工具调用”架构很多AI应用只是简单的聊天机器人你问它“我的磁盘空间还剩多少”它只能根据训练数据给你一个笼统的回答或者告诉你“我无法访问你的系统”。这显然不够用。gem-assist的核心设计哲学是“授人以鱼不如授人以渔”——更准确地说是“给AI配上渔具”。我采用了“工具调用”Tool Calling的架构。简单来说当AI理解你的自然语言指令后它不会也不能直接操作系统而是会判断是否需要、以及需要调用哪个预先定义好的“工具”函数。比如你问“当前目录下有哪些Python文件”AI会识别出这是一个“列出目录”的请求然后调用list_dir这个工具函数获取真实结果后再组织语言回复给你。这种设计有几个关键优势安全性可控AI本身不直接拥有系统权限所有危险操作如执行Shell命令、删除文件都封装在具体的工具函数里。你作为开发者可以严格控制哪些工具对AI开放。能力可扩展给AI增加新能力不再是去微调一个几十GB的模型而只是用Python写一个新的工具函数。比如你想让助手能查快递只需要写一个调用快递API的工具即可。结果可靠工具执行返回的是确定性的数据如文件列表、网络请求结果AI基于这些真实数据生成回答避免了“幻觉”即一本正经地胡说八道准确性更高。2.2 技术栈选型背后的考量项目的技术选型都是围绕“灵活”和“实用”两个目标来的。核心模型接口LiteLLM这是项目后期一个非常重要的决定。最初只支持Gemini API但用户环境千差万别有的需要联网用最强的云模型有的出于隐私考虑要用本地模型还有的已经有其他云服务的API密钥。自己为每个模型写适配器太麻烦。LiteLLM完美解决了这个问题它提供了统一的接口来调用上百种模型OpenAI, Anthropic, Gemini 以及通过Ollama、vLLM等部署的本地模型。在config.py里你只需要将MODEL设置为类似“gemini/gemini-2.0-flash”或“ollama/llama3.2”的格式剩下的LiteLLM会搞定。这相当于给助手装了一个“万能模型驱动”。依赖与包管理UV我选择了uv而不是传统的pip或poetry。uv是用Rust写的速度极快特别是在创建虚拟环境和安装依赖时体验提升非常明显。对于这种可能被频繁安装、测试的项目快速搭建环境是个硬需求。uv sync一条命令就能处理好所有依赖对新手也更友好。内置工具库的选择搜索选择了duckduckgo-search因为它无需API密钥直接可用对于快速信息检索足够用且尊重隐私。Reddit交互使用praw这是Python访问Reddit API的事实标准库稳定且功能全面。终端UI使用rich库来美化输出让对话和列表显示更美观提升交互体验。配置管理使用python-dotenv管理API密钥等敏感信息这是Python项目的标准做法。注意工具库的选型是“够用就好”的原则。例如网络搜索工具可能遇到限流文件下载工具对动态链接支持不完美这些在“已知问题”里都有说明。这提醒我们AI工具调用的是真实世界的接口必须考虑这些接口的稳定性和局限性。3. 从零开始环境搭建与初次运行3.1 前期准备获取“燃料”和“引擎”要让gem-assist跑起来你需要两样东西Python环境和AI模型的“访问权限”。首先确保你的Python版本在3.11或以上。这是很多现代异步库和语言特性的最低要求可以用python --version检查。其次安装uv。如果你在macOS或Linux上一条命令通常就够了curl -LsSf https://astral.sh/uv/install.sh | sh安装后重启终端运行uv --version确认安装成功。最后也是最重要的决定你的AI助手用什么“大脑”。这里有两个主流方向使用云端模型如Gemini需要API密钥。前往 Google AI Studio 创建一个API密钥。注意Gemini API目前可能不在所有区域都可用且新项目可能有免费额度限制。在项目的config.py中你需要将MODEL变量设置为类似“gemini/gemini-2.0-flash”的形式。LiteLLM会自动识别gemini/前缀并使用你的密钥。使用本地模型如通过Ollama需要安装Ollama并拉取模型。从 Ollama官网 下载并安装。在终端运行ollama pull llama3.2或其他你喜欢的模型如qwen2.5:7b来下载模型。在config.py中将MODEL设置为“ollama/llama3.2”。确保运行gem-assist时Ollama服务正在运行通常安装后会自动启动一个后台服务。3.2 安装与配置实战假设我们选择使用Gemini云端模型。第一步克隆项目并进入目录git clone https://github.com/Fus3n/gem-assist cd gem-assist第二步使用uv一键安装所有依赖。这个过程会自动创建Python虚拟环境venvuv sync这条命令会读取pyproject.toml文件安装所有列出的依赖包。如果遇到网络问题可以考虑配置镜像源。第三步配置环境变量。这是保护你API密钥的关键步骤。# 在项目根目录下创建 .env 文件 touch .env # 用你喜欢的编辑器打开 .env 文件填入以下内容在.env文件中添加GEMINI_API_KEY你的_真实_Gemini_API_密钥 # 以下为可选如果需要Reddit功能 REDDIT_ID你的_Reddit_应用_ID REDDIT_SECRET你的_Reddit_应用_密钥重要提示.env文件包含敏感信息务必将其添加到.gitignore中避免意外提交到公开仓库。第四步基础配置。打开config.py文件你会看到一些可配置项# 模型设置根据你的选择修改 MODEL “gemini/gemini-2.0-flash” # 例如使用Gemini Flash # MODEL “ollama/llama3.2” # 或者使用本地Ollama模型 # 助手名称 NAME “Gem” # 系统提示词这决定了AI助手的“性格”和核心指令 SYSTEM_PROMPT “””你是一个有帮助的终端助手。你可以使用工具来获取信息或执行操作。当用户请求需要工具时请调用合适的工具。保持回复简洁专业。”””系统提示词SYSTEM_PROMPT是调教AI行为的关键。你可以把它修改得更具体比如“你是一个专注于软件开发辅助的终端助手擅长解释代码、生成脚本片段和搜索技术文档。在回复中优先考虑准确性和可执行性。”3.3 启动与首次对话完成以上步骤后就可以启动你的AI助手了uv run assistant.py如果一切顺利你会看到终端清屏并出现一个漂亮的、带有[Gem]提示符的输入框。这表示助手已经启动正在等待你的指令。让我们进行几个简单的测试来验证核心功能是否正常基础问答输入“你好介绍一下你自己。”。AI应该会基于SYSTEM_PROMPT和它的知识库进行回复。工具调用测试 - 系统信息输入“告诉我现在的系统时间。”。这是一个对get_current_time工具的显式调用。观察AI的回复它应该会返回一个具体的时间戳而不是模糊的描述。工具调用测试 - 文件操作输入“列出当前目录下的所有文件。”。AI会调用list_dir工具并将结果格式化后输出给你。如果这些测试都通过了恭喜你你的终端AI助手已经成功上线你可以尝试问它“今天天气怎么样”会触发网页搜索或者“计算一下 125 * 88 17 等于多少。”会触发数学计算工具。实操心得第一次运行时最常见的错误是API密钥未设置或模型名称错误。如果启动失败请首先检查.env文件中的GEMINI_API_KEY是否正确且文件名前面确实有一个点.env。config.py中的MODEL字符串是否完全匹配LiteLLM支持的格式。对于Gemini必须是“gemini/模型名”的格式。错误信息通常会给出线索仔细阅读终端报错。4. 核心功能深度剖析与使用技巧4.1 工具库详解让你的助手“手眼通天”gem-assist的强大完全体现在其丰富的工具集上。这些工具主要定义在utility.py文件中。我们来分类解读一些最常用的工具并分享一些使用技巧。文件与系统操作工具这是最实用的一类工具让AI能直接与你本地的开发环境互动。list_dir(path‘.’): 列出目录内容。技巧你可以直接说“看看src目录里有什么”AI会自动将“src”作为path参数传递。如果路径包含空格AI通常会处理好引号。read_file(file_path): 读取文件内容。注意对于大文件AI的上下文窗口有限可能无法全部读入。最好用于查看配置文件、日志片段或源代码文件。write_files(file_updates): 写入文件。这是需要高度谨慎的工具。参数file_updates是一个列表每个元素是{“path”: “文件路径”, “content”: “文件内容”}。AI可能会根据你的要求生成代码并写入。强烈建议在让AI执行写入操作前先让它用read_file读一下目标文件确认位置或者先写入一个临时文件。run_shell_command(command): 执行Shell命令。这是威力最大也最危险的工具。在我的默认配置中这个工具可能被禁用或受到严格限制。如果你决定启用它务必通过系统提示词SYSTEM_PROMPT严格约束AI的行为例如禁止执行rm -rf /、format等危险命令。一个更安全的做法是只为AI封装特定的、安全的命令工具而不是开放通用的Shell。信息检索与网络工具让助手成为你的信息捕手。duckduckgo_search_tool(query, max_results5): 进行网页搜索。技巧当你问“如何用Python解析JSON”时AI可能会直接调用此工具。你可以要求它“搜索最新的信息”因为默认搜索结果是实时的。get_website_text_content(url): 获取网页正文文本。这对于让AI总结一篇技术文章非常有用。注意复杂的JavaScript渲染的页面可能无法获取有效内容。reddit_search(subreddit, query, limit5): 在Reddit特定板块内搜索。使用场景当你遇到一个具体的错误信息时可以让AI“去r/learnpython板块搜索一下 ‘SSL Error’”这比通用搜索更能找到开发者社区的解决方案。实用工具evaluate_math_expression(expression): 计算数学表达式。支持“sin(pi/4)”、“2**10”这样的复杂表达式底层用的是Python的eval在安全沙箱内非常方便。write_note(note_name, content)/read_note(note_name): 在对话间保存和读取笔记。这是实现记忆功能的关键。你可以让AI“记下明天要修复的bug是登录模块的Timeout错误”然后在下次启动时问它“我之前让你记了哪些待办事项”。笔记实际保存在项目目录下的一个JSON文件中。4.2 对话、记忆与命令系统对话管理输入exit、quit或bye可以结束对话。整个对话历史会保留在AI的上下文窗口中这意味着它记得你之前说过的话。但对于超长对话可能会因为上下文长度限制而遗忘开头的内容。记忆持久化除了write_note工具项目本身支持保存和加载整个对话。查看assistant.py的相关代码你可以找到序列化对话历史到文件的功能。这对于调试或回顾非常有用。内置命令系统输入/commands可以列出所有可用的命令。这些命令是硬编码在gem/builtin_commands.py中的功能不同于AI调用的工具。例如可能包含/save_chat、/load_chat、/clear等。命令是即时执行的不经过AI模型推理因此更可靠、快速。4.3 配置进阶调教你的专属助手config.py是你的助手“调教中心”。除了基础的模型和名字最值得花时间的是SYSTEM_PROMPT。一个强大的系统提示词应该包含身份与角色明确告诉AI它是什么“一个资深的Linux系统管理员助手”。能力范围与限制明确它能做什么不能做什么“你可以使用工具来读写文件但禁止执行任何包含 ‘rm -rf’ 或 ‘format’ 的命令”。回复风格指定你喜欢的风格“回复请尽量简洁优先给出代码片段或命令解释放在后面”。工具使用规则指导它如何选择工具“当用户询问需要实时信息的问题时优先使用搜索工具”。例如针对编程助手你可以这样设置SYSTEM_PROMPT “”” 你是一个专业的软件开发助手精通Python和命令行。你的核心任务是帮助用户高效编码和解决问题。 - 当用户提出编程问题时优先考虑提供可直接运行或修改的代码片段。 - 在操作文件前如果路径不明确先使用list_dir工具确认。 - 执行任何可能修改系统的命令前必须向用户二次确认。 - 回复格式首先用一句话总结关键点然后提供代码/命令最后是简要说明。 - 如果问题需要最新信息请主动使用duckduckgo_search_tool。 保持冷静、精准、乐于助人。 “””每次修改config.py后都需要重启assistant.py才能生效。5. 扩展指南为你的助手打造新工具gem-assist的真正潜力在于扩展。作者在README里也说了扩展需要写点代码。别担心这个过程比想象中简单因为项目结构很清晰。5.1 扩展原理工具是如何被调用的整个工具调用的流程可以简化为用户输入你在终端输入“把我桌面上的report.txt文件复制到backup文件夹”。AI理解与规划AI模型Gemini/Llama等分析这句话识别出意图是“复制文件”并提取出关键参数源路径~/Desktop/report.txt和目标路径~/backup。工具匹配与调用AI发现内置工具集中有一个copy_file(source, destination)函数与之匹配。于是它生成一个结构化的“工具调用请求”其中包含函数名和参数。执行与返回gem-assist的后台代码接收到这个请求在安全上下文中执行真正的copy_file函数并将执行结果成功或错误信息返回给AI。组织回复AI根据工具执行的结果组织成自然语言回复给你“已经将 report.txt 复制到 backup 文件夹。”5.2 三步添加一个新工具以“获取天气”为例假设我们想让助手能查询天气。我们需要创建一个新的工具函数并让系统知道它的存在。第一步在utility.py中定义工具函数打开utility.py文件在末尾或其他合适位置添加一个新函数。为了获取天气我们需要一个天气API。这里以调用免费的 Open-Meteo API 为例。import requests def get_weather_tool(latitude: float, longitude: float) - str: “”” 根据经纬度获取当前天气信息。 参数: latitude: 纬度 longitude: 经度 返回: 包含天气信息的字符串。 “”” try: # 使用 Open-Meteo 免费API url f“https://api.open-meteo.com/v1/forecast?latitude{latitude}longitude{longitude}current_weathertrue” response requests.get(url) response.raise_for_status() # 检查HTTP错误 data response.json() current data[‘current_weather’] temperature current[‘temperature’] wind_speed current[‘windspeed’] weather_code current[‘weathercode’] # 简单转换天气代码为文字WMO代码 weather_map { 0: “晴空” 1: “大部分晴朗” 2: “局部多云” 3: “阴天” 45: “雾” 48: “冻雾” 51: “小雨” 61: “雨” 80: “阵雨” } weather_desc weather_map.get(weather_code, “未知天气状况”) return f“当前天气{weather_desc}。温度{temperature}°C风速{wind_speed} km/h。” except requests.exceptions.RequestException as e: return f“获取天气信息失败{e}” except KeyError as e: return f“解析天气API响应失败缺少字段{e}”关键点函数必须有清晰的文档字符串“”“”“”AI会依靠它来理解这个工具是做什么的、需要什么参数。参数最好有类型提示如latitude: float这有助于AI理解。内部实现要做好错误处理并返回明确的字符串信息。AI会将这个字符串作为“工具执行结果”来读取。第二步将工具注册到系统中仅仅定义函数还不够需要让AI知道这个新工具的存在。这通常在assistant.py或初始化AI客户端的地方完成。你需要找到工具列表的定义位置可能是一个名为tools的列表或类似结构将你的新工具函数添加进去。假设在assistant.py中找到类似下面的代码块from utility import ( list_dir, read_file, write_files, …, get_weather_tool # 导入我们新写的函数 ) # 定义可供AI调用的工具列表 available_tools [ {“function”: list_dir, “description”: “列出指定目录下的文件和文件夹”}, {“function”: read_file, “description”: “读取指定文件的内容”}, # … 其他工具 {“function”: get_weather_tool, “description”: “根据经纬度查询当前天气信息”}, # 添加新工具 ]注意description非常重要它是AI决定是否调用该工具的主要依据描述要准确、简洁。第三步测试新工具重启assistant.py。现在你可以尝试对AI说“查询一下纬度39.9经度116.4的天气。” AI应该能识别出你的意图调用get_weather_tool并返回天气信息。为了让交互更自然你甚至可以进一步优化。比如修改系统提示词告诉AI“当用户询问某个城市的天气时你可以先使用网络搜索工具获取该城市的经纬度然后再调用天气查询工具。” 这展示了工具之间可以协作实现更复杂的功能。5.3 添加自定义命令除了工具你还可以添加“命令”。命令是以前缀如/触发的、直接执行的函数不经过AI推理响应更快、更确定。在gem/builtin_commands.py中你可以看到CommandExecutor类。添加一个新命令例如一个/weather命令class CommandExecutor: def __init__(self, …): # … 初始化代码 self.commands { “commands”: self.list_commands, “save_chat”: self.save_chat, # … 其他命令 “weather”: self.get_weather_command, # 添加新命令映射 } # … 其他方法 def get_weather_command(self, args): “”“手动触发天气查询例如/weather 39.9 116.4”“” if len(args) ! 2: return “用法/weather 纬度 经度” try: lat, lon float(args[0]), float(args[1]) # 直接调用我们之前写的工具函数 from utility import get_weather_tool result get_weather_tool(lat, lon) return result except ValueError: return “错误纬度和经度必须是数字。”然后在assistant.py中确保这个命令执行器被正确初始化和调用。这样用户就可以直接输入/weather 39.9 116.4来获得天气而不需要AI进行意图识别。6. 常见问题排查与实战经验在实际使用和扩展gem-assist的过程中你肯定会遇到各种问题。下面是我踩过的一些坑和解决方案。6.1 启动与连接问题问题现象可能原因排查步骤与解决方案运行uv run assistant.py立即报错提示缺少模块。依赖未正确安装或虚拟环境未激活。1. 确保在项目根目录下。2. 重新运行uv sync。3. 尝试使用uv run –with python 助手.py显式指定环境。启动后AI无响应或提示模型错误。1. API密钥错误或未设置。2. 模型名称拼写错误。3. 网络问题对于云端模型。4. Ollama服务未运行对于本地模型。1.检查.env文件确认GEMINI_API_KEY等变量名正确且值无误。可以临时在终端用echo $GEMINI_API_KEY测试是否被正确加载。2.检查config.py中的MODEL确保与LiteLLM支持的格式完全一致。例如gemini/gemini-2.0-flash。3.测试连接可以写一个简单的Python脚本只用litellm调用一次模型看是否成功。4.对于Ollama运行ollama list确认模型已下载运行ollama serve确保服务在运行。提示Invalid API Key或Permission denied。API密钥无效、过期或没有启用对应API服务。1. 前往对应的云平台如Google AI Studio检查API密钥状态确认是否已启用。2. 某些API有区域限制确认你的账户和请求区域符合要求。3. 如果使用免费额度确认额度是否已用尽。6.2 工具调用失败问题问题现象可能原因排查步骤与解决方案AI识别了意图但回复说“无法调用工具”或调用后无结果。1. 工具函数本身有bug异常未处理。2. 工具函数返回的数据格式AI无法理解。3. 网络工具遇到限流或目标网站屏蔽。1.查看终端日志gem-assist通常会在后台打印工具调用的详细信息和错误。这是最重要的调试信息来源。2.单独测试工具函数在Python交互环境中手动导入并调用你怀疑有问题的工具函数传入参数看是否正常返回。3.检查工具描述确保available_tools列表里该工具的description描述清晰能让AI准确理解其用途。文件操作工具如写文件成功了但内容不对或位置错了。AI误解了你的自然语言描述提取了错误的参数。1.更精确地描述与其说“把配置保存到文件”不如说“将以下内容写入到当前目录下的config.yaml文件中”。2.分步操作对于重要操作先让AI用list_dir确认当前目录再用read_file确认目标文件如果存在最后再执行写入。搜索工具返回的结果不相关或过时。搜索关键词不够精确或DuckDuckGo的索引问题。1.引导AI优化查询你可以说“用更具体的关键词搜索一下‘Python asyncio TaskGroup 用法示例’”。2. 考虑扩展工具集成其他搜索引擎如Searxng自建实例或特定网站如Stack Overflow的搜索。6.3 性能与行为调优响应慢云端模型可能是网络延迟或模型本身较慢如gemini-2.0-pro比flash慢。尝试在config.py中切换到更快的模型。本地模型确认你的硬件尤其是GPU是否足够支撑所选模型。7B参数量的模型在CPU上推理也会很慢。考虑使用量化版本如llama3.2:7b-instruct-q4_K_M。工具延迟如果工具涉及网络请求如搜索、获取网页这部分时间也会算入总响应时间。AI“不听话”或乱用工具强化系统提示词在SYSTEM_PROMPT中明确禁止某些行为并规定工具使用优先级。例如“未经明确确认不得使用write_files或run_shell_command工具。”调整工具列表在available_tools中暂时注释掉危险或不稳定的工具。模型温度Temperature如果使用的是支持参数调整的模型通过LiteLLM可以尝试降低temperature值如从0.7降到0.2使AI的输出更确定、更少“创造性”从而减少乱用工具的可能。6.4 扩展开发中的坑新工具不被调用首先检查工具函数是否被正确导入并添加到available_tools列表。其次检查函数的文档字符串是否清晰描述了功能和参数。AI严重依赖这个描述来做决策。最后重启assistant.py。工具函数异常导致崩溃务必在你的工具函数内部进行完善的异常处理try…except并返回一个友好的错误信息字符串而不是抛出异常。因为AI需要读取工具的返回结果来生成回复。依赖问题如果你新写的工具需要额外的Python包比如requests用于天气API记得把这个依赖添加到pyproject.toml文件的[project]部分的dependencies里然后重新运行uv sync。经过以上步骤你应该已经拥有了一个功能强大且高度个性化的终端AI助手。它不再是一个简单的聊天玩具而是一个能真正融入你工作流、替你执行重复任务的智能伙伴。从简单的文件操作到复杂的信息搜集与处理你可以通过添加工具来不断扩展它的能力边界。