ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于TypeScript与AI的智能日记Agent:打造个人知识库CLI工具

基于TypeScript与AI的智能日记Agent:打造个人知识库CLI工具 1. 项目概述为什么我们需要一个“日记型 Agent”在信息爆炸的时代我们每天接触的碎片化内容越来越多从一闪而过的灵感、读到的好句子到某个触动情绪的瞬间这些“记忆的钻石”往往还没来得及打磨就沉没在聊天记录、浏览器书签和手机备忘录的海洋里。传统的日记应用要么太重打开需要仪式感要么太轻只是一个简单的文本编辑器缺乏智能的整理和回溯能力。作为一个长期与代码和数据打交道的开发者我一直在想能不能用技术的力量为自己打造一个更聪明、更懂我的数字记忆伙伴这就是“回忆是钻石”这个日记型 Agent 项目的初衷。简单来说它不是一个传统的日记 App而是一个运行在命令行CLI里的智能助手。你用最自然的语言比如“今天和团队解决了那个棘手的线上 Bug方案是用 Redis 缓存做热点数据隔离”记录一条笔记这个 Agent 会理解你的内容自动为你打上标签如“工作”、“技术”、“Redis”生成摘要甚至在未来某个你模糊搜索“缓存问题”时它能精准地找到这条记录并关联起相关的上下文。它的核心是“理解”而不仅仅是“存储”目标是让记录和回顾都变得高效、无感且有温度。如果你也苦于想法稍纵即逝或是想系统化地沉淀个人经验与成长轨迹那么这个用 TypeScript 打造、基于 Markdown 存储、通过 npm 全球分发的工具或许正是你需要的。2. 核心设计思路当日记本遇上智能体2.1 定位CLI 优先的“轻量级大脑”为什么选择 CLI命令行界面作为主要交互方式这源于一个核心洞察最高效的记录发生在思维流动的瞬间任何需要切换窗口、点击鼠标的操作都会形成打断。对于开发者、写作者或任何经常使用终端的人来说命令行是最高频的工作环境。在这里一个简单的diary add “...命令就能完成记录体验无缝衔接。CLI 工具天生具有可脚本化、可集成的优势未来可以轻松与 Git Hook、自动化任务如每日 Stand-up 总结生成甚至 IDE 插件结合扩展性极强。2.2 架构基于 Agent 的异步处理管道这里的“Agent”并非指某个特定的 AI 大模型而是一种设计模式一个能感知环境你的输入、自主决策如何分类、摘要、执行动作保存、索引并持续学习从你的反馈中优化的智能体。本项目的架构核心是一个异步处理管道输入解析层接收来自 CLI 的原始文本、可选标签和元数据如日期、心情emoji。智能处理层Agent 核心这是大脑所在。我最初集成的是 OpenAI 的 API利用其强大的自然语言理解能力。这里的关键是设计精准的“系统提示词”System Prompt引导 AI 扮演一个“专业的个人知识库管理员”。例如提示词会要求它从记录中提取关键实体人名、项目名、技术名词、推断主题类别工作/生活/学习/灵感并生成一句凝练的摘要。所有处理均在本地通过 API 调用完成原始日记内容不会用于训练。数据持久层处理后的结构化数据原始内容、AI生成的标签、摘要、时间戳将以追加的方式写入本地的 Markdown 文件。选择 Markdown 是因为它是纯文本人类可读、版本控制友好如用 Git 管理且格式足够丰富支持标题、列表、代码块等。索引与查询层为了实现快速检索我们需要一个本地索引。这里没有直接用数据库而是选择在首次查询或定期任务中遍历 Markdown 文件构建一个内存中的倒排索引或使用轻量级库如FlexSearch将关键词映射到具体的日记条目和行号。2.3 技术选型TypeScript 与 Node.js 生态的强强联合整个项目使用 TypeScript 开发这确保了代码在复杂异步逻辑和数据流转中的类型安全大大减少了运行时错误。CLI 框架选用的是commander.js它功能强大且生态成熟能优雅地处理命令、子命令、选项和帮助文档。项目通过 npm 发布意味着任何用户都可以通过npm install -g memory-diamond-agent假设包名进行一键安装体验无缝的全局工具。Markdown 的解析与操作选用remark生态系统它提供了统一的 AST抽象语法树接口让我们可以像操作数据一样精准地操作文档结构。注意关于 AI 依赖的考量。项目设计上将 AI 处理模块设为“可插拔”。这意味着如果你没有 API 密钥或者希望在离线环境下使用Agent 可以降级为仅进行简单的关键词匹配和保存。未来也可以轻松切换为开源的本地大模型如通过 Ollama 调用 Llama 3这为工具的长期可用性和隐私性提供了保障。3. 核心功能拆解与实现细节3.1 智能记录不止于文本保存diary add命令是入口但其背后的工作流是价值所在。# 基础记录 diary add “下午代码评审时Alice 提到了用 useMemo 优化渲染性能的方案值得深入试试。” # 带初始标签和心情 diary add -t work,react -m “成功部署了新的日志聚合服务ELK 栈踩坑完毕。”当这条记录被触发后内部流程如下构造请求CLI 将用户输入、标签-t、心情-m以及当前时间戳打包成一个 JSON 对象。调用 AI Agent这个 JSON 被发送给智能处理层。这里有一个精心调校的提示词模板你是一个个人知识库管理助手。请分析用户的一段日记记录并完成以下任务 1. 提取核心摘要1句话中文。 2. 推断3-5个关键词或标签中英文均可基于内容而非用户已提供的标签。优先考虑技术术语、项目名称、核心概念、人物、情感基调。 3. 判断主要领域work工作、life生活、study学习、idea灵感/想法。 用户可能已提供一些标签请在此基础上补充。 请以 JSON 格式回复{“summary”: “”, “keywords”: [], “domain”: “”} 记录原文「{userInput}」解析与融合收到 AI 返回的 JSON 后系统会将 AI 生成的关键词与用户手动输入的标签合并、去重。领域信息用于后续的自动归档。生成 Markdown最终一条结构化的日记条目被生成## [2024-05-27 15:30] **摘要** 成功部署ELK栈日志聚合服务并总结踩坑经验。 **标签** #work #devops #elk #logging #踩坑 **领域** work 成功部署了新的日志聚合服务ELK 栈踩坑完毕。这个格式清晰地将元数据与内容分离既方便人阅读也方便程序解析。3.2 多维检索让记忆可以被“钩”出来简单的全文搜索grep在数据量多时会显得笨拙。本 Agent 实现了更智能的检索。# 全文检索基于构建的索引 diary search “缓存 性能” # 按标签检索 diary search --tag react --tag optimization # 按领域和时间范围检索 diary search --domain work --from 2024-05-01 --to 2024-05-20 # 组合检索查找上个月工作相关的“错误” diary search --domain work --from 2024-04-01 --to 2024-04-30 “错误”实现关键点在于索引构建。我们不会在每次写入时都重建索引那样效率太低。而是在后台运行一个守护进程或在每次search命令首次执行时检查索引是否过期例如通过对比 Markdown 文件的最后修改时间和索引文件的生成时间。索引的数据结构大致如下// 伪代码索引结构示例 const searchIndex { “关键词1”: [ { filePath: ‘/path/to/diary.md’, lineNumber: 45, score: 0.9 }, { filePath: ‘/path/to/diary.md’, lineNumber: 102, score: 0.7 } ], “关键词2”: [...], // ... 同时也会建立标签、领域等字段的倒排索引 };搜索时查询语句会被分词然后同时在“内容关键词索引”、“标签索引”、“领域索引”中查找最后根据匹配的相关性分数如 TF-IDF 简单计算进行排序和呈现。结果会高亮显示匹配的关键词并展示其所在的上下文段落。3.3 数据统计与回顾看见自己的成长脉络日记的价值不仅在于记录更在于回顾和洞察。Agent 提供数据分析功能# 生成过去一个月的记录统计 diary stats --last 30days # 输出标签云高频标签 diary stats --tag-clouddiary stats命令会解析指定时间范围内的所有日记生成报告数量趋势每日/每周记录数量折线图在终端中用字符画或输出数据供其他工具绘图。标签分布展示最常使用的标签帮你发现关注焦点。领域占比直观看到你在工作、生活、学习上的时间分布。情绪曲线如果记录了心情通过统计心情 emoji生成简单的情绪波动图。这些数据可视化即便只是终端里的简单图表能提供强大的复盘视角。比如你可能会发现“React”和“性能优化”这两个标签经常同时出现这或许暗示了你下一个技术深挖的方向或者发现每周五的生活类记录特别少提醒自己需要更好地平衡工作与生活。4. 开发实战从零构建你的日记 Agent4.1 项目初始化与核心依赖安装首先创建一个新的 TypeScript 项目环境。# 1. 初始化项目 mkdir memory-diamond-agent cd memory-diamond-agent npm init -y # 2. 安装 TypeScript 和 Node.js 类型定义 npm install -D typescript types/node ts-node # 3. 初始化 tsconfig.json npx tsc --init # 在生成的 tsconfig.json 中确保设置 target: ES2020, module: commonjs, outDir: ./dist # 4. 安装核心依赖 npm install commander dotenv openai remark remark-frontmatter remark-parse remark-stringify npm install -D types/node types/dotenv # commander: CLI框架 # dotenv: 管理环境变量如API密钥 # openai: OpenAI官方SDK用于AI功能 # remark系列: Markdown处理生态系统接下来规划项目核心目录结构。一个清晰的结构是后续可维护性的基础。memory-diamond-agent/ ├── src/ │ ├── cli/ # CLI命令定义与解析 │ │ └── index.ts │ ├── core/ # 核心业务逻辑 │ │ ├── agent/ # 智能处理Agent │ │ │ └── openai-agent.ts │ │ ├── storage/ # 数据存储与读取 │ │ │ └── markdown-store.ts │ │ └── indexer/ # 索引构建与搜索 │ │ └── simple-indexer.ts │ ├── types/ # TypeScript类型定义 │ │ └── diary.ts │ └── utils/ # 工具函数 │ └── date-utils.ts ├── bin/ # CLI入口点 │ └── diary.js # 指向编译后的JS ├── .env.example # 环境变量示例 ├── .gitignore ├── package.json ├── tsconfig.json └── README.md4.2 定义数据模型与核心流程在src/types/diary.ts中我们首先定义贯穿整个系统的数据类型。// src/types/diary.ts export interface DiaryEntryInput { content: string; // 用户输入的原始内容 tags?: string[]; // 用户手动输入的标签 mood?: string; // 心情emoji timestamp?: Date; // 记录时间默认为当前时间 } export interface DiaryEntry extends DiaryEntryInput { id: string; // 唯一ID如UUID或时间戳哈希 timestamp: Date; // 确保有值 summary: string; // AI生成的摘要 keywords: string[]; // AI提取的关键词与tags合并后 domain: ‘work’ | ‘life’ | ‘study’ | ‘idea’; // 所属领域 // 存储位置信息可选便于快速定位 filePath?: string; lineNumber?: number; } export interface SearchQuery { keyword?: string; tags?: string[]; domain?: string; fromDate?: Date; toDate?: Date; }核心流程的主干逻辑在src/cli/index.ts中它负责串联各个模块。// src/cli/index.ts (简化版) import { Command } from ‘commander’; import { addEntry } from ‘../core/add-entry’; import { searchEntries } from ‘../core/search-entries’; import { generateStats } from ‘../core/stats-generator’; const program new Command(); program .name(‘diary’) .description(‘你的智能日记型 Agent’) .version(‘1.0.0’); program .command(‘add’) .description(‘添加一条新日记’) .argument(‘content’, ‘日记内容’) .option(‘-t, --tags tags...’, ‘添加标签逗号分隔’) .option(‘-m, --mood mood’, ‘心情 emoji’) .action(async (content, options) { const input: DiaryEntryInput { content, tags: options.tags ? options.tags.split(‘,’) : [], mood: options.mood, }; const savedEntry await addEntry(input); console.log(✅ 记录已保存 (ID: ${savedEntry.id})); console.log(摘要: ${savedEntry.summary}); }); program .command(‘search’) .description(‘搜索日记条目’) .argument(‘[keyword]’, ‘搜索关键词’) .option(‘--tags tags...’, ‘按标签筛选’) .option(‘--domain domain’, ‘按领域筛选 (work/life/study/idea)’) .action(async (keyword, options) { const query: SearchQuery { keyword, tags: options.tags, domain: options.domain, }; const results await searchEntries(query); // 格式化并打印结果 results.forEach(entry { console.log(\n--- [${entry.timestamp.toLocaleDateString()}] ${entry.mood || ‘’} ---); console.log(摘要: ${entry.summary}); console.log(标签: ${entry.keywords.join(‘, ‘)}); console.log(内容: ${entry.content.slice(0, 100)}...); // 预览 }); }); // ... stats 等其他命令 program.parse();4.3 实现智能处理层Agent 核心这是项目的“大脑”。我们以 OpenAI 为例实现一个基础的 Agent。// src/core/agent/openai-agent.ts import OpenAI from ‘openai’; import { DiaryEntryInput, DiaryEntry } from ‘../../types/diary’; import * as dotenv from ‘dotenv’; dotenv.config(); export class OpenAIAgent { private client: OpenAI; private model: string; constructor() { // 从环境变量读取API密钥确保安全 const apiKey process.env.OPENAI_API_KEY; if (!apiKey) { throw new Error(‘OPENAI_API_KEY 环境变量未设置。请创建 .env 文件并添加您的密钥。’); } this.client new OpenAI({ apiKey }); this.model ‘gpt-3.5-turbo’; // 或 ‘gpt-4’兼顾成本与效果 } async processEntry(input: DiaryEntryInput): PromisePickDiaryEntry, ‘summary’ | ‘keywords’ | ‘domain’ { const systemPrompt 你是一个专业的个人知识库管理助手。请分析用户的一段日记记录并完成以下任务 1. 提取核心摘要1句话中文。 2. 推断3-5个关键词或标签中英文均可基于内容而非用户已提供的标签。优先考虑技术术语、项目名称、核心概念、人物、情感基调。 3. 判断主要领域work工作、life生活、study学习、idea灵感/想法。 用户可能已提供一些标签请在此基础上补充。 请严格以 JSON 格式回复格式如下{“summary”: “”, “keywords”: [], “domain”: “”}; const userPrompt 记录原文「${input.content}」 用户提供的标签[${input.tags?.join(‘, ‘) || ‘无’}]; try { const completion await this.client.chat.completions.create({ model: this.model, messages: [ { role: ‘system’, content: systemPrompt }, { role: ‘user’, content: userPrompt }, ], temperature: 0.2, // 较低的温度确保输出稳定、格式正确 response_format: { type: ‘json_object’ }, // 要求返回JSON }); const responseText completion.choices[0]?.message?.content; if (!responseText) { throw new Error(‘AI 代理返回了空响应’); } const parsedResult JSON.parse(responseText); // 合并用户标签和AI生成的关键词 const allKeywords [...new Set([...(input.tags || []), ...parsedResult.keywords])]; return { summary: parsedResult.summary, keywords: allKeywords, domain: parsedResult.domain, }; } catch (error) { console.error(‘AI 处理失败:’, error); // 降级策略返回基础信息 return { summary: input.content.slice(0, 50) ‘...’, keywords: input.tags || [], domain: ‘life’, // 默认领域 }; } } }4.4 实现数据持久层Markdown 存储我们将日记追加到按月份组织的 Markdown 文件中例如diary/2024-05.md。// src/core/storage/markdown-store.ts import fs from ‘fs/promises’; import path from ‘path’; import { DiaryEntry } from ‘../../types/diary’; import { format } from ‘date-fns’; export class MarkdownStore { private diaryDir: string; constructor(diaryDir: string ‘./diary’) { this.diaryDir diaryDir; } private getMonthlyFilePath(date: Date): string { const yearMonth format(date, ‘yyyy-MM’); return path.join(this.diaryDir, ${yearMonth}.md); } async saveEntry(entry: DiaryEntry): PromiseDiaryEntry { const filePath this.getMonthlyFilePath(entry.timestamp); await fs.mkdir(path.dirname(filePath), { recursive: true }); // 确保目录存在 const markdownContent this.entryToMarkdown(entry); // 追加写入文件 await fs.appendFile(filePath, markdownContent ‘\n\n’); // 补充存储位置信息 entry.filePath filePath; // 注意精确的行号需要读取文件计算这里为简化可先不存或存一个估算值 return entry; } private entryToMarkdown(entry: DiaryEntry): string { const timeStr format(entry.timestamp, ‘yyyy-MM-dd HH:mm’); const tagsStr entry.keywords.map(k #${k.replace(/\s/g, ‘-‘)}).join(‘ ‘); // 标签处理为#tag格式 return ## [${timeStr}] ${entry.mood || ‘’} **摘要**: ${entry.summary} **标签**: ${tagsStr} **领域**: ${entry.domain} ${entry.content}; } // 后续可以添加读取、解析整个文件的方法用于构建索引和搜索 async readMonthlyEntries(filePath: string): PromiseDiaryEntry[] { // 实现略需要解析Markdown反向构造DiaryEntry对象 // 可以使用 remark 和 remark-frontmatter 来解析 return []; } }4.5 构建与发布打造可分享的 npm 包为了让工具能被他人使用我们需要将其打包并发布到 npm。配置 package.json确保定义了正确的入口文件和 bin 字段。{ “name”: “memory-diamond-agent”, “version”: “1.0.0”, “description”: “A smart diary agent that understands your memories.”, “main”: “dist/index.js”, “bin”: { “diary”: “./bin/diary.js” }, “scripts”: { “build”: “tsc”, “start”: “node dist/cli/index.js”, “prepublishOnly”: “npm run build” }, “files”: [“dist”, “bin”], // ... 其他依赖和配置 }创建 CLI 入口点在bin/diary.js中指向编译后的代码。#!/usr/bin/env node require(‘../dist/cli/index.js’);编译与测试npm run build # 本地全局链接测试 npm link # 现在可以在任何地方运行 diary --help 了发布到 npm# 1. 登录 npm如果没有账号先去 npmjs.com 注册 npm login # 2. 确保包名唯一然后发布 npm publish --access public发布后任何人只需运行npm install -g memory-diamond-agent即可安装使用。5. 进阶优化与深度思考5.1 性能与隐私的平衡术随着日记数量增长比如超过 1000 条全量文件遍历搜索会变慢。此时引入一个轻量级本地数据库如 SQLite 或 LevelDB来存储元数据和索引是必要的。但核心日记内容仍建议保留在 Markdown 中因为纯文本的可移植性和可读性无可替代。架构可以演变为Markdown 作为“源文件”数据库作为“快速索引”。隐私是另一个核心关切。所有 AI 处理都应明确告知用户并默认使用本地模型或可信任的 API。项目应提供完整的“离线模式”在.env配置中不设置 API 密钥时自动切换为基于规则如关键词匹配的简单处理模式。数据存储路径也应允许用户自定义如~/.memory-diamond并考虑支持端到端加密存储选项。5.2 扩展性从 CLI 到生态系统一个 CLI 工具可以成为个人自动化工作流的枢纽。以下是一些扩展思路Git 集成将日记目录初始化为一个 Git 仓库每次添加记录后自动提交。这样你的记忆就有了完整的版本历史可以追溯任何想法的演变过程。定期回顾与推送结合cron任务或系统定时器每周日晚上自动运行diary stats --last 7days并将总结通过邮件或 Telegram Bot 发送给自己形成每周复盘的习惯。与笔记软件联动开发一个导出插件将标记为“知识密度高”的日记条目按照特定模板同步到 Obsidian、Logseq 或 Notion 中融入你更大的知识管理体系。语音输入通过集成系统级的语音转文本服务如 macOS 的say和听写实现diary add --voice命令在通勤路上或灵感迸发时快速口述记录。5.3 遇到的坑与解决方案实录在开发过程中我踩过几个典型的坑这里分享出来希望能帮你节省时间AI 提示词Prompt的稳定性问题初期AI 有时会不按 JSON 格式回复或者漏掉某个字段。解决方案除了在提示词中严格要求 JSON 格式外还在代码中增加了健壮的解析逻辑。使用try...catch包裹JSON.parse并在失败时提供清晰的错误信息或降级方案。同时将temperature参数调低如 0.2减少输出的随机性。Markdown 文件解析的复杂性自己用正则表达式解析 Markdown 很快会陷入困境尤其是处理嵌套列表、代码块等情况。解决方案果断采用remark生态系统。它提供了统一的 AST 接口remark-parse将文本转为 ASTremark-stringify将 AST 转回文本我们只需要在 AST 层面操作节点即可可靠性和可维护性大大提升。CLI 命令的异步处理在commander的action回调中执行异步的 AI 调用和文件写入如果不用async/await正确处理会导致命令提前结束或错误不报告。解决方案确保所有action回调函数都是async函数并对可能出错的操作进行try...catch给用户友好的错误反馈而不是一堆 Node.js 栈跟踪信息。跨平台路径问题在代码中硬编码路径分隔符/或\会导致在另一个操作系统上运行失败。解决方案始终使用 Node.js 的path模块如path.join(),path.dirname()来处理路径构建它能自动适配当前操作系统。索引的实时性与效率每次搜索都重建索引无法接受但如何感知文件变化解决方案采用“惰性索引”和“缓存失效”策略。首次搜索时构建索引并序列化JSON.stringify保存到文件。之后每次搜索前检查所有源 Markdown 文件的最后修改时间fs.statSync如果任何一个文件的修改时间晚于索引文件的生成时间则重建索引否则直接加载缓存的索引。这是一个简单有效的权衡。这个项目对我来说远不止是一个工具。它是我将技术用于解决自身真实需求的实践是“以我为主为我所用”的体现。在开发过程中我反复思考的不仅是“如何实现”更是“为什么需要”和“怎样更好”。它让我更主动地记录也让我在回看时能清晰地看到自己技术栈的变迁、思考深度的变化甚至情绪的脉络。这些“钻石”经过 Agent 的初步打磨在未来的某次搜索中被重新点亮时其价值远超简单的文本存储。如果你也动手构建一个或许最重要的不是复现我的每一行代码而是找到那个最适合你自身工作流和思维习惯的“智能”切入点。
返回列表