
最近这两年终端里的“智能感”明显变强了。以前我们在命令行里只是敲 Git 命令、执行测试、翻日志现在越来越多的工具把大模型能力直接塞进了终端。这个“Hey”系列项目就是一种很典型的尝试在终端里输入一句自然语言就能触发一个 AI 助手的响应而不是靠一长串参数去拼一个 API 请求。很多开发者第一次看到这类项目时会觉得“这不就是封装了一下大模型 API 吗”但实际深入之后会发现真正值得研究的是它背后的三层结构输入层怎么解析自然语言、中间层怎么设计上下文和工具调用、输出层怎么处理流式内容和错误恢复。如果只看表面很容易误以为这只是个玩具但对经常在终端环境下工作的人来说这类项目真正降低的是“离开终端去浏览器里找工具”的切换成本。这篇文章会围绕“Hey”这个终端 AI 助手项目从技术原理、环境搭建、配置方法、核心代码实现、运行验证到生产环境的最佳实践完整讲清楚。读完你不仅能跑通一个最小可用的终端 AI 助手还能理解这类项目在工程设计上需要注意的关键点以及哪些地方容易踩坑。1. 这篇文章真正要解决的问题先说说读者最关心的问题我为什么要在终端里用 AI 助手最常见的开发场景是这样你正在排查一个线上问题日志文件在服务器上命令在终端里上下文也都在终端里。这时候想快速请教 AI往往需要打开浏览器、登录平台、复制日志、粘贴问题、等回答、再复制答案回来。一套流程下来少说也要两三分钟而且上下文还经常丢。终端 AI 助手想要解决的就是这种场景问题。它不是要取代图形化的 AI 服务而是把“提问—理解—回答”这个闭环压缩在同一个终端环境里。你不需要切换窗口不需要复制粘贴那么多文本模型可以直接读取当前目录的文件、管道输入的内容甚至帮你执行命令。这篇文章特别适合以下读者每天大量时间在终端工作的后端开发、运维、测试工程人员。对 LLM API 感兴趣但不想一上来就研究完整 RAG 框架的开发者。需要私密、可控方式对接模型能力的团队。当然也需要先说明边界这类终端 AI 助手并不是“万能命令解释器”它依赖模型能力、API 配置和工具权限设计。如果用不好可能比不用更危险。所以这篇文章不仅教你跑起来更重要的是帮你建立一套安全的、可维护的使用习惯。2. “Hey”是什么核心概念与技术原理2.1 终端 AI 助手的工作方式“Hey”本质上是一个命令行工具。你在终端里输入一个以hey开头的指令例如hey 帮我解释一下这个目录下的 main.py 做了什么工具收到指令后会做三件事读取指令文本和必要的上下文如当前目录文件、剪贴板内容。把文本发给大模型 API并附带系统提示词和对话历史。接收模型返回结果以流式或非流式方式输出到终端。这里的关键点在于很多新手以为“终端问 AI”就是把问题原样丢给 API。实际操作中为了让模型更好地理解通常需要构造一个 system 级别的 prompt告诉模型“你是一个运行在终端中的助手输出要简洁、准确、适合阅读”。如果没有这一步模型可能给出冗长的营销式回答这在终端里是比较尴尬的体验。2.2 LLM 的对话协议目前主流的模型服务大多提供了兼容 OpenAI 风格的 HTTP 接口。其中最核心的就是/chat/completions接口。这个接口接收一个 JSON 结构里面包含模型名称、消息列表、温度参数等。一个最简单的请求内容如下{ model: your-model-name, messages: [ {role: system, content: 你是一个终端助手回答要简洁。}, {role: user, content: ls 命令有哪些常用参数} ], stream: false }其中role有三种常见取值system系统级指令用于设定助手的角色和行为准则。user用户的输入。assistant模型此前生成的内容用于多轮对话。理解了这套协议你就掌握了整个终端的核心枢纽无论前面套多漂亮的 CLI 外壳最终都是要转换成上面这个 JSON 结构发送给模型。2.3 终端工具与普通 Python 脚本的差异有人会问这不就是一个 Python 脚本调用 requests 库吗为什么值得单独做成一个项目区别在于工程化程度上。一个合格的终端 AI 助手需要处理参数解析区分--model、--temperature、--context等选项。流式输出模型生成过程中就逐步打印而不是等完全生成后再一次性输出。上下文管理历史对话保存在内存或本地文件里多轮对话不丢。错误处理API 超时、网络中断、模型返回异常都要有明确的提示。权限控制哪些命令可以让模型调用执行哪些不能必须提前设计。这些点单个拿出来都不复杂但组合在一起就是一个小型工程课题。这篇文章的实操部分就是围绕这些点展开的。3. 环境准备与前置条件在开始写代码之前先把环境准备好。下面的版本以当前常见稳定版本为例具体版本请以实际项目为准本文重点演示通用思路。3.1 操作系统与环境建议使用 macOS 或 Linux 系统因为终端工具在这两类系统上体验最自然。Windows 用户建议使用 WSL 2或者 Windows Terminal PowerShell Core。其实 Python 代码本身是跨平台的但路径拼接和管道输入在非 Unix 环境下略有差异。3.2 Python 版本推荐使用 Python 3.10 及以上版本。原因有几个typing模块在 3.10 后支持更清晰的联合类型写法。asyncio的 API 更稳定。dotenv等配置库对环境变量的支持更友好。检查命令python3 --version如果输出类似Python 3.10.12就满足要求。3.3 API 获取与配置这里需要你有一个可以访问的大模型 API 服务。目前市面上主流的做法是找一个兼容 OpenAI 接口格式的服务商获取对应的 API Key 和 Base URL。注意不要在生产环境中使用个人账号泄露的 Key也不要把 Key 直接硬编码到代码里。正确做法是写入环境变量或本地配置文件中并设置文件权限。建议创建项目目录mkdir -p ~/projects/hey-assistant cd ~/projects/hey-assistant3.4 依赖安装本项目最小化依赖核心只需要两个库requests发送 HTTP 请求。python-dotenv加载.env配置文件。安装命令pip install requests python-dotenv如果你想做更高级的流式响应还可以安装httpx或openai官方 SDK。本文先以requests为例等理解了原理后再升级到官方 SDK 也不迟。4. 环境搭建与基础配置4.1 创建项目结构推荐的项目结构如下hey-assistant/ ├── hey.py ├── .env ├── .env.example ├── requirements.txt └── README.md其中hey.py主入口文件。.env存放 API Key 等敏感信息不要提交到 Git。.env.example配置模板提交到 Git方便协作。requirements.txt依赖清单。4.2 配置文件说明先创建.env文件touch .env编辑内容# 请将 YOUR_API_KEY 替换为你的真实密钥 HEY_API_KEYYOUR_API_KEY HEY_API_BASEhttps://api.example.com/v1 HEY_MODELyour-model-name这里解释一下每个配置项的用途HEY_API_KEY调用模型接口的密钥属于敏感信息。HEY_API_BASEAPI 服务的根地址不同服务商有所不同通常以/v1结尾。HEY_MODEL模型名称需要向服务商确认你的账号是否开通了该模型的权限。再创建.env.exampleHEY_API_KEYreplace-with-your-key HEY_API_BASEhttps://api.example.com/v1 HEY_MODELyour-model-name这个文件用于版本管理让别人知道要配置哪些环境变量。4.3 加载配置的通用代码在hey.py顶部加载配置import os from dotenv import load_dotenv load_dotenv() API_KEY os.getenv(HEY_API_KEY) API_BASE os.getenv(HEY_API_BASE) MODEL os.getenv(HEY_MODEL) if not API_KEY or not API_BASE or not MODEL: raise ValueError(请检查 .env 文件必须配置 HEY_API_KEY、HEY_API_BASE、HEY_MODEL)这段代码的作用很明确启动时强制检查三项关键配置。如果缺了任何一项直接报错退出。这样比运行时接口返回 401 更容易排查问题。5. 核心流程拆解5.1 主流程设计终端 AI 助手的运行流程可以拆成六个步骤解析用户输入。读取可选上下文如当前目录文件。构造 messages 列表。调用 API。处理输出。保存历史。用代码来表示主流程def main(): user_input parse_args() context read_context() messages build_messages(user_input, context) response call_api(messages) print_response(response) save_history(user_input, response)5.2 参数解析参数解析决定了这个工具用起来顺不顺手。我们希望支持以下用法python hey.py 解释一下什么是死锁 python hey.py --file main.py 分析这个文件的代码质量 python hey.py --history 继续上一轮对话这里用 argparse 实现import argparse def parse_args(): parser argparse.ArgumentParser(descriptionHey终端 AI 助手) parser.add_argument(question, typestr, help你要问的问题) parser.add_argument(--file, typestr, help附加文件内容作为上下文) parser.add_argument(--history, actionstore_true, help继续上一轮对话) parser.add_argument(--model, typestr, defaultMODEL, help指定模型名称) return parser.parse_args()这里真正值得注意的地方是--file参数。它允许你在不手动复制代码内容的情况下直接把文件内容作为上下文传给模型。这个功能看似简单却在真实开发中非常高频。比如你写了一段有问题的代码想问问模型哪里不对如果还要手动复制粘贴就失去了终端工具的意义。5.3 读取文件上下文读取文件时要注意两个陷阱编码问题和文件过大问题。def read_file_content(file_path): try: with open(file_path, r, encodingutf-8) as f: content f.read() except UnicodeDecodeError: with open(file_path, r, encodinggbk, errorsignore) as f: content f.read() if len(content) 30000: content content[:30000] \n... (内容过长已截断) return content这里做了两件事兼容 UTF-8 和 GBK 两种常见编码。限制上下文长度避免一次请求超过模型 token 上限。第二个限制非常关键。很多人在调用模型 API 时遇到 “context length exceeded” 错误往往就是因为把整个大文件丢给模型。实际项目中更推荐的做法是配合grep、sed等命令先缩小范围再把精确片段传给模型。5.4 构造消息列表构造消息列表时要区分首轮对话和后续对话。首轮通常只包含 system 和 user后续对话要追加 assistant 的历史回复。def build_messages(question, file_contentNone, historyNone): system_prompt ( 你是一个运行在终端中的 AI 助手。 回答要求简洁、准确、直接。 不要输出与问题无关的营销文案。 如果涉及代码请用代码块格式输出。 ) messages [{role: system, content: system_prompt}] if history: messages.extend(history) if file_content: question f以下是文件内容\n{file_content}\n\n用户问题{question} messages.append({role: user, content: question}) return messages这里的 system prompt 值得反复调试。不同模型对指令的服从程度不同如果你发现模型回答过于啰嗦可以在 system prompt 里加强约束例如“不超过 200 字”“不要客套”等。5.5 调用 API调用 API 是整个流程的核心。这里要区分流式和非流式。非流式写法简单适合初版实现import requests import json def call_api(messages, model_nameMODEL): url f{API_BASE}/chat/completions headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: model_name, messages: messages, temperature: 0.7, stream: False } try: resp requests.post(url, headersheaders, jsonpayload, timeout60) resp.raise_for_status() data resp.json() return data[choices][0][message][content] except requests.exceptions.Timeout: return 请求超时请检查网络或稍后重试。 except requests.exceptions.ConnectionError: return 无法连接到 API 服务请检查 API_BASE 配置。 except requests.exceptions.HTTPError as e: return fAPI 返回错误{e.response.status_code}请检查 Key 和权限。 except (KeyError, IndexError): return 响应格式异常请检查模型名称是否正确。这段代码里最容易被新手忽略的是异常捕获。如果不对超时和连接错误做处理一旦 API 服务不稳定整个程序就会直接崩溃用户连一个可读的错误提示都看不到。5.6 保存对话历史保存历史是为了支持多轮对话。最简单的做法是用 JSON 文件存储import json import os HISTORY_FILE os.path.expanduser(~/.hey_history.json) def append_history(user_input, response): history load_history() history.append({role: user, content: user_input}) history.append({role: assistant, content: response}) # 只保留最近 20 条消息避免历史过长 history history[-20:] with open(HISTORY_FILE, w, encodingutf-8) as f: json.dump(history, f, ensure_asciiFalse, indent2) def load_history(): if not os.path.exists(HISTORY_FILE): return [] with open(HISTORY_FILE, r, encodingutf-8) as f: return json.load(f)这里要注意历史文件不能无限增长。对话轮数多了以后历史消息会占用大量 token也会拖慢响应速度。所以这里做了一个简单的截断策略只保留最近 20 条消息。真实项目里还可以按会话 ID 区分多份历史文件。6. 完整示例代码实现完整示例把上面拆解的模块整合到一起。为方便阅读下面的代码全部写在hey.py中实际工程中你可以按模块拆分。# 文件路径hey.py import argparse import json import os import requests from dotenv import load_dotenv load_dotenv() API_KEY os.getenv(HEY_API_KEY) API_BASE os.getenv(HEY_API_BASE) MODEL os.getenv(HEY_MODEL) HISTORY_FILE os.path.expanduser(~/.hey_history.json) def parse_args(): parser argparse.ArgumentParser(descriptionHey终端 AI 助手) parser.add_argument(question, typestr, help你要问的问题) parser.add_argument(--file, typestr, help附加文件内容作为上下文) parser.add_argument(--history, actionstore_true, help继续上一轮对话) parser.add_argument(--model, typestr, defaultMODEL, help指定模型名称) return parser.parse_args() def read_file_content(file_path): try: with open(file_path, r, encodingutf-8) as f: content f.read() except UnicodeDecodeError: with open(file_path, r, encodinggbk, errorsignore) as f: content f.read() if len(content) 30000: content content[:30000] \n... (内容过长已截断) return content def load_history(): if not os.path.exists(HISTORY_FILE): return [] with open(HISTORY_FILE, r, encodingutf-8) as f: try: return json.load(f) except json.JSONDecodeError: return [] def append_history(user_input, response): history load_history() history.append({role: user, content: user_input}) history.append({role: assistant, content: response}) history history[-20:] with open(HISTORY_FILE, w, encodingutf-8) as f: json.dump(history, f, ensure_asciiFalse, indent2) def build_messages(question, file_contentNone, historyNone): system_prompt ( 你是一个运行在终端中的 AI 助手。 回答要求简洁、准确、直接。 不要输出与问题无关的营销文案。 如果涉及代码请用代码块格式输出。 ) messages [{role: system, content: system_prompt}] if history: messages.extend(history) if file_content: question f以下是文件内容\n{file_content}\n\n用户问题{question} messages.append({role: user, content: question}) return messages def call_api(messages, model_nameMODEL): url f{API_BASE}/chat/completions headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: model_name, messages: messages, temperature: 0.7, stream: False } try: resp requests.post(url, headersheaders, jsonpayload, timeout60) resp.raise_for_status() data resp.json() return data[choices][0][message][content] except requests.exceptions.Timeout: return 请求超时请检查网络或稍后重试。 except requests.exceptions.ConnectionError: return 无法连接到 API 服务请检查 API_BASE 配置。 except requests.exceptions.HTTPError as e: return fAPI 返回错误{e.response.status_code}请检查 Key 和权限。 except (KeyError, IndexError): return 响应格式异常请检查模型名称是否正确。 def main(): args parse_args() file_content None if args.file: if not os.path.exists(args.file): print(f文件不存在{args.file}) return file_content read_file_content(args.file) history load_history() if args.history else None messages build_messages(args.question, file_content, history) response call_api(messages, args.model) print(response) append_history(args.question, response) if __name__ __main__: main()这段代码虽然只有一百多行但已经具备一个终端 AI 助手的最小完整形态。它支持参数解析、文件上下文、多轮对话和基本的异常处理可以直接跑通一个真实的问答流程。7. 运行结果与效果验证7.1 运行方式先确认.env配置正确然后运行python hey.py Python 中 list 和 tuple 有什么区别预期输出类似list 是可变序列tuple 是不可变序列。list 支持 append、pop 等修改操作tuple 创建后不能修改。在需要防止数据被意外修改的场景下优先使用 tuple。注意实际模型输出语言和内容会因模型而异但关键是要确认程序本身跑通了并且没有报配置错误。7.2 验证文件上下文创建一个测试文件cat test_example.py EOF def add(a, b): return a b EOF然后运行python hey.py --file test_example.py 这个函数有没有问题如果输出中提到了add函数的参数和返回值说明文件内容成功传给了模型。7.3 验证多轮对话第一轮python hey.py 给我一个快速排序的 Python 实现第二轮python hey.py --history 刚才的代码里如果数组很大会不会有性能问题如果第二轮的回复涉及“上一轮提供的快速排序”说明历史保存和追加逻辑正常。7.4 验证失败场景把.env里的HEY_API_KEY故意改错再运行python hey.py 你好预期会看到提示API 返回错误401请检查 Key 和权限。这说明异常处理逻辑生效了而不是直接抛出一个难以理解的堆栈。8. 常见问题与排查思路问题现象可能原因排查方式解决方案启动时报错提示缺少 HEY_API_KEY.env文件未创建或变量名拼写错误检查.env是否存在并确认HEY_API_KEY拼写复制.env.example为.env并填写真实值请求返回 401API Key 无效或未在请求头中正确携带打印headers检查 Authorization 字段重新生成 API Key确认没有多余空格请求返回 404API_BASE 路径不对确认接口地址是否为{API_BASE}/chat/completions查看服务商文档确认 Base URL 是否以/v1结尾返回内容很长刷屏模型没有按终端场景约束输出修改 system prompt要求“200字以内”在 system prompt 中加入长度限制中文乱码终端编码或文件编码不匹配检查终端字符集和 Python 读取文件的编码方式统一使用 UTF-8 编码Windows 下设置chcp 65001多轮对话答非所问历史消息过多或历史文件损坏打开~/.hey_history.json查看内容删除历史文件或限制保留条数大文件内容超限一次性把整个文件塞进 context查看报错信息中的 token 数用sed/grep先截取关键片段或调整read_file_content中的截断长度网络超时网络不稳定或 API 服务响应慢检查网络连接用curl测试接口连通性增加 timeout 值或改为流式输出提升体验这里的核心思路是任何错误都要能归因到一个可操作的排查动作。不要只把异常堆栈抛给用户这对终端工具的用户体验是致命的。9. 最佳实践与工程建议9.1 安全边界与权限管理使用终端 AI 助手时安全是头号问题。尤其是当你准备让工具具备“执行命令”能力时必须设置严格的白名单机制。本文示例中的hey.py只负责输出文本不执行任何系统命令这是一个相对安全的边界。如果你未来想扩展为“让模型帮你执行 shell 命令”建议遵守以下原则执行命令前必须打印完整命令并等待用户确认。只允许执行白名单命令例如git status、kubectl get等只读命令。禁止直接执行包含管道符、重定向、sudo、rm等危险操作。所有执行记录要写入日志便于追溯。9.2 密钥管理永远不要把你的 API Key 提交到 Git 仓库即使仓库是私有的也可能因为协作成员误操作而泄露。推荐做法在.gitignore中加入.env。使用export HEY_API_KEYxxx注入环境变量。在 CI/CD 中使用密钥管理服务而不是明文配置文件。9.3 配置管理与多环境切换实际项目中你可能需要在开发、测试、生产环境中使用不同的模型服务。建议在.env之外支持按环境加载配置import os from dotenv import load_dotenv ENV os.getenv(HEY_ENV, dev) load_dotenv(f.env.{ENV})这样你就可以同时维护.env.dev和.env.prod两份配置切换环境时只需要修改HEY_ENV变量。9.4 日志记录对于长期使用的工具建议增加日志文件import logging logging.basicConfig( filenameos.path.expanduser(~/.hey_assistant.log), levellogging.INFO, format%(asctime)s %(levelname)s %(message)s )每次请求的模型、耗时、状态码都可以记录进去。当问题出现时先查日志而不是让用户反复重试。9.5 模型版本兼容大模型服务和开源模型迭代速度非常快。你昨天配置的模型名称今天可能已经下线。所以在设计工具时不要把模型名称写死在代码里而是放在配置文件中。同时在异常处理中遇到模型不存在的错误码时要提示用户去检查HEY_MODEL配置。9.6 交互体验优化除了功能正确性终端工具的体验也很重要。几个容易忽略的细节请求期间可以打印思考中...避免用户以为程序卡住了。输出结束后打印分隔线方便在长对话中区分不同轮次。支持CtrlC中断请求避免长时间等待。10. 总结与后续学习方向把“Hey”从零跑通核心并不是写那几百行代码而是理解终端 AI 工具在设计时需要考虑的完整链路输入解析、上下文构造、配置管理、异常处理、历史存储和安全边界。这套思路完全可以迁移到其他 CLI 工具的开发中。如果你想继续深入建议按以下方向延伸流式输出。使用 SSE 或官方 SDK 的 streaming 模式让模型逐字输出大幅提升交互体验。嵌入向量检索。在本地建立小型的代码知识库让模型在回答前先检索相关内容减少无关输出。工具调用能力。让模型可以主动调用本地函数或外部 API把“问答”升级为“任务执行”。插件化架构。把不同的知识库、模型服务、上下文来源抽象成插件做成可复用的脚手架。最后提醒一点这类工具的第一版永远不要把权限做太大。先用只读模式跑通流程确认稳定后再逐步扩展。终端工具的安全事故往往不是模型答错而是权限边界没控制好。