让大模型真正“活”在你电脑里 ——CogitoAgent开发实战(前言)
让大模型真正“活”在你电脑里
——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' // 暂停思考,等待用户输入
};
状态流转图:
为什么需要双状态?
如果只有一个“运行”状态,用户输入和 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() |
数据流全景图
📚 完整技术栈清单
一、语言与运行时
| 技术 | 版本要求 | 使用范围 | 熟练度要求 |
|---|---|---|---|
| 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 协议细节 | 修改 fetchPage 或 webSearch |
需要了解 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 |
第四类:普通用户(不懂代码也可以)
背景画像:
- 非技术人员
- 有文件管理、整理的需求
- 注重数据隐私
使用流程(完全不需要写代码):
- 安装 Node.js(官网下载,一路 Next)
- 克隆或下载项目
- 在项目目录打开命令行,执行
npm install - 执行
npm start,跟随引导输入:- API Base URL(购买 API 服务商提供)
- API Key
- 模型名称
- 工作区路径(如
D:\我的文档) - 选择一个人设
- 完成后 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 支持一下开源项目!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)