各位读者朋友,大家好。
今天想和大家分享一个最近在 Hacker News 上热度不错的 Emacs 开源项目:Agent-shell。它打出的口号很有吸引力——“vendor-neutral chat with AI agents in Emacs”,翻译过来就是“在 Emacs 中与 AI 智能体进行厂商中立的对话”。
对于深度 Emacs 用户来说,这一两年我们经历了 AI 编程助手的大爆发。官方客户端很多,但你要么得用 VS Code,要么得忍受特定厂商的账号体系。在 Emacs 里,我们更希望“我的编辑器我做主”,最好能自由切换底层大模型,同时保留 Emacs 原生的编辑手感。Agent-shell 正是为了解决这类痛点而出现的项目。
本文将围绕 Agent-shell 这个主题,从设计动机、环境准备、配置流程到 Emacs 集成实战展开,帮你快速理解这类“厂商中立”的 AI Agent 客户端思路,并且能跟着步骤在自己的 Emacs 环境里运行起来。
无论你是 Emacs 老手,还是刚开始接触 Emacs AI 生态的开发者,这篇文章都会给你提供一套相对完整的落地参考。
1. 背景:为什么需要在 Emacs 中引入 AI Agent
1.1 从大模型客户端到 AI 智能体的演进
在深入 Agent-shell 之前,我们先把概念理清。
过去几年,我们习惯把大语言模型称为“聊天机器人”或“AI 助手”。最典型的用法是在网页对话框里输入问题,模型返回一段回答。后来,随着代码补全、代码生成、自动化修 Bug 这些场景的出现,“AI Agent”这个概念开始频繁出现在社区中。
什么是 AI Agent?通俗地理解,Agent 是一个能够自主完成多步任务的智能体。它不只是回答你“这段代码怎么改”,而是会主动分析项目结构、调用 Shell 命令、搜索文件、执行测试,然后根据结果继续迭代,直到完成目标。目前 ChatGPT、Claude、各种编程 IDE 里都在不同程度地实现 Agent 能力。
对于使用 Emacs 的开发者,特别是常年和终端、Lisp、文本界面打交道的用户来说,我们并不满足于只用 ChatGPT 网页,也不希望为每一个 AI 厂商安装不同的 IDE 客户端。我们需要的是一条让 AI 能力融入现有工作流的桥梁。
1.2 现有 Emacs AI 包面临的痛点
Emacs 生态中已经有很多第三方 AI 包,例如 gptel、llm.el、aichat、copilot-chat 等。我自己实际使用下来,遇到几个典型问题:
- 绑定单一厂商:很多包最初只实现了一套 API,比如 OpenAI 的 Chat Completions,后面再补 Anthropic、Gemini 的支持。如果你换模型厂商,就得换包。
- 依赖重量级 SDK:有些包为了支持多个模型,拉入了一大堆依赖,Elisp 侧还好,但 Python 侧、Node 侧依赖经常让人头疼。
- 没有真正“Agent”化:多数包只做“单轮对话 + 复制代码”,缺少和 Shell 交互、工具调用、命令执行的过程。
- 配置分散:你在 gptel 里配好的模型参数,到另一个工具里完全不能复用。
这些问题并不是某一个包做得不好,而是整个 Emacs AI 生态还在发展早期,大家都还在探索。Agent-shell 的出现在某种程度上就是针对这些痛点的回应。
1.3 Emacs 用户需要什么
Emacs 用户有一个共同特点:希望一切可定制、可审计、可控。AI Agent 进入 Emacs 后,至少应该满足以下几点:
- 可配置性:API 地址、模型名称、请求 Headers、代理设置都能调。
- 可持续性:当某一厂商不可用时,可以无缝切换到另一个提供商。
- 透明性:AI 执行的 Shell 命令、修改的文件列表,用户必须能看见并干预。
- 交互自然:对话可以在 Emacs buffer 中滚动,代码片段可以一键插入当前文件。
Agent-shell 正是围绕这些目标设计的。它不仅仅是又一个“ChatGPT 封装”,而是一个定位更清晰的“Agent 会话外壳”。
2. Agent-shell 项目是什么
2.1 项目定位:厂商中立的 Agent 会话层
根据项目标题“Show HN: Agent-shell – vendor-neutral chat with AI agents in Emacs”,Agent-shell 的核心定位是:在 Emacs 内提供一个厂商中立的聊天与 Agent 交互环境。
英文里“vendor-neutral”是一个很关键的词。它的意思不是说项目不依赖任何 AI 厂商的大模型,而是指项目本身不绑定某个特定厂商。你可以通过配置文件接入不同的后端,甚至可以在同一场会话中切换不同的模型提供商。这有点像我们使用邮件客户端,既可以配置 Gmail,也可以配置企业邮箱,但客户端本身并不属于某一家邮件服务商。
Agent-shell 在气质上更像是轻量级 Shell 工具,而不是厚重的图形技术栈。它用 Emacs Lisp 编写,尽量保持简单、透明、可读,方便用户阅读源码和定制行为。
2.2 Agent-shell 与“AI Agent”的关系
很多读者会问:Agent-shell 和现在流行的 AI Agent 编程工具(如 Claude Code、OpenAI Codex CLI、开源的 Aider)有什么区别?
我认为可以这样理解:
- Aider 这类工具:目标是直接从命令行或者 IDE 内完成编码任务,它们有自己的 Agent 循环、文件修改策略、Git 操作逻辑。
- Agent-shell:更多是提供一个 Emacs 内的“会话入口”和“交互框架”。它负责把用户输入的指令发往后端模型,并把回答/工具调用结果展示在 Emacs 中。至于背后是调用通用对话模型,还是调用了具备 Function Calling 能力的 Agent API,这取决于你的后端配置。
因此在 Agent-shell 的体系里,你可以把它看作是一个 Emacs 前端 + 适配层。真正干活的可能是:
- OpenAI Assistant API
- Anthropic Claude API
- 本地部署的 Ollama 模型
- 各类兼容 OpenAI 格式的网关(例如 One API、New API 等)
- 甚至是你自己写的本地脚本
Agent-shell 不为你定义“Agent 是什么”,它只提供管道和界面,让你自由接入。
2.3 为什么强调 vendor-neutral
作为经常和多家 AI 服务打交道的开发者,我对“锁定”(Lock-in)这个词非常敏感。如果一家公司突然调整了定价、接口或地区策略,你手上的全部客户端配置都可能失效。而 vendor-neutral 的价值在于:
- 风险分散:多后端配置可以降低对单一服务商的依赖。
- 横向对比:可以快速在多个大模型之间测试同一个 prompt 的效果。
- 私有化部署:如果你所在公司要求数据不出内网,你可以把后端配置为内网部署的模型服务,而无须换掉编辑器工作流。
- 长期可维护:接口标准化后,即便某家厂商下线,只需要修改配置或写一个适配脚本。
正是因为这个特性,Agent-shell 对很多企业内开发者和注重隐私的独立开发者会很有吸引力。
3. 环境准备与版本说明
3.1 需要准备什么
要使用 Agent-shell,你需要准备以下环境:
- GNU Emacs:建议使用 Emacs 27.1 及以上版本。项目本身以 Elisp 为主,较新版本能更好地支持 JSON、线程等特性。如果使用 Windows 平台,建议使用 Emacs 28+ 或 WSL。
- 后端 AI 服务:可以是 OpenAI、Anthropic、本地 Ollama 或者任何兼容接口的服务地址。
- 网络访问:确保 Emacs 可以访问你配置的 API 地址。如果在公司内网办公,需要提前确认网络策略。
- curl 或 HTTP 客户端支持:部分功能可能依赖 curl,因为 Elisp 的 url-retrieve 在流式响应方面的表现不如 curl 直接。
具体版本以项目 README 为准,本文以常见环境为例,重点演示配置思路。
3.2 安装方式
目前比较推荐两类安装方式:
方式一:使用 package.el 安装
打开你的 Emacs 配置文件(通常是~/.emacs.d/init.el),添加仓库:
(require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize)然后在 Emacs 中执行:
M-x package-refresh-contents M-x package-install RET agent-shell如果包名还没有进入 MELPA,可以通过 Git 直接安装。
方式二:使用 use-package + git
(use-package agent-shell :load-path "~/projects/agent-shell/" :config (setq agent-shell-provider "openai"))从源码安装的好处是方便追踪更新,因为这类新兴项目迭代很快。建议把它 clone 到本地,比如:
git clone https://github.com/yourname/agent-shell.git ~/projects/agent-shell注意,我上面用的是示例仓库地址,具体的 Git 仓库地址请以M-x list-packages展示的源信息,或者 GitHub 上的 README 为准。
3.3 验证安装是否成功
安装完成后,在 Emacs 中执行:
M-x agent-shell-version如果能够看到类似0.1.0或者对应的版本字符串,说明基本安装成功。如果提示command not found,需要检查 load-path 是否包含项目目录。
4. Agent-shell 核心设计与配置拆解
4.1 核心抽象:Provider(提供商)
Agent-shell 把后端连接抽象成 Provider。Providers 本质上是一个存储 API 地址、密钥、模型名、请求头等信息的表。
你可以在配置文件中定义几个 provider,然后在对话时切换。示例配置如下:
(setq agent-shell-providers '( ("openai" :base-url "https://api.openai.com/v1" :api-key (lambda () (auth-source-pick-first-password :host "api.openai.com")) :model "gpt-4o-mini") ("local" :base-url "http://localhost:11434/v1" :api-key "ollama" :model "llama3.2") ("anthropic" :base-url "https://api.anthropic.com/v1" :api-key (lambda () (getenv "ANTHROPIC_API_KEY")) :model "claude-sonnet-4-20250514") ))这里有几个设计细节值得注意:
:api-key既可以是一个字符串,也可以是一个函数。这比直接把密钥硬写在配置文件里更安全。:base-url保留了足够的自由度,甚至你可以在本地跑一个 API 网关,把请求转发到多个上游。:model是默认模型,对话时你也可以手动覆盖。
不同厂商的 API 格式可能不同,Agent-shell 内部会做一次性归一化。如果遇到不兼容的返回结构,通常你会得到一个明确的 JSON 解析错误提示,而不是直接崩溃。
4.2 与 auth-source 集成管理密钥
在 Emacs 中管理 API 密钥,我强烈建议使用内置的auth-source机制。它可以从.authinfo、~/.authinfo.gpg、操作系统钥匙串中读取密钥,避免密钥出现在你的 dotfiles 仓库中。
先在~/.authinfo中添加一条记录:
machine api.openai.com login apikey password sk-xxxxxxx machine api.anthropic.com login apikey password sk-ant-xxxxx然后在配置中引用:
(setq agent-shell-api-key (lambda () (auth-source-pick-first-password :host "api.openai.com" :user "apikey")))如果你的 Emacs 配置本身就是公开仓库,这种“不要把密钥写进配置文件”的做法尤其重要。
4.3 流式输出与系统提示词
大模型对话最影响体验的就是输出方式。等模型全部生成完再返回,在 Emacs 中会感觉非常卡顿。Agent-shell 支持流式输出时,默认会在一个专门的 buffer 中实时刷新内容。
系统提示词可以通过agent-shell-system-prompt设置:
(setq agent-shell-system-prompt "You are a helpful assistant embedded in Emacs. Be concise, use code blocks when relevant, and never assume you have access to the user's file system.")这里强调的是:Agent-shell 把系统提示词视为纯文本转发,不做额外处理。大模型是否能正确遵守,取决于模型本身的能力。
4.4 会话管理机制
Agent-shell 支持同时打开多个会话。每一个会话对应一个独立的 Emacs buffer。这样的好处是你可以一边和“代码审查助手”对话,一边和“数据库调优专家”对话,互不干扰。
常见操作:
agent-shell-new-session:新建会话。agent-shell-clear-conversation:清空当前上下文,让模型忘记之前的对话历史。agent-shell-kill-session:关闭会话 buffer。
上下文长度方面,Agent-shell 不会自动做 token 截断。这意味着长对话会让请求越来越大,费用也可能增加。实际项目中,建议把“清空会话”绑定到一个快捷键上,以便及时重置。
5. 实战:在 Emacs 中完成一次 Agent 对话
接下来我们通过一个完整实战案例,把 Agent-shell 用起来。
场景假设:
- 本机已经安装 Emacs 29。
- 本地有 Ollama,默认模型为
qwen2.5:7b。 - 希望通过 Agent-shell 在 Emacs 中向这个模型提问并得到一个可插入文件的 Python 代码片段。
5.1 创建最小配置
编辑你的 init.el,加入如下配置:
;;; init.el (require 'agent-shell) (setq agent-shell-default-provider "local") (setq agent-shell-providers '(("local" :base-url "http://localhost:11434/v1" :api-key "ollama" :model "qwen2.5:7b"))) ;; 提供简单的快捷键 (global-set-key (kbd "C-c a s") #'agent-shell-new-session) (global-set-key (kbd "C-c a q") #'agent-shell-send-question)这里把默认 provider 设置为本地 Ollama,避免密钥问题。如果你的环境没有 Ollama,也可以用任意兼容 OpenAI 格式的服务,只需修改 base-url。
5.2 启动服务端
如果你使用 Ollama,在终端中运行:
ollama serve另开一个终端,确认模型已下载:
ollama pull qwen2.5:7b如果不想本地拉模型,也可以把 provider 换成云厂商,但需要注意 API 地址和密钥配置。
5.3 在 Emacs 中发起对话
重新加载配置后:
- 执行
M-x agent-shell-new-session,打开一个新的会话 buffer。 - 在 buffer 中输入一个问题,比如:
请用 Python 写一个函数,输入一个整数列表,返回其中所有偶数平方的和。- 执行
M-x agent-shell-send-question。 - 等待响应,正常情况下会在 buffer 中看到流式输出。
预期输出大致如下(实际内容取决于模型):
def sum_of_even_squares(numbers): return sum(x * x for x in numbers if x % 2 == 0)注意:如果模型返回 markdown 格式,Agent-shell 默认会把代码块原样展示,不会自动执行间接插入。若需要插入当前文件,可以选中代码块后使用 Emacs 的复制/插入命令,或者配置额外的代码块动作。
5.4 将代码插入当前文件
Agent-shell 的定位是“对话”,而不是完整的代码编辑 Agent,因此它本身可能不内置像 Aider 那样的文件修改逻辑。但我们可以借助 Emacs 的键盘宏或简单的 Elisp 函数来提升效率。
比如定义以下函数:
(defun my-agent-shell-insert-code-block () "提取当前 region 或 buffer 中的代码块并插入当前文件." (interactive) (let ((content (buffer-string))) (when (string-match "```\\([a-zA-Z0-9_+-]*\\)\n\\(.*?\\)```" content (or (and (region-active-p) (region-beginning)) 0)) (kill-new (match-string 2 content)) (message "代码块已复制,可在目标文件粘贴"))))这个函数做的事很简单:从当前 buffer 中提取第一个 markdown 代码块,复制到 kill ring。当然,这只是一个演示思路,实际项目可以扩展成自动创建文件、判断文件类型、插入到光标位置等更高级的行为。
5.5 运行与验证
为了验证对话效果,我们可以把返回的代码保存到临时文件并运行:
cat > /tmp/test.py << 'EOF' # 将从 Agent-shell 获取的代码粘贴到这里 EOF python3 /tmp/test.py输入测试数据:
print(sum_of_even_squares([1, 2, 3, 4, 5, 6]))期望输出是2*2 + 4*4 + 6*6 = 4 + 16 + 36 = 56。
到这里,一次完整的“Emacs 中与 AI Agent 对话”的流程就跑通了。
6. 常见问题与排查思路
实际使用 Agent-shell 时,很多人会遇到一些共性问题。下面用表格梳理一下排查建议:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
agent-shell-version提示找不到函数 | 项目未正确加载 | 检查 load-path 与 require 顺序,确认包名是否正确 |
| 发送问题后 buffer 长时间无输出 | 网络不通或后端地址错误 | 先用 curl 测试 API 地址是否可访问,再检查 base-url |
| 返回内容为 JSON 错误 | API 格式不兼容 | 查看后端返回的原始数据,确认是否使用 OpenAI 兼容格式 |
| 流式输出不生效 | 后端不支持 SSE,或 Emacs 内部请求方式限制 | 检查后端文档,必要时关闭流式输出选项 |
| 密钥提示错误 | API key 读取失败 | 确认 authinfo 配置,或者临时使用环境变量 |
| 切换 provider 不生效 | 只修改了列表但未刷新当前会话 | 新建会话或重启 Emacs |
| 请求上下文太长导致 token 超限 | 未清空历史消息 | 使用agent-shell-clear-conversation清理历史 |
| 公司网络有代理但未配置 | Emacs 请求未走代理 | 在配置中设置代理变量,例如url-proxy-services |
这里单独说一点:如果你遇到任何与 API 格式有关的报错,第一步一定要用 curl 直接观察原始响应。先排除后端服务问题,再排查 Emacs 配置问题。这样可以节省很多时间。
例如用 curl 测试 OpenAI 兼容端点:
curl http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5:7b", "messages": [{"role": "user", "content": "ping"}] }'如果 curl 返回正常 JSON,再回 Emacs 中排查配置。
7. 最佳实践与工程建议
最后聊一些比较深的使用建议。对一个要长期使用的 Emacs AI 客户端来说,“能跑起来”只是第一步,下面的工程实践会让你的体验稳健很多。
7.1 不把密钥写入配置文件
配置文件是你的 dotfiles 的一部分,随时可能同步到 GitHub 或企业内部代码仓库。API 密钥一旦泄露,轻则额度被盗刷,重则带来安全问题。推荐做法是:
- 优先使用环境变量。
- 使用 Emacs
auth-source。 - 高保密环境使用
~/.authinfo.gpg加密。
7.2 多 Provider 切换不要贪多
虽然 Agent-shell 支持多家厂商,但日常配置建议控制在 2 到 3 个 Provider。太多 provider 会带来上下文、模型名称、API 参数之间的混乱。每个模型的行为差异很大,你在某个模型下调试好的 prompt 换到另一个模型未必有效。
7.3 保持对话上下文边界清晰
Agent-shell 和很多 AI 客户端一样,采用“多轮消息”作为上下文。也就是说,之前的所有问题与回答都会在后续请求中发送。这会产生两个问题:
- token 消耗线性增长。
- 模型容易被远期对话带偏。
我的习惯是:每一轮独立任务结束后,主动发起新会话。特别是代码生成类任务,当前代码上下文可能对下一步有参考价值时再保留,否则就清空。
7.4 流式输出与网络中断处理
网络中断是常态。Agent-shell 只是客户端,无法保证底层连接始终可靠。建议:
- 在 Emacs 中使用
shr或markdown-mode渲染返回的 Markdown。 - 遇到中断,先复制已有输出,再重试。
- 长时间运行的重活,尽量放在具备断点续跑能力的外部工具中,而不是依赖 Emacs 内缓冲。
7.5 如何安全地让 Agent 执行命令
如果你基于 Agent-shell 二次开发,让它调用 Shell 命令,请务必注意命令执行的授权边界。一个粗粒度的安全设计如下:
- 遇到工具调用请求时,先展示将要执行的命令。
- 默认不自动执行,需要用户按 y 确认。
- 命令执行结果反馈给模型,但要注意过滤敏感输出。
- 对高危命令(如 rm -rf、DROP TABLE、git push --force)直接拒绝。
- 所有命令执行日志保存到单独的 buffer 文件,便于审计。
下面给一个示意函数:
(defun my-agent-shell-safe-shell (command) "安全地执行 shell 命令,要求用户确认。" (interactive "sShell command: ") (when (y-or-n-p (format "Execute [%s]? " command)) (shell-command-to-string command)))这只是一个非常朴素的安全层,但它体现了“用户必须知道 Agent 在做什么”的核心原则。
7.6 结合 Emacs 原生能力做后处理
Agent-shell 的输出通常是 Markdown 格式,包含代码块。你可以利用 Emacs 强大的文本处理能力做后续加工:
- 用
markdown-toggle-markup-hiding隐藏标记符号,获得更清爽的阅读体验。 - 用
org-mode直接把对话内容整理成工作日志。 - 用
tramp在远程服务器上打开文件,实现“远程开发中调用 AI 助手”。
这些组合玩法才是 Emacs 相比 VS Code、JetBrains 的独特优势所在。
7.7 关注上游更新
Agent-shell 这类项目迭代速度非常快。今天它是这样配置的,明天可能就改了 API。建议定期执行:
git pull并且留意上游 README 的变更记录。商业大模型 API 的版本兼容也是一个动态问题,不要把程序写死,依赖具体响应字段时要做好容错。
8. 总结与下一步方向
本文从 Emacs AI 生态的背景出发,介绍了 Agent-shell 作为“厂商中立的 Agent 会话工具”的定位和设计思路。在此基础上,我们完成了环境准备、配置编写、本地 Ollama 对话、代码块提取以及常见问题排查的完整流程。最后还聊到了密钥管理、多 Provider 切换、安全执行命令等工程实践。
如果你正在寻找一个不绑定特定厂商、可灵活切换的 Emacs AI 对话前端,Agent-shell 是一个值得关注的方向。它当前可能还比较年轻,核心代码量也不算大,但它的架构思路符合 Emacs 用户对“可配置、可审计、可替换”的长期偏好。
下一步,你可以从以下方向继续探索:
- 深入阅读 Agent-shell 源码,理解它如何处理流式输出和错误返回。
- 尝试把 Agent-shell 与
compile-mode、grep-mode结合,实现“报错 → 自动修复 → 重新编译”的半自动闭环。 - 对比 gptel、llm.el 等其他包,找出最适合你工作流的组合。
- 如果公司有内部模型网关,可以把网关地址配置成一个专属 provider,把 Emacs 变成内网大模型的一个优雅终端。
Emacs 的魅力在于无限的可塑性,而 AI Agent 的加入,又让这份可塑性上多了一层智能。希望这篇文章能给你带来一些实用的参考,也欢迎你在配置过程中多尝试、多调试,最终打磨出一套属于你自己的 Emacs AI 工作流。
如果文章对你有帮助,可以收藏备用,后续用到时直接照着配置。