让大模型真正“活”在你电脑里

——CogitoAgent开发实战(前言)

📖 本文是专栏《让大模型真正“活”在你电脑里——CogitoAgent开发实战》的开篇前言。将从技术路线、架构设计、核心实现、技术栈、目标人群五个维度,深度剖析这个项目的全貌。

效果


📌 写在前面:一个痛点催生的项目

使用 ChatGPT 或各类 AI 助手时,你是否反复遇到过这些困境:

困境一:文件传输的尴尬

“帮我总结一下这份 PDF。”——你先得把文件上传到云端。敏感文档你敢传吗?不敢。但不上传,AI 就看不见。

困境二:对话的“失忆症”

每次打开都是新对话,刚才聊到哪了?AI 不记得。想让 AI 持续跟踪一个事情?做不到。

困境三:AI 没有“主动性”

你问一句,它答一句。它不会自己发现:“咦,这个文件夹里有个 README,我看看里面写了什么。”

困境四:能力边界太窄

AI 只能输出文字。让它帮你复制个文件?不行。让它打开浏览器搜一下?不行。

这些痛点指向同一个结论:

现有的 AI 对话产品,是一个 “被动响应的文本处理器” ,而不是一个 “主动行动的智能体”

CogitoAgent 正是为此而生——它不是又一个聊天机器人,而是一个真正活在你电脑里的智能体


🎯 项目定位:一个“活”的 AI 长什么样?

一句话定义

CogitoAgent 是一款运行于本地的自主 AI 智能体,具备持续思考、主动探索、工具执行三大核心能力,在保障数据绝对隐私的前提下,提供持久化的智能助理服务。

核心理念三支柱

支柱 技术含义 解决的痛点
本地优先 文件操作使用本地 fs 模块,对话历史存于 data/conversation.json,不上传任何用户数据 数据隐私泄露
持续思考 setTimeout 递归循环 + 双状态机,每 3 秒自动触发一次 thinkCycle() AI 缺乏主动性,每次对话都是“一次性”
主动探索 LLM 自主决策调用 ls()read() 等工具,无需用户逐一下达指令 手动上传文件、逐条指令交互繁琐

与 ChatGPT 的本质区别

ChatGPT 模式:
用户 → 上传文件 → 提问 → 回答 → 结束(无状态)

CogitoAgent 模式:
启动 → AI 自主 ls() 探索 → read() 理解 → 发现有趣内容 → 
   ├─ 主动分享给用户(带 [WAIT] 等待回复)
   └─ 自主执行操作(copy/mkdir/search 等,无需等待)

🧠 技术路线:五大核心机制

一、双状态机驱动的智能体循环

这是 CogitoAgent 最核心的设计。整个系统只有两个状态:

const STATE = {
  THINKING: 'THINKING',           // AI 独自思考、探索、执行
  AWAITING_INPUT: 'AWAITING_INPUT' // 暂停思考,等待用户输入
};

状态流转图:

启动

无[WAIT]且无用户打断

输出[WAIT]标签

用户按Enter打断

用户发送消息

用户按Enter(空消息)

THINKING

AWAITING_INPUT

每3秒自动触发thinkCycle()

完全停止思考,倾听用户

为什么需要双状态?

如果只有一个“运行”状态,用户输入和 AI 思考会冲突:

  • 用户打字时,AI 同时在想,产生竞态
  • 用户想问问题,AI 还在自顾自地执行工具

双状态解决了这个问题:AWAITING_INPUT 状态下,思考循环完全停止,用户输入完成后才切回 THINKING

调度实现:

// 递归调度,实现持续运行
function scheduleNextCycle() {
  clearTimeout(thinkingTimer);
  thinkingTimer = setTimeout(async () => {
    if (state === STATE.THINKING) {   // 只有 THINKING 状态才执行
      await thinkCycle();
      scheduleNextCycle();            // 递归调用,永不停止
    }
  }, 3000);  // 思考间隔 3 秒
}

用户按 Enter 打断的实现:

function handleUserInput(input) {
  if (state === STATE.THINKING) {
    shouldStop = true;           // 设置中断标志
    clearTimeout(thinkingTimer); // 清除定时器
    state = STATE.AWAITING_INPUT; // 切换到等待状态
  }
}

二、基于标记的工具调用协议

CogitoAgent 没有使用 OpenAI 的 Function Calling(因为需要特定的 API 支持和复杂的参数 schema),而是设计了一套纯文本标记协议

协议格式:

[TOOL] toolName("参数1", "参数2") [/TOOL]

示例:

[TOOL] ls("src") [/TOOL]
[TOOL] read("README.md") [/TOOL]
[TOOL] search("2025年人工智能发展趋势") [/TOOL]
[TOOL] create("notes/hello.txt", "Hello World") [/TOOL]

解析实现:

// 支持单条响应中的多个工具调用
function parseAllToolCalls(text) {
  const results = [];
  const regex = /\[TOOL\]\s*(\w+)\s*\(([^)]*)\)\s*\[\/TOOL\]/g;
  let match;
  while ((match = regex.exec(text)) !== null) {
    const tool = match[1];
    const argsStr = match[2];
    const args = parseArgs(argsStr);  // 解析参数,支持引号包裹
    results.push({ tool, args });
  }
  return results;
}

// 参数解析:支持带引号和不带引号
function parseArgs(argsStr) {
  const result = [];
  const parts = argsStr.split(',');
  for (const part of parts) {
    const trimmed = part.trim();
    if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
        (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
      result.push(trimmed.slice(1, -1));
    } else {
      result.push(trimmed);
    }
  }
  return result;
}

工具执行分发:

async function executeTool(tool, args) {
  switch (tool) {
    case 'ls':        return await ls(args[0]);
    case 'read':      return await read(args[0]);
    case 'copy':      return await copy(args[0], args[1]);
    case 'mkdir':     return await mkdir(args[0]);
    case 'create':    return await create(args[0], args[1]);
    case 'search':    return await search(args.join(','));
    case 'browse':    return await browse(args[0]);
    case 'fetchPage': return await fetchPage(args[0]);
    case 'listApps':  return await listApps();
    case 'openApp':   return await openApp(args[0]);
    case 'closeApp':  return await closeApp(args[0]);
    default:          return { success: false, error: `未知工具: ${tool}` };
  }
}

为什么不用 Function Calling?

对比项 Function Calling 标记协议
API 兼容性 仅 OpenAI 格式 任何 LLM 都支持
参数 schema 需要 JSON 定义 自然语言/简单字符串
多工具调用 需要特殊处理 正则匹配即可
调试难度 黑盒 纯文本,一目了然
本项目选择

三、流式响应与分区展示

LLM 的响应是流式返回的,CogitoAgent 将流分成三个通道分别处理。

API 返回的 chunk 结构:

{
  content: "这是正常的回复内容",      // 正文
  reasoning: "让我想想应该怎么回答..." // 思考过程(DeepSeek 等模型支持)
}

分区展示策略:

// 状态标志
let reasoningTagPrinted = false;  // 思考区是否已开启
let contentTagPrinted = false;    // 正文区是否已开启

function printReasoning(text) {
  if (!reasoningTagPrinted) {
    println('\n┌─ 思考过程 ─────────────────────────────', 'darkGreen');
    reasoningTagPrinted = true;
  }
  print(text, 'darkGreen');  // 灰色/暗绿色输出
}

function printContent(text) {
  if (!contentTagPrinted) {
    // 如果思考区开着,先关闭它
    if (reasoningTagPrinted) {
      println('\n└──────────────────────────────────────────', 'dim');
      reasoningTagPrinted = false;
    }
    println('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'cyan');
    println('           ▼ 回复内容 ▼', 'bold');
    contentTagPrinted = true;
  }
  print(text);  // 正常颜色输出
}

效果演示:

┌─ 思考过程 ─────────────────────────────
用户让我查看 src 目录,我需要先 ls 一下
└──────────────────────────────────────────

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
           ▼ 回复内容 ▼
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
我来看看 src 目录下有什么:

  ┌─── 工具调用 ────────────────────────
  │ [TOOL] ls("src") [/TOOL]
  └────────────────────────────────────────

[工具结果]: Agent/ api/ io/ config.js setup.js

工具调用的低调展示:

function printToolBlock(content, title = '工具调用') {
  println('\n  ┌─── ' + title + ' ────────────────────────', 'dim');
  const lines = content.split('\n');
  for (const line of lines) {
    println('  │ ' + line, 'dim');
  }
  println('  └────────────────────────────────────────', 'dim');
}

这样设计的意图:

  • 思考过程:灰色,折叠感,让用户知道 AI 在想什么但不干扰阅读
  • 正文回复:正常颜色,醒目分隔
  • 工具调用:灰色小框,缩进,不喧宾夺主

四、智能记忆压缩机制

长对话会导致 token 爆炸。CogitoAgent 设置了 150 轮自动压缩 的策略。

触发判断:

const COMPRESS_TURNS = 150;
let turnCount = 0;  // user + assistant 消息总数

function shouldCompress() {
  return turnCount >= COMPRESS_TURNS;
}

压缩执行:

function compressHistory() {
  // 构造总结提示词
  const summaryPrompt = `请总结以下对话的核心内容,保留:
1. 智能体已发现的重要文件/目录
2. 已执行的重要操作
3. 当前的工作状态和上下文

对话记录:
${conversationHistory.map(m => `${m.role}: ${m.content}`).join('\n')}`;

  // 重建历史:系统提示 + 总结(作为 user 消息)
  conversationHistory = [
    { role: 'system', content: buildSystemPrompt() },
    { role: 'user', content: summaryPrompt }
  ];
  turnCount = 1;  // 重置计数
  saveHistory();
}

压缩前后对比:

指标 压缩前 压缩后
消息条数 150+ 2
token 占用 数万 ~ 数十万 ~2000
上下文连贯性 完整但昂贵 精简但保留核心

注意:当前压缩后会丢失细节,但保留了 AI 需要知道的“关键事实”。这是一个权衡:token 成本 vs 信息完整度。


五、工作区隔离与路径安全

CogitoAgent 的核心承诺是“不越权访问”。实现方式是通过一个工作区根路径限制所有操作。

配置存储:

{
  "workspace": "D:\\my-project\\"
}

路径解析(当前实现):

import { getBasePath } from './tools/path.js';

function resolvePath(targetPath) {
  const basePath = getBasePath();
  const fullPath = path.isAbsolute(targetPath) 
    ? targetPath                    // 绝对路径:直接使用
    : path.join(basePath, targetPath);  // 相对路径:拼接到工作区
  return fullPath;
}

安全性分析:

场景 输入 解析结果 是否安全
相对路径 docs/readme.txt D:\my-project\docs\readme.txt ✅ 安全
工作区内绝对路径 D:\my-project\src\index.js D:\my-project\src\index.js ✅ 安全
工作区外绝对路径 C:\Windows\System32\config C:\Windows\System32\config 逃逸!
路径遍历 ../secret/password.txt D:\my-project\..\secret\password.txt = D:\secret\password.txt 逃逸!

⚠️ 当前版本存在路径逃逸风险。用户可传入工作区外的绝对路径或 ../ 遍历到上级目录。这是已知问题,将在后续版本修复。建议用户信任自己配置的工作区路径。

计划中的修复方案:

// 严格路径限制
function secureResolvePath(targetPath) {
  const basePath = path.resolve(getBasePath());
  const fullPath = path.resolve(basePath, targetPath);
  
  // 关键检查:解析后的路径必须以 basePath 开头
  if (!fullPath.startsWith(basePath)) {
    throw new Error(`路径越权: ${targetPath} 不在工作区内`);
  }
  return fullPath;
}

🏗️ 架构全景解析

目录结构

CogitoAgent/
├── src/
│   ├── index.js                 # 入口:检查配置 → 启动/引导
│   ├── config.js                # 配置读写、默认值合并
│   ├── setup.js                 # 首次运行交互式引导
│   ├── agent/
│   │   ├── Agent.js             # 核心:状态机 + 思考循环
│   │   ├── prompt.js            # 系统提示 + 历史管理 + 压缩
│   │   └── tools/
│   │       ├── index.js         # 工具统一导出
│   │       ├── path.js          # 工作区路径获取
│   │       ├── file.js          # 文件操作:ls/read/copy/mkdir/create
│   │       ├── web.js           # 联网操作:search/browse/fetchPage
│   │       ├── system.js        # 系统操作:listApps/openApp/closeApp
│   │       └── TOOL_DEVELOPMENT.md  # 工具扩展指南
│   ├── api/
│   │   ├── client.js            # OpenAI SDK 封装 + 流式调用
│   │   └── webSearch.js         # 搜索 API 封装
│   └── io/
│       └── terminal.js          # 终端 UI:彩色输出、交互、标签管理
├── personas/                    # 13 种预设人设
├── data/
│   └── conversation.json        # 对话历史持久化
├── persona.md                   # 当前激活的人设
├── config.json                  # 用户配置
└── package.json

模块职责详解

模块 文件 核心职责 关键导出
入口 src/index.js 检查配置完整性,决定进入引导还是启动 main()
配置 src/config.js 读取/保存 config.json,合并默认值 loadConfig(), saveConfig()
引导 src/setup.js 首次运行时收集 API 配置、工作区、人设 runSetup()
智能体 src/agent/Agent.js 状态机、思考循环、工具调度 start(), thinkCycle()
提示词 src/agent/prompt.js 动态构建系统提示、历史持久化、压缩 getMessages(), compressHistory()
文件工具 src/agent/tools/file.js 文件系统的增删改查 ls, read, copy, mkdir, create
联网工具 src/agent/tools/web.js 搜索、浏览、抓取 search, browse, fetchPage
系统工具 src/agent/tools/system.js Windows 软件/进程管理 listApps, openApp, closeApp
API 客户端 src/api/client.js OpenAI 兼容 API 流式调用 streamChat()
终端 UI src/io/terminal.js 彩色输出、交互、标签状态 printReasoning(), printContent()

数据流全景图

外部

工具层

API层

核心层

用户层

打断/输入

读取/写入

输出

调用

HTTP

工具调用

工具调用

工具调用

search

browse/fetchPage

入口层

index.js

isConfigured?

setup.js 引导配置

start.js 启动

👤 用户

Agent.js
状态机/思考循环

prompt.js
历史管理/压缩

terminal.js
UI输出

client.js
流式调用

webSearch.js
搜索API

file.js

web.js

system.js

LLM服务

文件系统

浏览器

操作系统


📚 完整技术栈清单

一、语言与运行时

技术 版本要求 使用范围 熟练度要求
Node.js 18+ 全项目运行环境 熟练(能运行、调试)
JavaScript (ES2020+) ES2020 全部源码 熟练(async/await、解构、模块)
npm 任意 依赖管理 基础(npm install

二、核心依赖库

依赖 版本 用途 需要掌握的程度
openai ^4.0.0 LLM API 调用(官方 SDK) 了解(已封装好)
cheerio ^1.2.0 网页解析(服务端 jQuery) 了解($('selector').text()
fs/promises 原生 文件系统操作 熟练掌握
path 原生 路径处理 熟练掌握
readline 原生 命令行交互 了解
child_process 原生 子进程(打开软件、执行命令) 了解(exec()

三、Node.js 原生模块掌握程度

模块 使用的 API 在本项目中的频率 必须掌握程度
fs/promises readdir, readFile, writeFile, copyFile, mkdir 极高(每个文件操作都用到) ⭐⭐⭐ 必须
path join, resolve, isAbsolute, dirname, extname 极高(路径解析无处不在) ⭐⭐⭐ 必须
readline createInterface, question, on('line') 中等(用户输入) ⭐⭐ 建议
child_process exec 低(仅系统工具) ⭐ 了解即可

四、不需要掌握的技术

降低学习门槛,以下技术本项目完全不涉及:

  • 前端框架:无 React、Vue、Angular
  • 数据库:无 SQL、NoSQL,历史存 JSON 文件
  • TypeScript:纯 JS 项目
  • 打包工具:无 Webpack、Vite、Rollup
  • Docker/容器化:直接运行
  • Web 服务器:无 Express、Koa
  • WebSocket:无实时双向通信
  • GraphQL:无

五、可选了解(扩展或修改时)

技术 场景 说明
PowerShell/批处理 修改 system.js 中的软件列表读取 目前用 Get-ItemProperty 读注册表
正则表达式 修改工具解析逻辑 目前用于解析 [TOOL]...[/TOOL]
ANSI 转义码 修改终端颜色输出 目前 terminal.js 已封装好
HTTP 协议细节 修改 fetchPagewebSearch 需要了解 headers、status code

六、开发环境建议

# 推荐工具
编辑器: VS Code(推荐安装 ESLint 插件)
调试: 内置 Node.js 调试器或 console.log
版本控制: Git

VS Code 推荐配置:

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "启动 CogitoAgent",
      "program": "${workspaceFolder}/src/index.js"
    }
  ]
}

👥 适合人群详解

第一类:Node.js 开发者(最核心目标人群)

背景画像

  • 熟悉 JavaScript/Node.js
  • 做过文件操作、命令行工具
  • 想学习 AI Agent 开发

能从本项目获得什么

学习点 在项目中的体现
状态机设计 STATE.THINKING / AWAITING_INPUT 双状态
异步递归调度 setTimeout + thinkCycle() 实现持续循环
工具系统架构 统一接口 {success, data/error} + 动态分发
流式数据处理 LLM 流式响应的 chunk 处理与 UI 分区
上下文管理 历史持久化 + 150 轮自动压缩
路径安全设计 工作区隔离(当前版有待加强)

第二类:AI 应用开发者

背景画像

  • 使用过 OpenAI API 或其他 LLM
  • 做过 Chatbot、RAG 等应用
  • 想了解 Agent 架构

能从本项目获得什么

学习点 本项目的实现
Function Calling 替代方案 [TOOL] 标记协议,比 JSON schema 更简单
长对话管理 压缩策略:保留关键信息,丢弃细节
人设系统 动态注入 persona.md 到系统提示词
工具执行反馈 结果回写到上下文,LLM 可以“看到”执行结果
主动打断机制 用户按 Enter 设置中断标志,LLM 调用可中止

对比学习:如果你用过 LangChain 或 LlamaIndex,CogitoAgent 展示了一个极简实现——没有复杂的框架抽象,2000 行代码完成完整 Agent。

第三类:开源贡献者

背景画像

  • 想参与真实开源项目
  • 关注 AI 工具生态
  • 有代码能力,愿意提交 PR

可以贡献的方向

方向 难度 说明
路径安全加固 ⭐⭐ 实现 secureResolvePath() 防止逃逸
跨平台支持(macOS/Linux) ⭐⭐⭐ 重写 system.js,使用不同命令
新增工具(rm/mv/find) TOOL_DEVELOPMENT.md 操作
搜索 API 抽象层 ⭐⭐ 支持多种搜索引擎
单元测试 ⭐⭐ 补充 Jest/Mocha 测试
日志系统 集成 winston/pino

第四类:普通用户(不懂代码也可以)

背景画像

  • 非技术人员
  • 有文件管理、整理的需求
  • 注重数据隐私

使用流程(完全不需要写代码):

  1. 安装 Node.js(官网下载,一路 Next)
  2. 克隆或下载项目
  3. 在项目目录打开命令行,执行 npm install
  4. 执行 npm start,跟随引导输入:
    • API Base URL(购买 API 服务商提供)
    • API Key
    • 模型名称
    • 工作区路径(如 D:\我的文档
    • 选择一个人设
  5. 完成后 AI 自动开始探索

日常使用

  • AI 会自动在工作区里探索、整理
  • 想让它做什么,直接打字说
  • 按 Enter 打断它当前思考,下达新指令
  • 输入 exit 退出

第五类:计算机专业学生

适合场景

  • 毕业设计参考(完整度足够)
  • 课程项目(AI、操作系统、软件工程)
  • 学习 Node.js 实战

可参考的毕设方向

  • 基于本项目的功能增强(如增加语音交互)
  • 人设系统的优化(让 AI 从对话中学习用户偏好)
  • 记忆机制的改进(向量数据库 + RAG)

不适合的人群

人群 原因
需要生产级安全的企业 路径逃逸问题未解决,不建议部署在重要环境
Mac/Linux 用户 系统工具(system.js)仅支持 Windows
不想自备 API Key 的用户 需要自行购买或使用开源模型部署
需要图形界面的用户 本项目是命令行工具(TUI)

📊 项目成熟度自评

维度 评分 详细说明
功能完整性 ⭐⭐⭐⭐ 文件操作完整,联网搜索可用,系统工具支持 Windows
代码质量 ⭐⭐⭐⭐ 模块化清晰,注释完整,命名规范
文档完善度 ⭐⭐⭐⭐⭐ README 详尽,工具开发指南完整,人设文件齐全
安全性 ⭐⭐⭐ 基础隔离有,但路径逃逸待修复
扩展性 ⭐⭐⭐⭐ 新增工具仅需 4 步,有完整指南
易用性 ⭐⭐⭐⭐ 首次运行引导式配置,交互友好
跨平台 ⭐⭐ 系统工具 Windows only,核心功能跨平台
测试覆盖 ⭐⭐ 目前无单元测试

🔗 开源仓库与参与方式

Gitee 仓库: https://gitee.com/cnt-code/cogito-agent

你可以做的事

角色 行动
用户 ⭐ Star 收藏,下载体验,提 Issue 反馈问题
开发者 🍴 Fork 仓库,提交 PR 修复 bug 或新增功能
技术博主 📝 写文章分享,引用本项目
产品经理 💡 提建议,讨论产品方向

💬 结语

CogitoAgent 不是一个完美的项目——它有已知的安全隐患,有平台限制,有未覆盖的测试。

但它展示了一个完整、可运行、可扩展的本地智能体应该是什么样子:

  • 一个持续运行的思考循环
  • 一套简单但够用的工具调用协议
  • 一个可定制的人设系统
  • 一种隐私优先的设计理念

这个专栏将一步步带你读懂它的每一行核心代码,理解每一个设计决策,学会如何扩展它、改进它、甚至重新实现它。

无论你是想学习 AI Agent 开发的程序员,还是想拥有一个本地智能体的普通用户,这里都有属于你的内容。

下一篇文章,我们将进入 Agent.js 的核心——状态机与思考循环的完整实现。


如果这篇文章对你有帮助,欢迎 ⭐Star 支持一下开源项目!

👉 https://gitee.com/cnt-code/cogito-agent 👈

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐