deepthink-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "deepthink-agent",
3
+ "version": "1.0.0",
4
+ "description": "从零实现的终端 AI Agent:Agent 循环、四层上下文工程、工具系统、RAG、跨会话记忆、多 Agent 协作",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "deepthink": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist/index.js",
11
+ "sample-data.txt",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "start": "tsx src/index.ts",
16
+ "build": "node scripts/build.mjs",
17
+ "build:exe": "node scripts/build-exe.mjs",
18
+ "init": "tsx src/index.ts init",
19
+ "prepublishOnly": "node scripts/build.mjs"
20
+ },
21
+ "keywords": [
22
+ "agent",
23
+ "ai",
24
+ "llm",
25
+ "cli",
26
+ "tool-calling",
27
+ "rag",
28
+ "mcp"
29
+ ],
30
+ "author": "youjiazhi",
31
+ "license": "ISC",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://gitee.com/youjiazhijiayou/deep-think-agent.git"
35
+ },
36
+ "engines": {
37
+ "node": ">=20"
38
+ },
39
+ "type": "module",
40
+ "devDependencies": {
41
+ "@types/better-sqlite3": "^9.6.0",
42
+ "@types/node": "^26.2.0",
43
+ "esbuild": "^0.28.2",
44
+ "postject": "1.0.0-alpha.6",
45
+ "tsx": "^4.23.12",
46
+ "typescript": "^7.0.2"
47
+ },
48
+ "dependencies": {
49
+ "@ai-sdk/openai": "^4.0.46",
50
+ "@modelcontextprotocol/server-github": "^2025.4.8",
51
+ "@types/turndown": "^5.0.6",
52
+ "ai": "^7.0.77",
53
+ "better-sqlite3": "^13.0.3",
54
+ "croner": "^10.0.1",
55
+ "dotenv": "^17.4.2",
56
+ "fast-glob": "^3.3.3",
57
+ "sqlite-vec": "^0.1.9",
58
+ "turndown": "^7.2.4",
59
+ "zod": "^4.5.4"
60
+ }
61
+ }
package/readme.md ADDED
@@ -0,0 +1,445 @@
1
+ # 基于claude的源代码打造一个agent -- DeepThink Agent
2
+
3
+ 一个从零实现的终端 AI Agent(不依赖 LangChain 之类的框架),包含 Agent 循环、
4
+ 四层上下文工程、工具系统、RAG 检索、跨会话记忆、Skill 机制、权限 Hook 管线、
5
+ 定时任务与多 Agent 协作。下面是**直接跑起来**的说明,设计与实现细节见本文后半部分。
6
+
7
+ ---
8
+
9
+ ## 快速开始
10
+
11
+ 需要 **Node.js >= 20**,以及一个 OpenAI 兼容的模型服务(任何一家都行,见下)。
12
+
13
+ **这个包不预置任何模型凭证**,你需要自己提供 API Key。
14
+
15
+ ### 1. 配置模型
16
+
17
+ **方式一:环境变量(最省事)**
18
+
19
+ ```bash
20
+ # 以阿里云 DashScope 为例
21
+ set DASHSCOPE_API_KEY=sk-xxx # Windows
22
+ export DASHSCOPE_API_KEY=sk-xxx # macOS / Linux
23
+
24
+ # 可选:换模型名
25
+ set MAIN_MODEL_NAME=qwen-max-latest
26
+ ```
27
+
28
+ **方式二:配置文件(可改 baseURL,接任意 OpenAI 兼容服务)**
29
+
30
+ 在当前目录放一个 `deepthink.config.json`:
31
+
32
+ ```json
33
+ {
34
+ "model": {
35
+ "provider": "custom",
36
+ "name": "qwen-plus",
37
+ "baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1",
38
+ "apiKey": "${DASHSCOPE_API_KEY}"
39
+ }
40
+ }
41
+ ```
42
+
43
+ `apiKey` 支持 `${VAR}` 语法从环境变量取值,这样密钥不必写进文件。
44
+ 把 `baseURL` 换掉就能接 OpenAI、DeepSeek、Moonshot、本地 Ollama 等任何兼容服务。
45
+
46
+ **方式三:交互式向导**
47
+
48
+ ```bash
49
+ npx deepthink-agent init
50
+ ```
51
+
52
+ ### 2. 启动
53
+
54
+ ```bash
55
+ npx deepthink-agent
56
+ ```
57
+
58
+ 首次运行会在当前目录自动创建 `.memory/`、`.sessions/`、`.usage/`、
59
+ `.cron/` 和 `knowledge.db`(这些都是你的个人数据,跟着当前目录走)。
60
+
61
+ ### 没配 Key 会怎样
62
+
63
+ 会自动回落到内置的 Mock 模型:Agent 循环、工具调用、检索、记忆等**完整流程照跑**,
64
+ 用来验证安装是否正常,但不产生真实大模型输出。
65
+
66
+ ```bash
67
+ npx deepthink-agent # 输出里会显示「演示模式」横幅
68
+ ```
69
+
70
+ ### 试试这些
71
+
72
+ 进入交互界面后可以输入:
73
+
74
+ ```
75
+ 读一下 package.json # 触发 read_file
76
+ 搜索一下 export # 触发 grep
77
+ 帮我对比 Hono、Fastify 和 Express 的性能和生态 # 触发子 Agent 协作
78
+ /help # 查看全部命令
79
+ /rag # 查看知识库状态
80
+ /agents # 查看子 Agent 执行记录
81
+ /cron # 查看定时任务
82
+ ```
83
+
84
+ 配好真实模型后,正常用自然语言提需求即可。如果当前是演示模式(没配 Key),
85
+ 上面「读一下…」「搜索一下…」这类句式会命中 Mock 的固定关键词,同样能把
86
+ Agent 循环和工具调用链路跑给你看。
87
+
88
+ > 提醒:Agent 的 `bash` 工具会在当前目录执行 shell 命令。建议在一个**专门的空目录**里试用,
89
+ > 不要直接在重要目录下运行。
90
+
91
+ ---
92
+
93
+ ## 项目起步
94
+ 1. pnpm init 初始化项目
95
+ 2. pnpm i typescript --save-dev 安装typescript
96
+ 3. tsc --init 初始化typescriptconfig.json
97
+ 4. pnpm add ai @ai-sdk/openai dotenv 如果用普通的fetch,无法支持流式的eventsource,所以引入ai-sdk以openai标准调用ai的api,dotenv用来读取env
98
+ 5. pnpm add -D tsx @types/node tsc是typescript的运行环境,tsx是实时的运行,ts写完直接编译成js再跑,@types/node是typescript的node类型定义
99
+
100
+ ## ai 这个SDK
101
+ - generateText 生成文本
102
+ - streamText 流式生成文本
103
+ ## 进程持续
104
+ - readline 读取用户输入
105
+ - process.stdout.write 写入标准输出
106
+ - process.stdin.write 写入标准输入
107
+ - process.stdin.on('data', (data) => { ... }) 监听标准输入事件
108
+ - process.exit 退出进程
109
+
110
+ ## 模型调用三要素
111
+ 1. 模型调用: StreamConsumer --- 解析工具调用,推理过程,token用量等多种时间<!--streamText+model-->>
112
+ 2. 消息管理: 四层上下文管理 --- 截断、时间衰减修剪、LLM摘要压缩、Cache优化 <!--messages,把对话和回答全部注入上下文中-->>
113
+ 3. 交互循环: AgentLoop --- while(true){think - act - observe} <!--ask递归调用模拟AgentLoop-->>
114
+
115
+ # AgentLoop搭建 - agent的心跳
116
+ ## 从能聊天到能干活,ai包自带的循环机制
117
+ ```
118
+ user: 南昌今天的天气怎么样?
119
+ agent:[调用get_weather工具]->南昌今天天晴,30摄氏度...
120
+ ```
121
+ - SDK ai 提供的 stremText 方法,存在自动循环机制 stopWhen,给定终止条件,自动进行循环
122
+ 用户提问 -> LLM说要调用工具 -> 调用工具 -> 再次调用LLM -> 得到工具返回的结果 -> 再次调用LLM .... ->循环结束,返回给用户
123
+
124
+ - **可定制性太差,无法在循环中插入自己的逻辑** --- 一个优秀的agent,应当在AgentLoop中插入一些重要的逻辑,例如 添加日志、添加缓存、添加错误处理等等
125
+
126
+ ## 上保险丝,防止死循环和无进展
127
+ 1. 死循环检测: 连续调用相同工具+相同参数?打断循环 (结果一致较难判断,暂时不加)
128
+ * 通用检测: 同个工具、相同参数、相同结果?打断循环
129
+ **实现手段**:
130
+ 1. 调用指纹、结果指纹。将工具名+参数做一个确定性的JSON序列化,再哈希256加密,get_weather({city:'南昌'}),得到一串哈希值。
131
+ 2. 滑动窗口: 例如,就看最近的30轮有没有重复的文件指纹
132
+ 3. 相同的输入+相同的输出 === 无进展: 只有调用指纹与结果指纹都想通过,才认为是无进展的
133
+ > 为什么是JSON序列化?因为接受多个参数,顺序不同时,也应当判定为相同参数。所以用JSON序列化
134
+
135
+ * 无进展的轮询检测:每隔一段时间轮询,结果无进展?打断循环
136
+ * Ping-Pong检测: 两个工具,交替使用,结果没有进展?打断循环
137
+ 2. Token 预算: 烧了多少token?超过预算?打断循环
138
+ - 把每步的token用量累加起来,超过预算则打断循环
139
+ - 缓存命中问题待解决
140
+ 3. API容错: 请求重试、模型降级
141
+ - 从能跑 -> 知道何时跑挂
142
+ - 错误要分类,哪些值得重试、哪些不值得重试
143
+ - 重试机制: 指数退避+随机抖动
144
+
145
+ # Tool System - agent的手脚
146
+ 搭建一个正经的系统,从工具的注册到执行到截断,每一层都要有明确的职责
147
+ - 对于LLM来说,Tool是什么样子的存在?
148
+ 1. description 一段描述 - 告诉LLM这个工具是做什么的,什么时候该用
149
+ 2. Schema 一份参数 - 告诉LLM这个工具需要哪些参数,参数的类型,参数的必填性等
150
+ 3. 一个执行函数 - 真正的逻辑
151
+ * 在生产环境中,还要注意工具的权限问题,这个工具能否和别的工具并发执行。例如,写文件的函数只能串行,读文件的函数就可以并行。
152
+ ### 并发控制
153
+ - LLM在一次回复中说要调用多个工具,AI SDK 会并发执行所有带有execute属性的工具
154
+ - 需要 读写锁 来保护工具的执行,防止多个工具同时执行导致的并发问题
155
+
156
+ - 经典思路
157
+ 1. 只读工具: 获取共享锁,可以和其他只读工具同时持有
158
+ 2. 读写工具: 获取独占锁,必须等所有其他工具执行完毕后,才能执行
159
+
160
+ - 代码实现逻辑
161
+ 假设同时有三个工具调用,read_file、write_file、write_file。AI SDK 会同时执行三个工具调用的execute方法,但是在执行逻辑前,我们先判断该工具是否安全(可并行),如果安全则直接执行,并记录并发锁数量,表示当前正在执行的工具数量。如果不安全,则在队列中塞入阻塞函数,阻止当前的工具执行。等待没有共享锁的时候,则公平竞争独占锁。
162
+ ### 联网搜索
163
+ 1. Tovily 搜索引擎 (1000次/一个月免费额度)
164
+ 2. Serper 搜索引擎 (2500次/一个月免费额度) --- google 搜索引擎代理
165
+ - 双引擎实现
166
+ * 通过环境变量自动选择
167
+
168
+ ## agent接入MCP服务
169
+ * MCP 的通信协议是 JSON-RPC 2.0,传输方式支持 stdio 和 Streamable HTTP。我们启用 stdio 本地进程,通过标准的输入和输出来收发消息
170
+ <!-- node中进程之间的通讯就叫标准的输入输出 -->
171
+ - 接入 GitHub MCP 服务器
172
+
173
+ - 我们的Agent(client)启动一个对接 MCP Server 的进程,通过 stdio 发JSON消息给GitHub Mcp Server。GitHub MCP Server 会向我们的进程中返回JSON消息,我们通过 stdout 读取这些消息。
174
+ 1. 握手 --- Client 发 initialize method 给 Server,Server 会返回一个 JSON-RPC 2.0 的 response,回复它支持的能力
175
+ 2. 发现工具 --- client 发 tools/list method 给 server,server 会返回所有的工具名称、描述、参数 schema 等消息
176
+ 3. 调用工具 --- 模型决定调用某个工具,client 发tools/call method 给 server,server会执行该工具,返回工具的执行结果
177
+ ### ToolSearch 延迟加载
178
+ - 把不常用的工具藏起来,模型需要的时候才按需搜索,按需发现,将Prompt中的给哦你根据数量从几十个减少到几个,同时不影响agent的能力
179
+
180
+ - 工具分类
181
+ 1. 核心工具: 几乎每次都要用得到的工具,例如 read、write、edit、bash、glob、grep 等等
182
+ 2. 低频工具: 偶尔需要的,直接打上标记 shouldDefer:true,例如 websearch、webfetch、notion等等,以及所有MCP接入工具都属于低频工具
183
+
184
+ claudecode细节:工具被标记为 shouldDefer:true,但是这个延迟工具的Schema如果没有超过上下文窗口的10%,那么就不延迟加载。
185
+ 也就是说:**当工具所占上下文超过10%时,才启用延迟加载,否则全部加载**
186
+
187
+ - 打造一个元工具:tool_search 用户输入-> agent -> LLM发现无法处理问题,调用tool_search工具 -> tool_search根据关键词返回可能的工具列表 -> LLM找到了需要的工具,返回工具的调用 -> 执行结果返回LLM,继续循环...
188
+ 有了元工具,核心工具全部携带进入prompt,延迟工具的名字和搜索关键字都带上进入prompt。这样是为了防止LLM不知道有额外工具的存在,同时,我们采用精确匹配的方式,LLM需要有搜索关键字才能搜到需要的工具
189
+
190
+ # 半程总结
191
+ ## 我们的agent做了什么
192
+ 1. 搭建了 ToolRegistry 模块,统一注册和管理所有的工具,加了截断和读写锁
193
+ 2. 通过MCP协议接入了github mcp 服务
194
+ 3. 实现了 ToolSearch 延迟加载功能,解决了工具数量过多挤占上下文空间的问题
195
+
196
+ # 上下文工程
197
+ 1. offload(卸载): 上下文太挤,把信息搬到上下文之外(文件、数据库)
198
+ 2. Reduce(压缩): 上下文太长,把信息压缩成更短的形式(摘要,向量化)
199
+ 3. Retrieve(检索): 需要的信息不在上下文当中,从外部源中检索相关信息(RAG、文件读取)
200
+ 4. lsolate(隔离): 一个上下文装不下所有的事,多agent协作时,需要多个上下文,将不同来源的信息隔离处理。(Multi-Agent)
201
+ 5. Cache(缓存): 重复计算,将计算过的结果缓存起来,复用已有的计算结果(KV Cache、Prompt Cache)
202
+ ## 持久化上下文 --- 对话存档
203
+ 1. SQLite 数据库
204
+ 2. Redis 缓存
205
+ 3. JSON 文件 --- 我们采用claudcode的这个方式
206
+ - 我们采用claudecode同款方式,JSONL (JSON Lines)格式,因为JSONL格式简单、易读易写
207
+ * 为什么不用数据库呢?
208
+ 1. 数据库比JSONL更重,更复杂。并且JSONL格式不怕崩溃,写入文件的操作天然更安全一些。
209
+ 2. 可调试,直接人为打开文件,查看数据
210
+ 3. 零依赖,不需要安装任何库
211
+
212
+
213
+ ## 系统提示词处理 --- 让system prompt 变得可维护、可拓展
214
+ - 设计 Prompt Pipe 模式。将 system prompt 分为多个部分,每个部分负责一块独立的功能。
215
+ 1. 核心规则 -- 介绍 Deep Think Agent 的功能
216
+ 2. 工具引导 -- 介绍可用的工具、搜索功能
217
+ 3. 延迟工具摘要 -- 介绍延迟工具的摘要,包含工具名称与搜索关键词
218
+ 4. 会话上下文 -- 介绍当前会话有多少条上下文
219
+ - 以上的拼接顺序是不能打乱的,应该保证不能以发生变更的模块放在最前面,因为LLM的kv Cache机制。
220
+ > 在预测下一个token时,将根据与之前的token的点积值进行加权求和,而已经计算过的token的点积值,直接从缓存中获取,避免重复计算。所以,容易变更的提示词模块,放在前面会导致每次更改完整的提示词都需要全部重新计算,无法命中缓存。
221
+
222
+ ## 上下文压缩
223
+ - Compaction(紧凑化) --- 不破坏原有对话结构
224
+ 1. 移除某些比较大的工具调用的内容
225
+ 2. 去重
226
+ 3. 图片资源替换为占位符
227
+ - Summarization(摘要)
228
+ 1. 用LLM来将上下文的摘要提取出来,作为新对话的上下文
229
+
230
+ * 上下文中有哪些内容?
231
+ 1. System Prompt (不能压)
232
+ 2. 用户输入 (不能压)
233
+ 3. 工具调用结果 (可以压)
234
+ 4. 历史对话信息 (可以压)
235
+
236
+ ### 不需要LLM即时压缩上下文,三层防线
237
+ 与其等到上下文爆了再压缩,不如一开始就少放点东西进上下文
238
+ - 摘要化压缩,要达到上下文窗口的87%(claude)时才触发,属于没办法的办法
239
+ 1. 当前的上下文用了多少token,**精确的token计算**通过大模型token用量来看,靠API返回usage.prompt_tokens。缺点是要API调用完才能拿到,而在API调用结束之前,我们需要估算一下token用量,来判断要不要干预
240
+ (AI SDK 会把 usage.prompt_tokens 改写成 usage.inputTokens。)
241
+ 2. 工具返回的值可能会被截断,截断的阈值应当设置为动态变化,根据上下文的使用率来调整。
242
+ 工具调用的结果,不超过上下文窗口的50%,且整个上下文不超过上下文窗口的75% (**openclaw双重约束**)
243
+ 3. TTL 修减 --- 时间衰减
244
+ - 老的工具得到的结果,几乎不会被使用,可以将其修剪掉。修剪分为两档
245
+ 1. 软修剪(5分钟前) - 保留头部与尾部各1500字符(openclaw),中间替换为占位符,例如 `[soft pruned]`
246
+ 2. 硬清除(10分钟) - 整个工具结果替换为占位符,例如`[tool result expired: read_file]`
247
+
248
+ > User、Assistant角色的上下文不会被修剪,永久保留。这样才能保证完整的对话结构
249
+
250
+ **三层防线 减少 即时 LLM 摘要压缩的压力:超大结果截断、清理过期内容、追踪token用量,之后再判断是否需要触发摘要压缩**
251
+
252
+
253
+ ## Prompt Cache & 成本追踪
254
+ - 成本: 假设你已经通过三层防线+摘要压缩,将上下文从100k减少到了20k,但是你的agent在执行50轮循环时,每次都要带上20k的上下文给LLM,这会让token用量增加很多不必要的花销
255
+ - Prompt Cache - 把请求的 "前缀" 缓存在服务器,下次发同样的前缀直接复用
256
+ - 三大问题
257
+ 1. 各个LLM厂家的缓存机制有何差异(3种模式)
258
+ - 隐式缓存: 模型那边自动把前缀缓存,我们可以直接在usage种看到返回的cached_tokens
259
+ - 显式标记模式: 在请求中添加一个 cache_control:{type:'ephemeral'}标记
260
+ - 显示缓存创建模式: 先调用API,拿到一个cache对象的ID,下次直接带上这个cacheID即可
261
+ **杀死缓存的坑**:系统提示词带上时间戳、工具列表一直在变化
262
+ 2. 给agent做好完整的成本追踪链路
263
+ - 让成本可见
264
+ 3. 做一个终端面板,让你可以随时看上下文占用(暂且搁置)
265
+
266
+ ## 跨会话记忆
267
+ - Session Memory 解决了一次对话内的连续性 --- 关掉对话后,重新打开可以接着继续对话。本质是将历史对话记录并携带进prompt
268
+ * 问题在于: 当对话进行多轮后,如果将几十次的对话记录都悉数携带进入prompt,上下文会爆、噪声也太多。
269
+
270
+ - Agent能从对话中提取值得长期保存的信息,存到文件中,下次开工时自动加载
271
+ ### Memory + File claude的skill
272
+ ```
273
+ ---
274
+ name: 用户偏好 Typescript
275
+ description: 用户对Typescript偏好,不喜欢python
276
+ type: user
277
+ ---
278
+ ```
279
+ 1. 文件记忆,claude也这样。 用 md 文件作为一个skill存记忆 + MEMORY.md 来索引
280
+ ### Memory + RAG
281
+ 1. 数据库记忆 :openclaw 用 SQLite 数据库 + sqlite-vec (向量检索)
282
+ 用文件和用数据库记忆有何区别? 各有优劣,文件记忆无法处理私域数据,数据库记忆有崩溃风险
283
+
284
+ ## RAG 检索增强生成
285
+ Agent没有参与过的流程,它没有记忆,如果需要处理私域知识,就需要用RAG来检索再生成
286
+ - 从零实现一个完整的RAG管线
287
+ 1. 分块
288
+ 2. 向量化
289
+ 3. 混合检索 (openclaw这么干) 向量搜索+关键词搜索 (73开,70%向量,30%关键词)
290
+ 4. 结果注入
291
+ <!-- 语义相近的文本在向量的高维空间中坐标接近 -->
292
+ 我们做的:
293
+ 1. 打造了文档分块的函数
294
+ 2. 打造了一个向量数据库(本质就是个手搓的数组)
295
+ 3. 打造了 RAG 工具,rag ingest
296
+ 4. 参考openclaw做了混合检索,向量检索用余弦相似度查找,关键词用BM25算法
297
+
298
+ ### 生产级别的RAG SQLite + SQLite-vec + FTS5 一个.db文件
299
+ - 假如有 10k 个文档片段,每个片段又有 原文内容、向量、来源、时间戳 等数据要存放,最直觉的做法就是存成一张表
300
+ 1. 向量搜索慢、关键词搜索慢
301
+
302
+ openclaw这么干:
303
+ 1. SQLite 存主要的数据(id、原文、来源、向量、时间戳)
304
+ 2. 通过 id 关联 chunks_vec(向量索引),把向量处理为二进制做索引加速
305
+ 3. 通过 id 关联 chunks_fts (全文索引)
306
+
307
+ ### 记忆库的体检
308
+ agent运行一段时间后,memory中会堆积很多历史记忆,这些历史记忆可能会跟当前的新记忆冲突,导致agent的决策错误。
309
+
310
+ - 记忆会变坏
311
+ 1. 记忆污染 - 被污染,LLM把推测当作了事实。例如agent读到文件中存在mysql配置文件,但是实际上我把数据库迁移为了阿里的瑶池数据库
312
+ 2. 数据爆炸 - 记忆太多,噪声太多。
313
+ 3. 记忆过期 - 代码变更了,但是记忆没跟上,agent还在用
314
+ 4. 记忆冲突 - 新旧记忆相互矛盾
315
+ - 处理方案:
316
+ 1. 不要什么垃圾都存入记忆库
317
+ 2. lint + TTL 分级清理
318
+ 3. dream 自动整理,让agent自己合并重复、清理垃圾
319
+
320
+ * claudecode 的记忆系统有一份明确不会保存的清单:
321
+ 1. 代码能推导的,不保存
322
+ 2. git 能查出来的不保存
323
+ 3. 文档明确要求不存的,不保存。claudecode中有一个配置文件 CLAUDE.md。其中可以配置不保存的文件
324
+ 4. 临时性的内容,不保存
325
+ * 该存放: 只存在对话中,其他地方推导不出来的
326
+ # Skill 机制
327
+ Skill 就是一份提示词,用来约束Agent的行为,让Agent拥有一套自己的行为规范
328
+ - Skill 不是 Tool
329
+ 1. Tool 是一个可执行的函数,是一个原子操作,read_file 工具是不会告诉 Agent 应该读哪个文件的
330
+ 2. Skill 是一份知识文档,是用markdown格式编写的agent行为指导。skills 应当注入到 System Prompt 中,让agent知道自己的行为规范
331
+ <!-- Tool可以比作是具体干活的工人,不同的工人有不同的分工,skill更像是一个规则,例如工地上干什么活要怎样的流程,需要哪些工人来干。 -->
332
+
333
+ claude的skill项目结构如下:
334
+ ```
335
+ .skills/
336
+ code-review/
337
+ SKILL.md
338
+ research/
339
+ SKILL.md
340
+ ```
341
+ ## skill采用YAML frontmatter格式
342
+ ```
343
+
344
+ ```
345
+ name:code-review
346
+ description: "以高级工程师的视角来审查代码变更"
347
+ type: user
348
+ ```
349
+
350
+ # code-review
351
+ ## 审查流程
352
+ ``` markdown
353
+ **1. 搜集变更范围**
354
+ - 从git中获取变更范围
355
+ - 分析变更范围,确定需要审查的文件
356
+ **2. 审查代码**
357
+ - 对每个文件进行审查
358
+ - 记录审查结果
359
+ ```
360
+
361
+
362
+ ### claude 怎么处理skill的加载
363
+ claude在每次对话启动,将所有的skill的frontmatter注入到 System Prompt 中,让agent知道有哪些skill,以及其描述。不带content防止挤压上下文。
364
+
365
+ claude中还给skill做了指引文件,
366
+
367
+ ## 写好一个skill,应当干些什么
368
+ 1. 做什么 - 要做什么,任务内容。例如审查代码变更
369
+ 2. 怎么做 - 按照什么步骤,什么优先级执行。例如先从git中获取变更范围,分析变更范围,确定需要审查的文件,对每个文件进行审查,记录审查结果
370
+ 3. 输出什么 - 报告格式。例如审查报告,返回的格式要有审查范围、审查文件、审查结果,且按照markdown格式返回。
371
+
372
+ https://github.com/mattpocock/skills 一个厉害的skill,在开发场景规范代码用
373
+
374
+ # plugin 机制
375
+ - 允许别人为我的agent打造一些功能
376
+ - 设计一个Plugin接口
377
+ 1. 你是谁(名称、版本、描述等等)
378
+ 2. 你要注册什么(工具、Skill)
379
+ 3. 你什么时候退出(生命周期结束,清理资源)
380
+ activate + destroy
381
+
382
+
383
+ ## Plugin 总结
384
+ 1. 接口契约: (PluginDefinition)定义了一个插件应该长什么样子
385
+ 2. API隔离: 定义了一个PluginApi,从环境文件里读到。将API暴露给插件,让每一个插件都能独立借助api来注册自己的工具
386
+ (让插件自己注册工具,而不是在我们的agent中注册。这样让第三方打造的插件,自行管理自己的工具,而不是全部委托给agent干)
387
+ 3. 命名隔离,`插件名__工具名`,防止插件之间出现命名冲突
388
+ 4. 命名周期管理: activate + destroy 插件的加载、卸载的生命周期管理。
389
+ * skill 是往 System Prompt 中注入一份知识,让agent执行有法可依。改变的是agent怎么思考
390
+ * plugin是往 agent 的生命周期中注入一份代码,包括工具。改变agent能做什么。
391
+ - plugin就是针对某个独立场景拓展的一套工具。
392
+
393
+ MCP 用的是 json rcp 2.0
394
+ ### Plugin 与 MCP Server有什么区别
395
+
396
+
397
+ # 权限系统 + Hook 管线
398
+ - 角色权限控制 -- 谁能用什么工具
399
+ - Bash 风险检测 -- 拦截危险命令
400
+ - Hook 管线 在工具执行前后,插入自定义的代码,例如日志、监控、权限检查等等
401
+
402
+ ## 角色权限控制,三级角色
403
+ 1. owner -- 管理员权限,可以使用所有的工具,包括 bash
404
+ 2. collaborator -- 可以使用大部分工具,但是不能使用 bash
405
+ 3. guest -- 只可以用安全的只读工具(查天气、做搜索、读文件等等)
406
+
407
+ ### Bash 风险把控,即使是owner,有些高危命令也不能执行
408
+ bash 命令细化,例如rm -rf /,ls -l /,etc/passwd 等,还有mac上的 sudo,linux的curl xxx
409
+
410
+
411
+ ## Hook 管线
412
+ - 在工具执行前后,插入钩子函数,执行一些自定义的逻辑。
413
+ 例如执行某些命令的时间。
414
+
415
+ # Corn 定时任务
416
+ - agent 主动执行一些任务,比如每天早上总结github仓库的commit,或者每三十分钟检查数据库是否有异常数据等等
417
+
418
+ - 生产级别的agent Cron 任务很复杂:
419
+ 1. 支持多种调度表达式 (固定时间,固定间隔,一次性定时)
420
+ 2. 支持任务持久化 (agent工作留痕,重启后,任务还能继续执行)
421
+ 3. 执行状态追踪 (记录任务跑了吗,跑失败了,跑成功了)
422
+ 4. 连续失败熔断
423
+ 5. 崩溃恢复
424
+
425
+ - 如果一个任务7天内都没被激活,则删除
426
+ - 一次性任务执行完删除
427
+
428
+
429
+ # 多agent协作 multi-agents
430
+ 用独立的上下文窗口做探索,压缩后传回结论,父agent的上下文保持干净
431
+
432
+ - 为什么要子Agent
433
+ 1. 单一的agent在工作工程中,会积累大量的上下文。频繁做压缩,又会破坏对话结构
434
+ 2. 提高效率,可以将一个任务拆分出多个任务并行执行,提升效率
435
+ 3. 让子agent做破坏性的操作,例如实验性的重构。这样不会影响主agent的正常运行。
436
+
437
+ ## 怎么让子agent在独立的上下文窗口中执行
438
+ 1. 隔离一个新的 messages 数组,子agent从零开始,与父agent的上下文相互独立
439
+ 2. 每次只取子agent工作结果的最后一条assistant消息,作为工作总结注入父agent上下文
440
+ 3. 借助 Promise.all 并行执行多个子agent,等待所有子agent执行完成后,合并结果给父agent
441
+ 4. 设计了两道保护机制:
442
+ - 嵌套深度: 默认不允许子agent fork新的子agent。防止递归
443
+ - 并发控制: 默认最多3个子agent同时运行,防止token消耗过大
444
+
445
+ - 同进程做隔离: 在一个终端隔离出多个子agent,零开销、启动快
@@ -0,0 +1,102 @@
1
+ DeepThink Agent 演示数据
2
+ ========================
3
+
4
+ 这份文件用于演示工具调用链路中的 read_file / edit_file / grep / glob。
5
+ 它不是程序运行所必需的,删掉也可以,只是对应的演示命令会失败。
6
+
7
+ 一、工具注册机制
8
+
9
+ 所有工具都实现统一的 ToolDefinition 接口,通过 ToolRegistry 注册:
10
+
11
+ registry.register(...allTools);
12
+
13
+ 注册后工具并不会立刻全部塞进模型的上下文里。工具被分成两类:
14
+ - 常驻工具:直接出现在 system prompt 的工具清单中;
15
+ - 延迟工具(deferred):只在清单里留一行摘要,模型需要时先调用
16
+ tool_search 把对应工具"提升"为可用状态。
17
+
18
+ 这么设计是为了控制 token。工具数量一多,光是工具描述就能吃掉几千 token,
19
+ 而大部分对话其实只用到其中少数几个。
20
+
21
+ 二、Agent 主循环
22
+
23
+ agentLoop 是一个 while 循环,每一轮:
24
+ 1. 调 streamText,把 messages 和当前可用工具交给模型;
25
+ 2. 消费 fullStream,遇到 text-delta 就打到终端,遇到 tool-call 就执行;
26
+ 3. 把工具结果作为新的消息追加回 messages,进入下一轮。
27
+
28
+ 循环有三道刹车:
29
+ - MAX_STEPS:最多 50 轮,防止模型无限自我调用;
30
+ - 工具级循环检测:同一个工具用相同的参数反复调用会被判定为"卡住",
31
+ 先警告,再熔断;
32
+ - token 预算:累计超过 500000 token 直接停止。
33
+
34
+ 三、上下文管理
35
+
36
+ 上下文按"管道"组装,每一段是一个 pipe:
37
+
38
+ new PromptBuilder()
39
+ .pipe('coreRules', coreRules())
40
+ .pipe('toolGuide', toolGuide())
41
+ .pipe('memoryContext', memoryContext(memoryStore))
42
+ .pipe('ragContext', ragContext(vectorStore));
43
+
44
+ 这样做的好处是每一段可以单独测试,也可以按需增删。记忆检索的结果和
45
+ 知识库检索的结果都只是管道里的一节,不影响主循环。
46
+
47
+ 四、RAG 检索
48
+
49
+ 文档先按标题和长度切成片段(chunkDocument),再用 embedding 模型转成向量,
50
+ 存进 sqlite-vec 的虚拟表。检索时把 query 也转成向量,用 KNN 找最近的若干片段。
51
+
52
+ 这里的向量检索是真正的向量检索,不是关键词匹配 —— 所以它能处理
53
+ "部署流程"和"上线步骤"这种字面不同但语义相近的提问。
54
+
55
+ 五、记忆系统
56
+
57
+ 记忆是跨会话的:写进记忆的内容会落盘成 markdown 文件,下次启动时重新加载。
58
+ 和 RAG 的区别在于,记忆是模型主动决定要记住的,而 RAG 是导入的文档。
59
+
60
+ 记忆需要定期清理 —— 文件里引用的路径可能已经不存在了,同一条信息也可能
61
+ 被重复记了两次。这就是 dream(记忆整理)命令要解决的问题。
62
+
63
+ 六、安全钩子
64
+
65
+ HookPipeline 提供 pre / post 两个挂载点:
66
+ - pre:工具执行前,可以审计、拦截;
67
+ - post:工具执行后,可以修改输出。
68
+
69
+ 示例里注册了两个钩子:文件写入操作会被记进审计日志;bash 的输出会被
70
+ 自动加上时间戳前缀。
71
+
72
+ 七、子 Agent
73
+
74
+ spawn 工具可以开一个子 Agent 去跑独立的子任务。子 Agent 有自己的消息历史,
75
+ 不污染主对话的上下文。深度和并发都有上限,防止递归失控。
76
+
77
+ 八、插件
78
+
79
+ 插件是运行时加载的工具包。supabase 插件会注册 list_tables / query / insert
80
+ 三个工具。插件没配置连接信息时会自动降级到 Mock 模式,方便本地演示。
81
+
82
+ 九、演示命令速查
83
+
84
+ 测试bash → 执行一条 shell 命令
85
+ 测试glob → 按通配符找文件
86
+ 测试搜索 → 在文件内容里搜索
87
+ 测试截断 → 读一个较大的文件,触发输出截断
88
+ 测试编辑 → 修改本文件里的某段文字
89
+ 测试并发 → 同时发起多个工具调用
90
+ 测试死循环 → 故意触发循环检测与熔断
91
+ 测试重试 → 模拟 429 限流和自动重试
92
+
93
+ 十、设计取舍
94
+
95
+ 这个项目里最花时间的不是"让模型能用工具",而是"让模型在失控时能停下来"。
96
+ 工具调用这件事本身很简单 —— 模型返回一个 tool-call,你执行,把结果塞回去,
97
+ 再问一次。真正麻烦的是模型会:
98
+ - 用同样的参数反复调用同一个工具;
99
+ - 在几十轮里逐渐跑偏;
100
+ - 单次读入一个巨大的文件把上下文撑爆。
101
+
102
+ 所以循环检测、token 预算、输出截断这些"不性感"的部分,反而是代码量最大的地方。