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的监控仪表盘

这种结构的优势非常明显:

  1. 代码共享与一致性 :所有包共享同一套代码质量工具(ESLint, Prettier, TypeScript配置),确保风格统一。
  2. 简化依赖管理 :内部包之间的引用变得直接,版本同步问题不复存在。
  3. 高效的构建与测试 :Turborepo可以智能地识别代码变更的影响范围,只重新构建和测试相关的包,极大提升了开发效率。

更重要的是其 插件化系统 。无论是“渠道适配器”(Channel Adapter)还是“技能”(Skill),都以插件形式存在。核心框架通过依赖注入容器来管理和装配这些插件。当你运行 /add-channel-slack 命令时,实际上是在向系统注册 SlackAdapter 插件;当你开发一个新技能(比如自动生成SQL语句),也只需要创建一个符合 Skill 接口的新包即可。这种设计使得系统极具弹性,社区可以轻松贡献新的适配器或技能,而无需修改核心代码。

2.3 任务执行流程与数据流

理解一次@请求是如何被处理的,有助于调试和开发。整个流程可以概括为以下几个阶段:

  1. 触发与接收 :你在Slack频道中发送“@ai 今天天气怎么样?”。Slack的Socket Mode(一种长连接模式,无需公网URL)将这条消息事件推送给Nagi的 SlackAdapter
  2. 解析与路由 SlackAdapter 解析消息,识别出触发指令( @ai )和文本内容( 今天天气怎么样? )。它将这个请求包装成一个标准的 AgentTask 对象,发送到核心的 TaskDispatcher (任务调度器)。
  3. 会话与上下文管理 TaskDispatcher 检查是否存在与该Slack线程关联的进行中会话( Session )。如果没有,则创建一个新会话。会话对象会持续跟踪整个对话历史,这对于需要多轮交互的复杂任务至关重要。
  4. 技能匹配与执行 :调度器分析任务内容,通过关键词或意图识别,将其路由到最合适的技能(例如 skill-weather )。然后,它命令 ContainerManager (容器管理器)启动一个专用于此任务的Docker容器。
  5. 容器内执行 :在容器内部,预装了Claude Code SDK和必要依赖的环境开始工作。Claude模型接收会话历史和当前请求,决定需要调用哪些工具(可能是查询天气的API)。工具调用的结果、模型的“思考过程”(Chain-of-Thought)、token消耗和估算成本,都会被实时记录。
  6. 响应与回调 :技能执行完毕后,生成的结果(可能是结构化数据或文本)被送回 TaskDispatcher Dispatcher 再通过最初接收任务的 SlackAdapter ,将回复发送回原来的Slack线程。同时,所有的执行日志、token用量和成本数据会被持久化到数据库(如SQLite),供Web仪表盘查看。
  7. 清理 :任务完成后,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 不成功怎么办? 这是新手最常见的问题。请按以下步骤手动检查:

  1. 检查Claude Code的上下文 :确认Claude Code的“工作区”或“当前目录”确实指向了 nagi 文件夹。有时界面会漂移到别的目录。
  2. 查看错误信息 :仔细阅读Claude Code返回的错误输出。常见问题有:网络超时导致 pnpm install 失败、Docker守护进程未运行、或者缺少某些系统级依赖(如 git )。
  3. 手动执行关键步骤 :你可以尝试分步手动执行,这能帮你定位问题。
    # 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

  1. 创建App :点击“Create New App”,选择“From scratch”,给你的App起个名,比如“My Nagi Assistant”,并选择要安装的工作区。
  2. 配置Socket Mode :在左侧菜单找到“Socket Mode”,开启它。这允许Nagi主动与Slack建立长连接,省去了配置公网URL和SSL证书的麻烦。开启后,你会获得一个 APP_TOKEN ,它以 xapp- 开头。把这个令牌保存好。
  3. 获取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- 开头。
  4. 启用事件订阅(Event Subscriptions) :在左侧菜单“Event Subscriptions”中,开启事件。在“Subscribe to bot events”下,添加 app_mention 事件。这样,当有人@你的Bot时,Slack才会通知你的应用。
  5. 回到Nagi项目 :现在,在Nagi项目根目录下,运行Slack渠道的添加命令。这通常是通过一个内置的CLI工具完成的。
    pnpm cli add-channel-slack
    
    根据提示,依次输入你刚才获取的 APP_TOKEN BOT_TOKEN 。CLI工具会帮你完成与Slack App的握手验证,并将渠道配置保存到数据库中。
  6. 测试 :回到你的Slack工作区,在任意频道或直接消息中,尝试@你的Bot(它的名字默认是“ai”,你可以在Slack App设置里改),然后输入一些简单指令,比如 help 。如果一切顺利,你应该能收到回复。

注意事项 :Slack的令牌权限非常重要。 APP_TOKEN 仅用于建立Socket Mode连接,而 BOT_TOKEN 用于代表Bot执行操作(如发消息)。务必妥善保管,不要泄露。如果怀疑泄露,应立即在Slack App设置中重新生成。

4. 核心功能实操:技能开发与任务执行

4.1 内置技能体验与原理

Nagi自带了一些示例技能,让我们通过它们来理解技能是如何工作的。以查询天气为例,当你发送“@ai 北京天气怎么样?”时,背后发生了:

  1. 意图识别 :核心框架会遍历所有已注册的技能,调用每个技能的 canHandle 方法。 skill-weather canHandle 方法可能通过正则表达式(如 /天气|weather/i )或更复杂的NLP逻辑来判断它是否应该处理这条消息。
  2. 技能执行 :如果匹配,则调用技能的 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);
    }
    
  3. 结果呈现 :核心框架将 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 }), // 可以传入自定义配置
];

第四步:构建与测试

  1. 在技能目录下运行 pnpm build 进行编译。
  2. 在项目根目录运行 pnpm build ,确保所有包都被正确构建。
  3. 重启Nagi的服务进程。
  4. 在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

这条命令会同时启动两个服务:

  1. 前端SPA开发服务器 :运行在 http://localhost:5174 ,基于Vite + React 19 + Tailwind CSS 4,界面响应迅速。
  2. 后端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是会产生成本的。以下是一些控制成本的实用技巧:

  1. 设置预算与告警 :在Anthropic控制台设置每月预算和支出告警,避免意外费用。
  2. 利用会话上下文 :Nagi的会话管理会自动将历史消息作为上下文传递给Claude。对于多轮对话,这能提高效率,但也会增加token消耗。对于非常长的对话线程,考虑在技能逻辑中主动总结或清除早期历史。
  3. 选择合适模型
    • claude-3-haiku :最快、最便宜,适合简单分类、提取、格式化任务。
    • claude-3-sonnet :能力、速度和成本的平衡点,适合大多数通用任务和复杂指令。
    • claude-3-opus :能力最强,也最贵最慢,仅用于需要顶级推理能力的关键任务。 可以在技能级别或甚至任务级别通过配置动态选择模型。
  4. 监控Token用量 :充分利用Nagi仪表盘和消息内联的成本显示功能。定期查看哪些技能或对话消耗token最多,并思考是否有优化空间(例如,优化提示词、减少不必要的上下文)。
  5. 缓存策略 :对于重复性查询(如天气、汇率),可以在技能中实现简单的缓存机制,在一定时间内(如10分钟)直接返回缓存结果,避免重复调用Claude和外部API。

7. 进阶部署与生产环境考量

当你个人使用顺畅后,可能会想将其部署到一台长期运行的服务器上,供小团队使用。这时需要考虑更多生产环境的问题。

使用Docker Compose编排 项目根目录通常已经提供了 docker-compose.yml 示例。一个生产就绪的配置可能需要包含以下服务:

  • nagi-core : 核心API与调度服务。
  • nagi-ui : 前端仪表盘(可以构建为静态文件,用Nginx服务)。
  • postgres : 替代SQLite,用于生产数据库。
  • redis (可选): 用于缓存会话状态或任务队列,提升性能。
  • traefik nginx : 作为反向代理,处理SSL/TLS证书和路由。

安全性加固

  1. 环境变量管理 :绝不将密钥硬编码或提交到代码库。使用Docker Secrets、云服务商的密钥管理服务,或至少是加密的 .env 文件。
  2. 网络隔离 :将Nagi的服务放在独立的Docker网络或内部子网中,限制其对外部网络的访问(只允许访问必要的API,如Claude API、Slack API)。
  3. 容器安全 :确保使用的Docker基础镜像来自可信源,并及时更新。以非root用户运行容器进程。
  4. 仪表盘认证 :如前所述,为Web仪表盘添加登录认证。可以集成OAuth(如GitHub, Google),或使用简单的Basic Auth。

监控与告警

  1. 日志聚合 :将Docker容器的日志导出到集中式日志系统,如ELK Stack(Elasticsearch, Logstash, Kibana)或Grafana Loki,方便搜索和分析。
  2. 应用性能监控(APM) :集成像OpenTelemetry这样的工具,追踪请求链路、数据库查询性能和外部API调用耗时。
  3. 健康检查 :在 docker-compose.yml 中为关键服务配置健康检查( healthcheck ),并设置重启策略( restart: unless-stopped )。
  4. 备份 :定期备份数据库。如果使用PostgreSQL,可以设置 pg_dump 定时任务。

技能商店与社区 Nagi的插件化架构为社区共享技能打开了大门。你可以设想一个未来的“技能商店”,开发者可以提交自己的技能包,其他人通过一行命令就能安装使用。要实现这一点,需要在核心框架中增加一个技能包管理器,能够从远程仓库(如GitHub或私有Registry)拉取和安装技能包,并处理版本依赖。这将是让Nagi生态繁荣的关键一步。

从个人自动化工具到团队协作基盘,Nagi提供了一个极具潜力的起点。它的价值不仅在于已经实现的功能,更在于其清晰、可扩展的架构,让你能够根据自己的需求,轻松地将其塑造成最适合你工作流的那个“风平浪静”的助手。

更多推荐