基于Claude与Docker构建个人AI代理:Nagi架构解析与实战部署
1. 项目概述:构建你的个人AI代理伙伴
如果你和我一样,每天需要在Slack、Discord和Asana这些协作工具之间来回切换,处理各种琐碎任务——比如从一堆发布说明里整理周报、自动生成项目更新、或者只是查个天气——那么你大概也幻想过能有个“数字助手”帮你把这些杂事都处理掉。Nagi(凪)就是这样一个项目,它不是一个简单的聊天机器人,而是一个能真正在后台为你“干活”的个人AI代理基盘。
“凪”在日语里是“风平浪静”的意思,这个名字非常贴切地描述了它的设计哲学:它不张扬,不刷存在感,只是在你需要的时候,安静地帮你把那些麻烦的“噪音”消除掉,让你的日常工作流恢复平静。它的核心思路很清晰: 将强大的Claude AI模型封装在独立的Docker容器中运行,然后通过你日常使用的消息渠道(Slack、Discord、Asana)来触发和交互 。你只需要像@同事一样@它,它就能在容器里执行代码、操作浏览器、调用API,完成一系列复杂任务,并把结果和过程清晰地反馈回对话线程。
这个项目最初是 NanoClaw 的一个“洁净室”重新实现。所谓洁净室,就是从零开始,只参考其概念和功能,而不直接复制代码,这保证了架构的现代性和可维护性。Nagi在此基础上,采用了 Turborepo单体仓库架构 、 插件化技能系统 和 基于依赖注入(DI)的设计 ,使得整个系统模块清晰,扩展起来非常顺手。无论是想增加对新聊天工具的支持,还是开发一个全新的AI技能,你都能找到清晰的路径。
简单来说,Nagi适合三类人:一是 厌倦了重复性手工操作的开发者或项目经理 ,希望将固定流程自动化;二是 对AI代理(AI Agent)实际落地感兴趣的技术爱好者 ,想有一个高质量、可自托管的研究和实践平台;三是 小型团队或个人 ,希望拥有一个私有的、能集成到现有工作流中的智能助手,而不想依赖那些云端SaaS服务,担心数据隐私和定制化问题。接下来,我会带你深入它的世界,看看如何从零开始,把它部署成你可靠的数字伙伴。
2. 核心架构与设计哲学解析
2.1 为什么选择容器化与消息驱动?
在深入代码之前,理解Nagi的几个核心设计选择至关重要,这能帮你明白它为何稳定,以及如何更好地使用它。
首先, 为什么所有AI代理都运行在Docker容器里? 这不仅仅是追赶技术潮流。AI任务,尤其是那些涉及代码执行、网页浏览或调用外部工具的任务,本质上是不可预测且资源密集的。一个写文件的脚本可能出错,一个网页爬虫可能陷入死循环。容器化提供了最强的 隔离性和安全性 。每个任务都在一个全新的、沙盒化的环境中运行,即使任务崩溃或行为异常,也不会污染宿主机或其他任务的环境。任务结束后,容器被销毁,所有临时状态随之清除,实现了“无状态”执行,这对于长期运行的代理服务来说至关重要。从运维角度看,这也简化了依赖管理——你不需要在主机上安装Python、Node.js或Chrome,所有依赖都封装在任务对应的Docker镜像里。
其次, 为什么采用消息通道(Slack, Discord, Asana)作为交互界面? 答案是为了 无缝集成 和 自然交互 。我们大部分的工作沟通已经发生在这些工具里。让AI代理也“生活”在同一个空间,消除了切换应用的心理负担和操作成本。你不需要打开一个新的网页或终端,只需要在正在进行的对话中@一下Nagi,就像你向同事提问一样自然。这种设计也带来了很好的 上下文感知能力 。例如,在Asana中,当你在某个任务的评论里@Nagi时,它能自动获取该任务的名称、描述和之前的评论历史作为对话背景,使得它的回复更具针对性和连续性。
2.2 单体仓库(Monorepo)与插件化系统
Nagi采用Turborepo管理其单体仓库结构。这意味着核心框架、各个渠道适配器(Adapter)、技能(Skill)以及Web仪表盘都放在同一个代码仓库中,但通过 pnpm workspace 进行逻辑隔离。
packages/
├── core/ # 核心框架:DI容器、任务调度、日志等
├── adapter-slack/ # Slack渠道集成
├── adapter-discord/# Discord渠道集成
├── adapter-asana/ # Asana渠道集成
├── skill-weather/ # 示例:查询天气技能
├── skill-web/ # 网页浏览与自动化技能
└── web-ui/ # 基于React的监控仪表盘
这种结构的优势非常明显:
- 代码共享与一致性 :所有包共享同一套代码质量工具(ESLint, Prettier, TypeScript配置),确保风格统一。
- 简化依赖管理 :内部包之间的引用变得直接,版本同步问题不复存在。
- 高效的构建与测试 :Turborepo可以智能地识别代码变更的影响范围,只重新构建和测试相关的包,极大提升了开发效率。
更重要的是其 插件化系统 。无论是“渠道适配器”(Channel Adapter)还是“技能”(Skill),都以插件形式存在。核心框架通过依赖注入容器来管理和装配这些插件。当你运行 /add-channel-slack 命令时,实际上是在向系统注册 SlackAdapter 插件;当你开发一个新技能(比如自动生成SQL语句),也只需要创建一个符合 Skill 接口的新包即可。这种设计使得系统极具弹性,社区可以轻松贡献新的适配器或技能,而无需修改核心代码。
2.3 任务执行流程与数据流
理解一次@请求是如何被处理的,有助于调试和开发。整个流程可以概括为以下几个阶段:
- 触发与接收 :你在Slack频道中发送“@ai 今天天气怎么样?”。Slack的Socket Mode(一种长连接模式,无需公网URL)将这条消息事件推送给Nagi的
SlackAdapter。 - 解析与路由 :
SlackAdapter解析消息,识别出触发指令(@ai)和文本内容(今天天气怎么样?)。它将这个请求包装成一个标准的AgentTask对象,发送到核心的TaskDispatcher(任务调度器)。 - 会话与上下文管理 :
TaskDispatcher检查是否存在与该Slack线程关联的进行中会话(Session)。如果没有,则创建一个新会话。会话对象会持续跟踪整个对话历史,这对于需要多轮交互的复杂任务至关重要。 - 技能匹配与执行 :调度器分析任务内容,通过关键词或意图识别,将其路由到最合适的技能(例如
skill-weather)。然后,它命令ContainerManager(容器管理器)启动一个专用于此任务的Docker容器。 - 容器内执行 :在容器内部,预装了Claude Code SDK和必要依赖的环境开始工作。Claude模型接收会话历史和当前请求,决定需要调用哪些工具(可能是查询天气的API)。工具调用的结果、模型的“思考过程”(Chain-of-Thought)、token消耗和估算成本,都会被实时记录。
- 响应与回调 :技能执行完毕后,生成的结果(可能是结构化数据或文本)被送回
TaskDispatcher。Dispatcher再通过最初接收任务的SlackAdapter,将回复发送回原来的Slack线程。同时,所有的执行日志、token用量和成本数据会被持久化到数据库(如SQLite),供Web仪表盘查看。 - 清理 :任务完成后,Docker容器被销毁,释放资源。
注意 :Asana渠道的处理略有不同。为了不弄乱主任务,Nagi会在你@它的评论下,自动创建一个子任务(Subtask),并将所有的思考和回复都放在这个子任务里。这样,主任务的时间线和评论区依然保持整洁,所有与AI的交互都被规整地收纳在子任务中,这个设计细节非常贴心。
3. 从零开始的详细部署与配置指南
纸上谈兵终觉浅,让我们动手把Nagi运行起来。我将以macOS/Linux环境为例,Windows用户使用WSL2可以获得最佳体验。
3.1 基础环境准备:不只是安装软件
Docker Desktop :这是基石。请务必从官网下载安装。安装后,建议进入设置(Preferences)> Resources,根据你的机器配置适当调整分配给Docker的CPU和内存(建议至少4核CPU、8GB内存),因为AI任务可能比较消耗资源。打开终端,运行 docker --version 和 docker compose version 确认安装成功。
Node.js与pnpm :Nagi要求Node.js >= 22版本。我强烈建议使用 nvm (Node Version Manager)来管理Node版本,这样可以轻松切换不同项目所需的环境。
# 安装nvm(如果尚未安装)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# 重新打开终端,或运行 source ~/.zshrc (或 ~/.bashrc)
# 安装并使用Node.js 22
nvm install 22
nvm use 22
node --version # 应显示 v22.x.x
# 安装pnpm
corepack enable
corepack prepare pnpm@latest --activate
pnpm --version # 应显示 v9.x.x
Claude Code设置 :这是Nagi的“大脑”驱动核心。你需要一个Claude.ai账号。访问 Claude Code ,这是一个专为编码优化的Claude界面。确保你已在账户设置中生成了API密钥(通常叫 CLAUDE_API_KEY )。虽然Nagi的快速启动说直接让Claude Code来 /setup ,但理解背后的手动步骤能让你在出问题时游刃有余。
3.2 项目初始化与依赖安装
首先,将代码克隆到本地。
git clone https://github.com/yukihirop/nagi.git
cd nagi
接下来是安装依赖。这里使用 pnpm ,因为它能很好地处理monorepo的依赖关系,并且安装速度更快。
pnpm install
这个过程可能会花费几分钟,因为它需要安装所有workspace内包的依赖。如果遇到网络问题,可以考虑配置npm镜像源,但 pnpm 有时对镜像支持不如 npm 完美,优先尝试稳定的网络环境。
关键一步:环境变量配置 。项目根目录下应该有一个 .env.example 文件。复制它并创建你自己的 .env 文件。
cp .env.example .env
现在,用你喜欢的编辑器打开 .env 文件。以下是一些最关键的配置项,你需要填入自己的信息:
# Claude API 配置 - 核心中的核心
CLAUDE_API_KEY=sk-ant-xxxx... # 你的Claude API密钥
CLAUDE_MODEL=claude-3-5-sonnet-20241022 # 推荐使用最新的Sonnet模型,能力更强
CLAUDE_BASE_URL=https://api.anthropic.com # 通常不需要改,除非你用代理
# 数据库配置(用于存储会话、任务记录)
DATABASE_URL="file:./data/nagi.db" # 使用SQLite,数据文件会保存在项目下的data目录
# 应用运行时配置
NODE_ENV=development
PORT=3000 # 核心API服务端口
UI_PORT=5174 # 前端仪表盘开发服务器端口
实操心得 :关于
CLAUDE_API_KEY,安全第一。千万不要把这个.env文件提交到Git仓库。确保.gitignore文件中包含了.env和data/目录。对于生产环境,你应该使用更安全的密钥管理服务,如AWS Secrets Manager或HashiCorp Vault。
3.3 使用Claude Code进行智能初始化
这是Nagi项目非常酷的一个特点:它用AI来辅助搭建AI代理。确保你的终端当前位于 nagi 项目根目录下,然后打开Claude Code界面。
在Claude Code的聊天输入框中,简单地输入命令:
/setup
Claude Code会读取项目的 claude.md (或类似)说明文件,理解当前项目的结构和依赖,然后开始自动执行一系列初始化操作。这个过程可能包括:
- 检查并安装缺失的全局或本地依赖。
- 运行数据库迁移(Migration)脚本来创建初始表结构。
- 构建(Build)必要的包。
- 甚至可能启动一个基础的Docker镜像构建。
如果 /setup 不成功怎么办? 这是新手最常见的问题。请按以下步骤手动检查:
- 检查Claude Code的上下文 :确认Claude Code的“工作区”或“当前目录”确实指向了
nagi文件夹。有时界面会漂移到别的目录。 - 查看错误信息 :仔细阅读Claude Code返回的错误输出。常见问题有:网络超时导致
pnpm install失败、Docker守护进程未运行、或者缺少某些系统级依赖(如git)。 - 手动执行关键步骤 :你可以尝试分步手动执行,这能帮你定位问题。
# 1. 确保Docker在运行 docker info # 2. 手动运行数据库迁移(如果项目提供了prisma) pnpm -F core db:push # 或 pnpm run db:migrate,具体看package.json脚本 # 3. 尝试构建核心包 pnpm -F core build
3.4 连接你的第一个消息渠道:以Slack为例
Nagi支持多个渠道,我们以最常用的Slack开始。在Slack上创建一个新的 Slack App 。
- 创建App :点击“Create New App”,选择“From scratch”,给你的App起个名,比如“My Nagi Assistant”,并选择要安装的工作区。
- 配置Socket Mode :在左侧菜单找到“Socket Mode”,开启它。这允许Nagi主动与Slack建立长连接,省去了配置公网URL和SSL证书的麻烦。开启后,你会获得一个
APP_TOKEN,它以xapp-开头。把这个令牌保存好。 - 获取Bot Token :在左侧菜单“OAuth & Permissions”中,将以下OAuth Scope添加到Bot Token Scopes:
-
app_mentions:read(读取提及消息) -
chat:write(发送消息) -
channels:history(读取频道历史,用于获取上下文,可选但推荐) -
groups:history(同上,用于私密频道) -
im:history(同上,用于直接消息) 添加后,点击页面顶部的“Install to Workspace”,授权安装。完成后,你会获得一个BOT_TOKEN,它以xoxb-开头。
-
- 启用事件订阅(Event Subscriptions) :在左侧菜单“Event Subscriptions”中,开启事件。在“Subscribe to bot events”下,添加
app_mention事件。这样,当有人@你的Bot时,Slack才会通知你的应用。 - 回到Nagi项目 :现在,在Nagi项目根目录下,运行Slack渠道的添加命令。这通常是通过一个内置的CLI工具完成的。
根据提示,依次输入你刚才获取的pnpm cli add-channel-slackAPP_TOKEN和BOT_TOKEN。CLI工具会帮你完成与Slack App的握手验证,并将渠道配置保存到数据库中。 - 测试 :回到你的Slack工作区,在任意频道或直接消息中,尝试@你的Bot(它的名字默认是“ai”,你可以在Slack App设置里改),然后输入一些简单指令,比如
help。如果一切顺利,你应该能收到回复。
注意事项 :Slack的令牌权限非常重要。
APP_TOKEN仅用于建立Socket Mode连接,而BOT_TOKEN用于代表Bot执行操作(如发消息)。务必妥善保管,不要泄露。如果怀疑泄露,应立即在Slack App设置中重新生成。
4. 核心功能实操:技能开发与任务执行
4.1 内置技能体验与原理
Nagi自带了一些示例技能,让我们通过它们来理解技能是如何工作的。以查询天气为例,当你发送“@ai 北京天气怎么样?”时,背后发生了:
- 意图识别 :核心框架会遍历所有已注册的技能,调用每个技能的
canHandle方法。skill-weather的canHandle方法可能通过正则表达式(如/天气|weather/i)或更复杂的NLP逻辑来判断它是否应该处理这条消息。 - 技能执行 :如果匹配,则调用技能的
execute方法。该方法会接收到完整的会话上下文(Session)和当前任务(Task)。在execute方法内部:// 伪代码示意 async execute(session: Session, task: Task): Promise<SkillResult> { // 1. 从用户消息中提取城市名(可能用到Claude进行实体提取) const city = await extractCity(task.message); // 2. 构造给Claude的提示词(Prompt),明确告诉它要调用天气API工具 const prompt = `用户想知道${city}的天气。请调用get_weather工具。`; // 3. 通过Claude SDK发起对话,Claude会返回一个工具调用请求 const claudeResponse = await claude.messages.create({ model: this.model, messages: [...session.history, {role: 'user', content: prompt}], tools: [weatherToolDefinition] // 定义了get_weather工具的schema }); // 4. 执行Claude请求的工具调用(这里是调用真实的天气API) const weatherData = await callWeatherAPI(city); // 5. 将API结果返回给Claude,让它生成自然语言回复 const finalReply = await claude.messages.create({ // ... 附带上工具执行结果 }); // 6. 将最终回复、工具调用记录、token消耗封装成SkillResult返回 return new SkillResult(finalReply, toolCalls, tokenUsage); } - 结果呈现 :核心框架将
SkillResult中的自然语言回复发送回Slack。同时,如果配置了,它还会在回复中内联显示本次调用使用了哪些工具、消耗了多少token和估算成本(例如[工具: get_weather, Token: 245, 成本: ~$0.001]),这种透明化设计对于控制使用成本非常有用。
4.2 开发一个自定义技能:以“生成随机密码”为例
让我们动手创建一个全新的技能,加深理解。假设我们想要一个能生成指定长度和复杂度的随机密码的技能。
第一步:创建技能包结构 在 packages/ 目录下,新建一个文件夹 skill-password-generator 。
mkdir -p packages/skill-password-generator/src
cd packages/skill-password-generator
初始化 package.json :
{
"name": "@nagi/skill-password-generator",
"version": "0.1.0",
"main": "dist/index.js",
"types": "src/index.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"dependencies": {
"@nagi/core": "workspace:*" // 依赖核心框架
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
第二步:实现技能核心逻辑 创建 src/index.ts :
import { Skill, SkillContext, SkillResult, ToolCall } from '@nagi/core';
// 定义技能配置接口(如果需要从外部接收参数)
export interface PasswordSkillConfig {
defaultLength: number;
allowSpecialChars: boolean;
}
export class PasswordGeneratorSkill implements Skill {
name = 'password-generator';
description = '生成安全的随机密码。';
private config: PasswordSkillConfig;
constructor(config: Partial<PasswordSkillConfig> = {}) {
this.config = {
defaultLength: config.defaultLength || 12,
allowSpecialChars: config.allowSpecialChars ?? true,
};
}
// 关键方法:判断是否处理此消息
async canHandle(message: string): Promise<boolean> {
const triggers = ['密码', 'password', 'generate password', '随机密码'];
return triggers.some(trigger =>
message.toLowerCase().includes(trigger.toLowerCase())
);
}
// 关键方法:执行技能
async execute(context: SkillContext): Promise<SkillResult> {
const { task, session, logger } = context;
const userMessage = task.message;
// 1. 解析用户请求,提取参数(例如:长度、是否包含特殊字符)
// 这里可以简单使用正则,或者让Claude帮你解析
const lengthMatch = userMessage.match(/长度\s*(\d+)|(\d+)\s*位/);
const length = lengthMatch ? parseInt(lengthMatch[1] || lengthMatch[2]) : this.config.defaultLength;
const noSpecialMatch = userMessage.match(/不包含特殊字符|只用数字字母/);
const useSpecial = this.config.allowSpecialChars && !noSpecialMatch;
// 2. 生成密码的逻辑(纯函数,不依赖Claude)
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' +
(useSpecial ? '!@#$%^&*()_+-=[]{}|;:,.<>?' : '');
let password = '';
const crypto = require('crypto'); // 使用更安全的随机数生成器
for (let i = 0; i < length; i++) {
const randomIndex = crypto.randomInt(0, charset.length);
password += charset[randomIndex];
}
// 3. 构造回复。我们可以直接回复,也可以让Claude润色一下。
const replyText = `根据您的要求,生成了一个${length}位的${useSpecial ? '包含特殊字符的' : '仅字母数字的'}密码:\n\`${password}\`\n\n**请务必立即保存到安全的地方,此消息一旦关闭将无法再次查看。**`;
// 4. 记录工具调用(本例未调用外部API,但可以记录生成了密码)
const toolCalls: ToolCall[] = [{
id: 'generate_password',
name: 'generate_password',
input: { length, useSpecial },
output: { passwordGenerated: true },
cost: 0, // 无外部API成本
duration: 10 // 假设耗时10ms
}];
// 5. 返回结果
return new SkillResult({
text: replyText,
toolCalls,
tokenUsage: { input: 0, output: 0, total: 0 } // 未使用Claude,token为0
});
}
}
// 默认导出一个创建技能实例的函数,供DI容器使用
export default (config?: Partial<PasswordSkillConfig>) => new PasswordGeneratorSkill(config);
第三步:注册技能 我们需要告诉Nagi核心框架这个新技能的存在。通常,这通过在核心包的配置文件中导入并注册来完成。更模块化的方式是在技能包的 package.json 中定义一个 nagi-plugin 入口,或者在一个集中的技能注册表里添加。
假设项目有一个 skills/index.ts 文件来管理所有技能:
// 在核心包或某个配置文件中
import passwordSkill from '@nagi/skill-password-generator';
export const availableSkills = [
// ... 其他技能
passwordSkill({ defaultLength: 16 }), // 可以传入自定义配置
];
第四步:构建与测试
- 在技能目录下运行
pnpm build进行编译。 - 在项目根目录运行
pnpm build,确保所有包都被正确构建。 - 重启Nagi的服务进程。
- 在Slack中尝试发送“@ai 帮我生成一个16位的密码”或“@ai 生成一个不包含特殊字符的密码”。
4.3 通过CLI直接与代理交互
除了通过消息渠道,Nagi也提供了命令行界面(CLI),方便快速测试和调试,而无需打开Slack。
# 最基本用法:直接向代理提问
pnpm nagi "今天上海的天气如何?"
# 列出所有可用的技能
pnpm nagi --list
# 输出示例:
# Available Skills:
# - weather (查询天气)
# - web-browser (网页浏览)
# - password-generator (生成随机密码)
# - ai-changelog (汇总AI提供商更新日志)
# 指定使用某个技能(如果自动匹配不准确)
pnpm nagi --skill weather "北京明天会下雨吗?"
# 以JSON格式输出详细结果,用于调试
pnpm nagi --json "生成密码" | jq . # 需要安装jq工具来美化JSON输出
CLI模式背后,它依然会启动一个Docker容器来执行任务,并将结果输出到终端。这对于开发技能时的快速迭代测试非常有用,你可以立刻看到技能的输出,而不用等待消息渠道的往返。
5. Web仪表盘:监控与管理的控制中心
Nagi提供了一个现代化的Web仪表盘,让你能清晰地掌控所有AI代理的活动。启动它非常简单:
pnpm ui:dev
这条命令会同时启动两个服务:
- 前端SPA开发服务器 :运行在
http://localhost:5174,基于Vite + React 19 + Tailwind CSS 4,界面响应迅速。 - 后端API服务器 :运行在
http://localhost:3001,为前端提供数据,基于Hono框架(一个轻量级、快速的Web框架)。
仪表盘主要包含以下几个功能模块:
概览(Overview) 一进入就能看到关键指标的统计卡片:已注册的代理组(Groups)数量、活跃的消息渠道(Channels)连接数、正在执行或排队的任务(Tasks)数、历史会话(Sessions)总数以及最新的日志条目。这让你对系统整体状态一目了然。
渠道与组管理(Groups / Channels) 在这里,你可以看到所有已配置的消息渠道(如Slack Workspace “My Team”、Discord Server “Gaming Hub”)及其当前连接状态(在线、离线、错误)。你还可以创建逻辑上的“组”(Groups),将多个渠道归类,便于统一管理权限或技能分配。例如,你可以创建一个“开发组”,包含团队的Slack频道和Asana项目,然后只给这个组分配代码相关的技能。
会话浏览(Sessions) 这是最有用的功能之一。所有通过@触发的对话都会形成一个会话。在这里,你可以像查看聊天记录一样,回顾完整的对话过程。更强大的是,你可以 展开每一步的“思考过程” 。点击会话中的某条AI回复,你可以看到Claude在生成最终答案前,内部经历了怎样的推理链条(Chain-of-Thought),以及它具体调用了哪些工具、传入的参数是什么、返回的结果又是什么。这对于调试复杂技能的失败案例,或者单纯学习Claude的思考方式,都极具价值。
任务队列(Tasks) Nagi支持计划任务(Cron Jobs)。你可以在这里看到所有已配置的定时任务,例如“每天上午9点自动生成项目日报并发送到Slack”。可以查看它们的下次执行时间、历史执行记录和状态(成功/失败)。
日志查看器(Logs) 聚合了所有Docker容器的标准输出(stdout)和标准错误(stderr),以及核心框架的运行日志。你可以按日志级别(Info, Warn, Error)进行过滤,快速定位问题。当某个技能执行失败时,这里通常是排查的第一站。
设置(Settings) 目前主要是简单的主题切换(深色/浅色模式)。未来可能会增加更多个人化配置选项。
注意事项 :仪表盘默认可能没有启用身份验证。这意味着如果将其部署到公网(例如通过
ngrok暴露本地端口),任何人都可以访问你的任务历史和日志。 在将Nagi用于生产环境或处理敏感数据前,务必为仪表盘添加认证层 ,例如配置基础的HTTP认证,或将其置于一个需要VPN访问的内网之后。
6. 常见问题排查与性能优化实录
即使按照指南操作,也难免会遇到问题。下面是我在部署和使用Nagi过程中遇到的一些典型问题及解决方法。
6.1 连接类问题
问题:Slack Bot 无响应,仪表盘显示渠道离线。
- 检查Socket Mode连接 :首先确认在创建Slack App时正确启用了Socket Mode,并且
APP_TOKEN(xapp-开头)已正确配置到Nagi中。查看Nagi的后台日志,看是否有连接Slack API的错误信息。 - 检查Bot Token权限 :确保
BOT_TOKEN(xoxb-开头)已添加app_mentions:read和chat:write等必要权限,并且已经重新安装(Reinstall)了App到工作区。权限变更后必须重新安装才能生效。 - 检查防火墙/代理 :如果你的网络环境需要代理才能访问外部API,需要确保Docker容器内的进程也能使用代理。可以在Docker运行命令或
docker-compose.yml中设置环境变量HTTP_PROXY和HTTPS_PROXY。 - 查看容器日志 :运行
docker logs <nagi_container_id>查看负责Slack适配器的容器是否有报错。
问题:Claude API 调用返回权限错误或超时。
- 验证API密钥 :确认
.env文件中的CLAUDE_API_KEY正确无误,且没有多余的空格或换行。可以尝试在命令行用curl手动测试一下:curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $YOUR_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-3-haiku-20240307", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}] }' - 检查配额与账单 :登录Anthropic控制台,确认API密钥有效,且没有超出速率限制(Rate Limit)或使用额度已用完。
- 模型可用性 :确认你配置的
CLAUDE_MODEL(如claude-3-5-sonnet-20241022)是当前可用的模型名。模型列表可能会更新。
6.2 执行类问题
问题:技能执行失败,日志显示“Tool call failed”或“Container exited with code 1”。
- 技能逻辑错误 :这是最常见的原因。技能代码本身存在Bug。前往仪表盘的“Logs”页面或查看对应容器的日志,找到具体的错误堆栈信息。使用CLI模式单独测试该技能可以更快定位问题。
- 容器内依赖缺失 :技能可能需要特定的系统库或软件。检查该技能对应的Dockerfile,确保所有依赖都已正确安装。例如,网页浏览技能可能需要安装Chromium浏览器和无头模式的相关依赖。
- 资源不足 :复杂的任务(如大型语言模型推理、网页截图)可能消耗大量内存,导致容器被系统OOM(Out-Of-Memory)终止。尝试在
docker-compose.yml中为服务增加资源限制:services: nagi-core: # ... deploy: resources: limits: memory: 2G # 限制最大内存为2GB cpus: '1.0' # 限制使用1个CPU核心
问题:任务执行速度很慢。
- 优化Docker镜像 :确保技能使用的Docker镜像层是缓存的,并且尽可能小。使用多阶段构建,清理不必要的临时文件。
- 并行度限制 :默认配置可能限制了同时运行的任务容器数量。如果你有多个并发请求,可以调整核心调度器的并发设置(如果暴露了相关配置)。
- Claude模型选择 :对于不需要最强推理能力的简单任务(如文本格式化、简单提取),可以尝试使用更快、更便宜的模型,如
claude-3-haiku,并在技能代码中动态指定模型。
6.3 数据与状态管理问题
问题:重启后会话历史丢失。
- 确认数据库配置 :Nagi默认使用SQLite文件数据库(
./data/nagi.db)。确保运行Nagi的用户对该文件及其所在目录有读写权限。如果使用Docker Compose,检查数据卷(volume)映射是否正确,确保数据库文件被持久化在宿主机上,而不是在易失的容器内部。 - 检查数据库连接 :查看日志中是否有数据库连接错误。有时并发访问SQLite文件可能会出问题,对于更高负载的场景,可以考虑切换到PostgreSQL,并修改
DATABASE_URL环境变量。
问题:Web仪表盘无法加载或数据不更新。
- 检查API服务 :确保后端API服务(
localhost:3001)正在运行且可访问。打开浏览器开发者工具(F12),查看网络(Network)选项卡,前端请求API时是否返回错误(如404或500)。 - CORS问题 :如果前端和后端部署在不同域名或端口,可能会遇到CORS错误。需要在后端API服务器(Hono)中配置正确的CORS头。检查核心包的服务器启动代码。
6.4 成本控制与优化技巧
使用Claude API是会产生成本的。以下是一些控制成本的实用技巧:
- 设置预算与告警 :在Anthropic控制台设置每月预算和支出告警,避免意外费用。
- 利用会话上下文 :Nagi的会话管理会自动将历史消息作为上下文传递给Claude。对于多轮对话,这能提高效率,但也会增加token消耗。对于非常长的对话线程,考虑在技能逻辑中主动总结或清除早期历史。
- 选择合适模型 :
-
claude-3-haiku:最快、最便宜,适合简单分类、提取、格式化任务。 -
claude-3-sonnet:能力、速度和成本的平衡点,适合大多数通用任务和复杂指令。 -
claude-3-opus:能力最强,也最贵最慢,仅用于需要顶级推理能力的关键任务。 可以在技能级别或甚至任务级别通过配置动态选择模型。
-
- 监控Token用量 :充分利用Nagi仪表盘和消息内联的成本显示功能。定期查看哪些技能或对话消耗token最多,并思考是否有优化空间(例如,优化提示词、减少不必要的上下文)。
- 缓存策略 :对于重复性查询(如天气、汇率),可以在技能中实现简单的缓存机制,在一定时间内(如10分钟)直接返回缓存结果,避免重复调用Claude和外部API。
7. 进阶部署与生产环境考量
当你个人使用顺畅后,可能会想将其部署到一台长期运行的服务器上,供小团队使用。这时需要考虑更多生产环境的问题。
使用Docker Compose编排 项目根目录通常已经提供了 docker-compose.yml 示例。一个生产就绪的配置可能需要包含以下服务:
-
nagi-core: 核心API与调度服务。 -
nagi-ui: 前端仪表盘(可以构建为静态文件,用Nginx服务)。 -
postgres: 替代SQLite,用于生产数据库。 -
redis(可选): 用于缓存会话状态或任务队列,提升性能。 -
traefik或nginx: 作为反向代理,处理SSL/TLS证书和路由。
安全性加固
- 环境变量管理 :绝不将密钥硬编码或提交到代码库。使用Docker Secrets、云服务商的密钥管理服务,或至少是加密的
.env文件。 - 网络隔离 :将Nagi的服务放在独立的Docker网络或内部子网中,限制其对外部网络的访问(只允许访问必要的API,如Claude API、Slack API)。
- 容器安全 :确保使用的Docker基础镜像来自可信源,并及时更新。以非root用户运行容器进程。
- 仪表盘认证 :如前所述,为Web仪表盘添加登录认证。可以集成OAuth(如GitHub, Google),或使用简单的Basic Auth。
监控与告警
- 日志聚合 :将Docker容器的日志导出到集中式日志系统,如ELK Stack(Elasticsearch, Logstash, Kibana)或Grafana Loki,方便搜索和分析。
- 应用性能监控(APM) :集成像OpenTelemetry这样的工具,追踪请求链路、数据库查询性能和外部API调用耗时。
- 健康检查 :在
docker-compose.yml中为关键服务配置健康检查(healthcheck),并设置重启策略(restart: unless-stopped)。 - 备份 :定期备份数据库。如果使用PostgreSQL,可以设置
pg_dump定时任务。
技能商店与社区 Nagi的插件化架构为社区共享技能打开了大门。你可以设想一个未来的“技能商店”,开发者可以提交自己的技能包,其他人通过一行命令就能安装使用。要实现这一点,需要在核心框架中增加一个技能包管理器,能够从远程仓库(如GitHub或私有Registry)拉取和安装技能包,并处理版本依赖。这将是让Nagi生态繁荣的关键一步。
从个人自动化工具到团队协作基盘,Nagi提供了一个极具潜力的起点。它的价值不仅在于已经实现的功能,更在于其清晰、可扩展的架构,让你能够根据自己的需求,轻松地将其塑造成最适合你工作流的那个“风平浪静”的助手。
更多推荐
所有评论(0)