手把手教你逆向Claude Code:如何监控AI每一次'内心独白'?
通过技术手段逆向分析Claude Code的API交互过程,揭秘AI编程之王的多模型协作策略、精巧的系统提示词设计和智能工具调用机制。
手把手教你逆向Claude Code:如何监控AI每一次"内心独白"?
来源: 掘金文章
作者: 子昕AI编程
发布时间: 2025-01-15
文章摘要
通过技术手段逆向分析Claude Code的API交互过程,揭秘AI编程之王的多模型协作策略、精巧的系统提示词设计和智能工具调用机制,手把手教你监控AI的每一次"内心独白"。
正文内容
大家好,我是子昕,一个干了10年的后端开发,现在在AI编程这条路上边冲边摸索,每天都被新技术追着跑。
最近几天我发现一个有趣的现象:作为 Claude Pro 订阅用户,我明显感觉到 Claude Code 有点降智
了,不如之前那么聪明。
这让我突然想起一个经典问题——我们能否偷看一下 AI 的"小抄"?
作为一个有着强烈好奇心的程序员,我决定对 Claude Code 来一场"开膛破肚"式的逆向分析。毕竟,既然它被誉为"AI编程工具之王",那我就要看看它的王座到底是怎么坐稳的。
经过一番折腾,我发现了 GitHub 上一个对Claude Code进行逆向的项目:claude-code-reverse
这个项目可以让我们实时拦截和分析 Claude Code 与服务器的所有通信,相当于给 AI 装了个窃听器
。
为什么要逆向 Claude Code?
在开始动手之前,先说说为什么要这么做:
- 技术好奇心:Claude Code 凭什么能做到比其他 AI 编程工具更强?
- 成本透明度:作为 Pro 用户,我想知道每次对话到底消耗了哪个模型,用了多少 tokens
- 学习借鉴:了解顶级 AI Agent 的设计思路,对我们自己开发 AI 应用有巨大价值
- 质量监控:当感觉 AI 表现异常时,可以通过日志分析找到原因
准备工作:工具箱清单
在开始这场"技术侦探"之旅前,你需要准备:
- Claude Code:废话,没有目标怎么逆向
- Node.js 环境:用于安装 js-beautify
- 一颗不怕搞坏东西的心:记得备份,万一搞砸了别哭
第一步:定位"目标"
首先要找到 Claude Code 的真身。在命令行执行:
which claude
通常会得到类似这样的结果:
/opt/homebrew/bin/claude
但这只是个"替身"!在 Mac 上,这通常是一个软链接。我们需要找到真正的 cli.js
文件:
ls -l /opt/homebrew/bin/claude
你会看到它指向了真正的安装位置:
/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js
这就是我们要"动手脚"的地方!
第二步:美化代码,让它"可读"
Claude Code 的代码是压缩过的,就像一团乱麻。我们需要先让它变得人类可读:
# 进入 Claude Code 安装目录
cd /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/
# 备份原文件(这一步很重要!)
mv cli.js cli.bak
# 安装代码美化工具
npm install -g js-beautify
# 美化代码
js-beautify cli.bak > cli.js
现在 cli.js
就变成了格式良好、可读性强的代码。
第三步:植入"间谍代码"
这是整个过程中最关键的一步。我们要在 cli.js
中植入监控代码,让它把所有与 LLM 的对话都记录下来。
3.1 添加基础监控模块
在文件开头 #!/usr/bin/env node
这行之后,添加我们的"间谍模块":
间谍代码如下,直接复制粘贴即可:
// ============= 间谍模块开始 =============
import fs from "fs";
import path from "path";
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const LOG_PATH = path.resolve(__dirname, 'messages.log');
// 每次启动时创建新的日志会话
fs.writeFileSync(
LOG_PATH,
`---Session ${new Date()}---\n`
);
function isAsyncIterable(x) {
return x && typeof x[Symbol.asyncIterator] === 'function';
}
const ts = () => new Date().toISOString();
function uid() {
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}
// 这个函数负责拦截流式响应,记录工具调用的详细信息
function tapIteratorInPlaceWithTools(inner, onFinal) {
if (!inner) return inner;
const TAPPED = Symbol.for('anthropic.tap.iterator');
if (inner[TAPPED]) return inner;
Object.defineProperty(inner, TAPPED, { value: true, configurable: true });
const byteLen = s =>
typeof Buffer !== 'undefined'
? Buffer.byteLength(s, 'utf8')
: new TextEncoder().encode(s).length;
const makeWrapper = getOrigIter => function() {
const it = getOrigIter();
let text = '';
const open = new Map();
const done = [];
const PREVIEW_CAP = Infinity;
const start = (id, name) => {
if (id == null || open.has(id)) return;
open.set(id, {
id,
name: name||'unknown',
startedAt: Date.now(),
inputBytes: 0,
preview: ''
});
};
const delta = (id, chunk) => {
if (id == null) return;
if (!open.has(id)) start(id);
const rec = open.get(id);
if (!rec) return;
const s = typeof chunk==='string' ? chunk : JSON.stringify(chunk||'');
rec.inputBytes += byteLen(s);
if (rec.preview.length < PREVIEW_CAP) {
rec.preview += s.slice(0, PREVIEW_CAP - rec.preview.length);
}
};
const stop = id => {
const rec = open.get(id);
if (!rec) return;
open.delete(id);
const finishedAt = Date.now();
done.push({
...rec,
finishedAt,
durationMs: finishedAt - rec.startedAt
});
};
const finalizeDangling = err => {
for (const rec of open.values()) {
done.push({
...rec,
finishedAt: Date.now(),
durationMs: Date.now() - rec.startedAt,
errored: err ? (err.stack||String(err)) : undefined
});
}
open.clear();
};
return (async function*() {
try {
for await (const ev of it) {
// 记录每个事件
const logEntry = {
timestamp: ts(),
event: ev,
type: ev.type || 'unknown'
};
fs.appendFileSync(LOG_PATH, JSON.stringify(logEntry, null, 2) + '\n');
yield ev;
}
} catch (err) {
finalizeDangling(err);
throw err;
}
})();
};
return makeWrapper(() => inner);
}
// ============= 间谍模块结束 =============
3.2 找到正确的注入点
现在我们需要找到在 cli.js
中注入监控代码的位置。搜索处理 API 调用的函数,寻找类似这样的模式:
async function makeRequest(options) {
// ... 现有代码
}
在 API 调用之前插入我们的间谍代码:
// 在 API 调用之前添加监控
const monitoredResponse = tapIteratorInPlaceWithTools(response, () => {
// 记录最终结果
fs.appendFileSync(LOG_PATH, `---Session End ${new Date()}---\n\n`);
});
第四步:测试我们的"窃听器"
现在让我们测试监控系统:
# 启动 Claude Code
claude
# 问一个简单的问题
Describe the project structure
你应该会在 Claude Code 安装目录中看到一个 messages.log
文件生成。这个文件包含了所有被拦截的通信!
第五步:分析结果
打开日志文件,你会看到关于以下内容的详细信息:
- API 请求/响应:完整的 HTTP 通信
- 模型选择:每个任务使用了哪个模型
- Token 使用:详细的 token 消耗
- 工具调用:所有工具调用及其参数
- 系统提示词:指导 Claude Code 行为的复杂提示词
关键发现
1. 多模型协作策略
Claude Code 并不只使用一个模型。它会智能地为不同任务选择不同的模型:
- Opus:用于复杂推理和代码生成
- Sonnet:用于常规任务和简单查询
- Haiku:用于快速响应和基本操作
2. 精巧的系统提示词设计
系统提示词非常详细且精心设计,涵盖:
- 角色定义:Claude Code 能力的明确定义
- 工具使用指南:每个工具的详细说明
- 错误处理:全面的错误恢复策略
- 上下文管理:智能的上下文压缩和保留
3. 智能的上下文管理
当对话变长时,Claude Code 会自动压缩历史上下文,保留关键信息的同时节省 token 消耗。这个功能通过专门的压缩提示词实现。
4. 工具调用的精细化设计
Claude Code 定义了丰富的工具集,包括:
- 文件系统操作(读取、写入、搜索)
- 代码执行(Bash、Python 等)
- 任务管理(TodoWrite)
- IDE 集成工具
- 子代理系统(Task)
实际应用:解决"降智"问题
通过日志分析,我发现我感觉到的"降智"现象可能有几个原因:
- 模型选择策略变化:可能是为了控制成本,某些任务改用了较轻量的模型
- 上下文压缩过于激进:重要信息在压缩过程中丢失
- 工具调用链过长:复杂任务的多步骤推理被分散到多个工具调用中
总结
通过这次"技术侦探"之旅,我不仅了解了 Claude Code 强大秘密的冰山一角,还学到了一些 AI Agent 设计的宝贵经验:
- 多模型协作比单一模型更高效
- 精心设计的系统提示词是关键
- 工具系统的丰富性决定了能力上限
- 上下文管理策略影响对话质量
这只是开始
需要说明的是,这次分析只是通过一个简单的"描述项目结构"需求,初步了解了如何进行逆向分析。Claude Code 的真正实力远不止于此!
后面我计划通过更复杂的场景来深入挖掘:
复杂编程任务
:看它如何处理多文件重构、架构设计等高难度任务性能优化场景
:分析它如何进行代码审查和性能调优调试和问题解决
:观察它的错误诊断和修复策略项目搭建全流程
:从零开始创建一个完整项目的思维过程Sub-Agent 系统
:深入了解它的多智能体协作机制
每一个场景都会揭示 Claude Code 更深层的设计哲学和技术细节。如果你对某个特定场景特别感兴趣,欢迎留言告诉我!
如果你也想探索 AI 的内心世界,不妨试试这个方法。记住,好奇心是程序员最宝贵的品质!
相关图片
文章包含多张截图,展示了逆向分析的过程和结果:
- 代码植入过程截图
- API 拦截代码示例
- 监控日志文件生成
- 可视化分析界面
- 多模型协作流程图
- 工具调用序列图
- 系统提示词详细内容
这些图片详细展示了整个逆向分析的技术细节和发现结果。
本文基于掘金的子昕AI编程的原创作品。你可以在 https://juejin.cn/post/7535400490835656740 找到原文。