RussellLuo

让思想在文字间徜徉

自去年10月推出以来,Agent Skills迅速成为Claude Agent产品的核心组件,并在社区中得到了广泛的采纳和应用。关于Skills的介绍,网上已经有很多文章。本文尝试从底层原理的角度,探讨Agent Skills是如何工作的。

按照官方定义,Skills是一种基于文件系统的资源,用于为Agent提供特定领域的专业知识,从而将其转变为专家。理解Skills的前提,在于先厘清Agent的基本原理。

ReAct Agent

现代Agent都是基于ReAct模式构建的。ReAct的核心思想是将大语言模型的推理能力(Reasoning)与行动执行(Acting)相结合,使Agent能够反复思考问题、使用工具,并根据观察结果采取行动,从而实现用户目标。

ReAct模式

早期采用ReAct模式的Agent,效果并不理想。随着LLM能力的持续演进,特别是函数调用(Function Calling)的引入,ReAct模式的效果得到了显著改善,使得Agent能够更可靠且高效地完成任务。

以天气查询为例,从上下文(Context)的角度来看,ReAct Agent的运行过程大致如下:

1
2
3
4
5
System: You are a helpful weather assistant.
User: What is the weather like in Chengdu?
Assistant: ToolCall(name="get_weather", args={"location": "Chengdu"})
User: ToolOutput(result={"weather": "Sunny", "temperature": "22°C"})
Assistant: The weather in Chengdu is Sunny with a temperature of 22°C.

Claude Code

作为一个现代Agent系统,Claude Code也遵循了ReAct模式。我们在揭秘Claude Code:自主式编程中介绍过它的核心架构:

Claude Code自主式编程架构

可以看出,Claude Code与常规Agent(如天气查询助手)最大的不同之处在于:它工作在操作系统之上,几乎所有的工具都是围绕文件系统和Shell命令构建而成!

以“查看文件并创建一个Hello World函数”为例,Claude Code运行过程中的Context大致如下:

1
2
3
4
5
6
7
8
9
System: You are Claude Code, Anthropic's official CLI for Claude.
User: What files are there?
Assistant: ToolCall(name="Bash", args={"command": "ls"})
User: ToolOutput(result="[README.md]")
Assistant: There is only one file named README.md.
User: Create a hello world function in Python.
Assistant: ToolCall(name="Write", args={"file_path": "hello_world.py", "content": "def hello_world():\n print('Hello, World!')\n\nif __name__ == '__main__':\n hello_world()"})
User: ToolOutput(result="Created `hello_world.py` with a simple hello world function.")
Assistant: I've created a simple Python file with a "Hello, World!" function.

上下文管理

有了对ReAct Agent和Claude Code的基本认识,我们再来讨论一个关键话题——上下文管理。

了解大语言模型的读者可能知道,LLM的上下文有两个重要特征:

  1. 上下文窗口大小限制:LLM的上下文窗口大小是有限的(早期GPT 3仅有2048个token),虽然这个大小在持续增长(比如最新Claude Sonnet 4.5已支持百万token),但仍然是有上限的。
  2. 上下文过载导致性能下降:即使最先进的LLM支持长上下文(如百万token),但如果上下文内容过多,其性能也会显著下降。除了经典的Lost in the Middle,还会出现上下文污染(Context Poisoning)、上下文混淆(Context Confusion)等各种问题。感兴趣的读者可以进一步参考How Long Contexts Fail

因此,如何有效地管理上下文,成为了Agent设计中的一个重要课题。常见的上下文管理策略包括检索增强(RAG)、上下文总结(Context Summarization)、上下文隔离(Context Quarantine)和上下文卸载(Context Offloading)等。本文的讨论重点关注Context Offloading。

关于Context Offloading,How to Fix Your Context一文给出了以下定义:

上下文卸载(Context Offloading)是指将信息存到LLM的上下文之外,通常借助能管理数据的工具来实现。

而该文引用的Anthropic原文The “think” tool中,则这样指出:

这个“think”工具特别适合用在那些仅凭用户提问、Claude信息不够没法直接回答的情况,还有那些需要处理外部信息(比如工具返回的结果)的场景。比起深度思考那种全面推演,Claude用“think”工具做的推理会更聚焦,主要围绕模型刚发现的新信息展开。

在Claude Code中实现Context Offloading

上述关于Context Offloading的描述稍显抽象。为了便于理解,我们来设想一个问题:如果要让Claude Code支持Context Offloading,应该如何实现?

结合前文的讨论,我们知道Claude Code的能力基本建立在文件系统和Shell命令之上。因此,要实现Context Offloading,从第一性原理出发,我们能够很自然地想到以下方案:

  1. 将信息存储在文件系统,而不是一开始就全部放到LLM的上下文中;
  2. 为了让LLM知道这些信息的存在,需要在LLM的上下文中(通常在系统提示词中)记录这些信息的位置;
  3. 用户提问信息不全时,Agent可以根据系统提示词的引导,按需从文件系统寻找信息;
  4. 寻找信息的过程,需要借助文件读写和Shell命令等工具;
  5. 将获取到的外部信息加载到LLM的上下文中,以辅助完成用户的目标。

以“生成博客前端页面”为例,为了不把所有的设计指南都塞到LLM的上下文中,可以将其保存到resources/frontend-design.md文件中:

1
2
3
4
5
6
7
8
9
# Frontend Aesthetics Guidelines

Focus on:

**Typography**: Choose fonts that are beautiful, unique, and interesting...

**Color & Theme**: Commit to a cohesive aesthetic...

...

同时,在Agent的系统提示词中添加对这个文件的引用:

1
2
3
4
5
6
You are Claude Code...

You have access to the following resources:
- `resources/frontend-design.md`: Guidelines for designing the frontend UI.

...

如此一来,Agent只会在用户请求生成前端页面时,才会去额外读取resources/frontend-design.md文件中的内容,从而避免了不必要的上下文膨胀。具体来说,运行过程中的Context可能会是这样的:

1
2
3
4
5
6
7
8
9
System: You are Claude Code...\n\nYou have access to the following resources:\n- `resources/frontend-design.md`: Guidelines for designing the frontend UI.
User: Generate a blog frontend UI.
Assistant: ToolCall(name="Read", args={"file_path": "resources/fontend-design.md"})
User: ToolOutput(result="Choose fonts that are beautiful, unique, and interesting...")
Assistant: ToolCall(name="Write", args={"file_path": "app/index.html", "content": "<html><head><style>...</style></head><body>...</body></html>"})
User: ToolOutput(result="Created file `app/index.html`.")
Assistant: ToolCall(name="Write", args={"file_path": "app/styles.css", "content": "..."})
User: ToolOutput(result="Created file `app/styles.css`.")
Assistant: I've generated a simple blog frontend UI based on the guidelines.

讨论到这里,使用过Skills的读者可能发现了,如果把上述例子中的resources/重命名为skills/,那么frontend-design.md本质上就是一个Skill(参考anthropics/skills/frontend-design/SKILL.md)。

Skills的三层加载技术

至此我们可以看出,Skills的核心思想,其实也遵循了Context Offloading的上下文管理策略。当然,上述例子只是最基础的实现。

Agent Skills上下文窗口

在Anthropic的设计中,又巧妙地引入了Skills的三层加载技术,以求最大化减少LLM上下文的负担:

  1. 元数据(Metadata):可用Skills的名称、描述及其文件路径。这些信息会被预先放到上下文(系统提示词)中,以确保Agent知道有哪些Skills可以利用。
  2. 指令(Instructions):每个Skill都有一个对应的SKILL.md文件,其中包含了Skill的详细描述、使用方法和示例等信息。当Agent需要某个Skill的帮助时,它会通过Read工具读取SKILL.md文件的内容,进而将其动态加载到上下文中。
  3. 资源(Resources):除了SKILL.md文件,每个Skill还可以包含其他类型的资源文件,如配置文件、文档等。当Agent需要更具体的信息时,它会进一步读取这些资源文件的内容,从而将其加载到上下文中。

代码执行与虚拟机

除了前文讨论的内容,需要强调的是,Skills的完整能力还涉及代码执行和虚拟机:

  • 代码执行(Code Execution):某些Skills可能包含代码片段,甚至Agent为了处理任务还会动态生成代码,这些代码都需要执行。
  • 虚拟机(Virtual Machine):为了确保安全性,通常需要在一个隔离的沙盒环境(虚拟机)中管理文件系统、执行Shell命令和运行代码。

Agent Skills架构

由于篇幅所限,这里不再展开详细讨论,感兴趣的读者可以参考官方文档或者其他相关资料。

结语

通过本文的探讨,相信读者对Agent Skills有了更深入的理解。在Claude Agent产品中,Skills的实现基于Context Offloading这一上下文管理策略;而该策略的落地,则依托于ReAct模式的思想框架,以及文件系统、Shell命令等基础工具的支撑。

此外,代码执行和虚拟机也是非常重要的话题,本文限于篇幅只做了简要提及。实际上,它们不仅是Skills的关键技术,也代表着Agent未来的主流演进方向。在后续的文章中,我们将继续深入探讨这些话题,敬请期待!

引言

Claude Code伴侣(即Claude Code Mate,以下简称CCM)最初的定位是一个极简的LLM代理工具,旨在帮助开发者快速用上Claude Code和切换各种大模型。所以CCM一开始选择了纯命令行交互,以及通过YAML文件进行配置。这种方式既简洁高效,也符合开发者的使用习惯。

距离CCM第一版发布(轻松解锁Claude Code:国内用户的多元模型新玩法)已经过去了近一个月,随着自己使用的增多,以及零星收到的一些用户反馈,我发现CCM存在以下问题:

  1. 模型管理不直观:用户如果不查阅LiteLLM文档,很难知道CCM支持哪些模型,以及如何配置这些模型(比如OpenAI兼容的模型,需要加上openai/前缀)。
  2. 没有用量统计:用户在CCM中无法查看自己的用量情况,包括各个模型的请求数、输入/输出Token数和费用消耗等信息。

为了解决这些问题,我决定为CCM引入一个可视化管理后台,以提升用户体验。

PostgreSQL小插曲

了解LiteLLM的朋友可能知道,LiteLLM Proxy其实原本就提供Admin UI。然而,它对数据库的选择有特定偏好:仅支持PostgreSQL,并且明确表示不考虑SQLite等其他轻量级选项

这意味着,如果要启用Admin UI,用户必须要先安装和配置PostgreSQL。这显然与CCM的初衷——提供一个简单易用的工具——是相违背的。

幸运的是,经过一番调研,我找到了一个Python库pgserver,可以实现:

  • 可嵌入:通过pip安装依赖库,即可自动下载PostgreSQL
  • 零配置:无需用户手动设置数据库环境
  • 跨平台:支持Windows、macOS和Linux
  • 无Docker:不需要额外安装和配置Docker

于是在pgserver的帮助下,CCM成功引入了Admin UI,并做到了用户无感。

快速开始

为了保持轻量级,CCM默认不包含UI功能。如果要启用UI功能,可以通过以下命令安装:

1
2
3
4
# 使用uv(推荐)
uv pip install --system --python 3.12 "claude-code-mate[ui]"
# 或者使用pip
pip install "claude-code-mate[ui]"

安装后,使用以下命令启动UI:

1
ccm ui

打开Admin UI后,使用默认的用户名和密码(adminsk-1234567890)登录,即可进入管理后台。

可视化管理后台

LiteLLM Proxy提供的Admin UI功能很强大,其中就包括CCM第一版缺失的模型管理和用量统计功能。

模型管理

模型管理界面

LiteLLM内置支持众多提供商的模型,包括但不限于:

  • 知名官方模型(如Anthropic、OpenAI和DeepSeek等)
  • 聚合平台的模型(如OpenRouter等)
  • OpenAI兼容模型
  • Ollama本地模型

用户可以通过界面:

  • 轻松添加、编辑和删除模型,无需了解LiteLLM的特殊前缀规则。
  • 设置输入/输出的Token价格,以便准确计算费用。
  • 修改一些高级参数,如TPM/RPM、超时时间和max_tokens等。

用量统计

用量统计界面

通过用量统计功能,用户可以清晰地看到:

  • 总(或按模型)的请求次数,以及成功和失败的次数。
  • 总(或按模型)的Token数量,以及输入和输出的Token数量。
  • 总(或按模型)的费用消耗情况等。

其他功能

除了上述两个功能外,Admin UI还提供了以下一些实用功能:

  • 模型测试(Test Key):快速测试模型的可用性和效果。
  • 日志查看(Logs):实时查看请求日志,便于调试和排查问题。

模型测试界面

日志界面

以上只列举了Admin UI的部分功能,对于其他功能感兴趣的读者,可以进一步参考LiteLLM Proxy文档

结语

欢迎大家体验Claude Code Mate的新UI功能!需要说明的是,该功能只在macOS(我的开发环境)进行了测试,尚未在Windows和Linux上进行全面验证。如果你有任何问题或建议,欢迎随时在GitHub仓库中提出。

Learn about Claude Code, Anthropic’s agentic coding tool that lives in your terminal and helps you turn ideas into code faster than ever before.

Claude Code在官方文档中自称是一款Agentic(自主式,或智能体驱动)编程工具,它能够帮助开发者更高效地将想法转化为代码。那么,何为自主式编程?其背后的核心原理与技术实现又是怎样的?本文将围绕这一主题展开讨论,深入剖析Claude Code的技术架构与独到之处。

什么是自主式编程?

与传统基于静态提示词的单步代码生成工具不同,自主式编程系统以目标驱动、多步执行为核心特征。这些由大语言模型(LLM)驱动的系统能够自主执行软件开发任务,通过推理、决策、使用外部工具(如编译器、调试器、测试运行器)并基于反馈迭代优化输出结果。

自主式编程系统通常具有以下显著特点:

  • 高度自主性:能够在没有持续人工监督的情况下自主决策和执行操作
  • 环境交互性:在执行过程中与外部工具和环境进行动态交互
  • 迭代优化能力:基于中间反馈不断改进输出质量
  • 目标导向性:追求高层次的任务目标,而非仅仅对一次性指令进行响应

自主式编程代表了软件开发自动化的重要转变,从基于规则的自动化、传统机器学习模型或单次LLM调用,转向真正参与软件开发全过程的智能系统。这一变革为智能代码辅助、自主调试测试、自动化代码维护甚至自我进化的软件系统开辟了全新可能性。

Claude Code的核心架构

Claude Code自主式编程架构

如图所示,Claude Code作为典型的自主式编程工具,其核心架构包含以下几个关键组件:

  • 自主执行循环:Claude Code将大语言模型嵌入到一个执行循环中,使其能够与开发环境进行持续交互。当接收到用户的自然语言提示后,系统会从操作系统和工作区收集额外上下文信息(如文件摘要、环境状态等)。

  • 多步推理机制:在推理循环中,大语言模型会将复杂任务分解为多个子目标,生成代码或做出决策,并判断是否需要调用外部工具——包括文件读写、终端命令执行等操作。

  • 迭代优化过程:工具执行的结果会返回至循环中,作为进一步优化的反馈。这个迭代过程持续进行,直到自主系统完成任务或达到停止条件,最终结果将实时返回给用户。

案例分析:你好,世界!

为了更好地理解Claude Code的核心架构,让我们通过一个实际案例来观察其工作流程。

简单起见,我们来创建一个Python版的Hello World函数:

Hello World:输入提示词

Hello World:生成代码

Hello World:写入文件

从上述执行过程中,借助mitmproxy可以捕获到两条数据流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
================================================================================
FLOW #1 - ID: 6a662989-33d0-4e86-a984-8c39f90ed9e7
================================================================================

📤 REQUEST:
----------------------------------------
System:
System[0]:
type: text
text: You are Claude Code, Anthropic's official CLI for Claude.
System[1]:
type: text
text:
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.

...

Messages:
Message[0]:
role: user
content:
Content[0]:
type: text
text:
<system-reminder>
This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit from a todo list please use the TodoWrite tool to create one. If not, please feel free to ignore. Again do not mention this message to the user.
</system-reminder>
Content[1]:
type: text
text:
<system-reminder>
As you answer the user's questions, you can use the following context:
# important-instruction-reminders
Do what has been asked; nothing more, nothing less.
NEVER create files unless they're absolutely necessary for achieving your goal.
ALWAYS prefer editing an existing file to creating a new one.
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.


IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
</system-reminder>

Content[2]:
type: text
text:
create a hello world function in python
Tools:
...

Tool[8]:
Name: Write
Description: Writes a file to the local filesystem.

Usage:
- This tool will overwrite the existing file if there is one at the provided path.
- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.
Input Schema:
Type: object
Required: file_path, content
Properties: file_path, content

...

📥 RESPONSE:
----------------------------------------
📡 Parsed SSE Response:
Tool: Write (ID: toolu_bdrk_01Tv5P8iPpKfvXkD7jGmjLFr) - {"file_path": "/Users/russellluo/Projects/demo/hello_world.py", "content": "def hello_world():\n print(\"Hello, World!\")\n\nif __name__ == \"__main__\":\n hello_world()"}

================================================================================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
================================================================================
FLOW #2 - ID: 5b84e626-298d-4f35-a4c2-d22b1c0ab1ab
================================================================================

📤 REQUEST:
----------------------------------------
System:
...

Messages:
Message[0]:
role: user
content:
Content[0]:
type: text
text:
<system-reminder>
This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit
from a todo list please use the TodoWrite tool to create one. If not, please feel free to ignore. Again do not mention this message to the user.
</system-reminder>
Content[1]:
type: text
text:
<system-reminder>
As you answer the user's questions, you can use the following context:
# important-instruction-reminders
Do what has been asked; nothing more, nothing less.
NEVER create files unless they're absolutely necessary for achieving your goal.
ALWAYS prefer editing an existing file to creating a new one.
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.


IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
</system-reminder>

Content[2]:
type: text
text:
create a hello world function in python
Message[1]:
role: assistant
content:
Content[0]:
type: text
text:
(no content)
Content[1]:
type: tool_use
id: toolu_bdrk_01Tv5P8iPpKfvXkD7jGmjLFr
name: Write
input: {'file_path': '/Users/russellluo/Projects/demo/hello_world.py', 'content': 'def hello_world():\n print("Hello, World!")\n\nif __name__ == "__main__":\n hello_world()'}
Message[2]:
role: user
content:
Content[0]:
tool_use_id: toolu_bdrk_01Tv5P8iPpKfvXkD7jGmjLFr
type: tool_result
content: File created successfully at: /Users/russellluo/Projects/demo/hello_world.py
Tools:
...

📥 RESPONSE:
----------------------------------------
📡 Parsed SSE Response:
Created `hello_world.py` with a simple hello world function.

================================================================================

从数据流中可以看出,Claude Code处理“创建Hello World函数”这种简单任务,背后也是一个典型的多步、交互式的自主执行循环。其特点总结如下:

  1. 清晰的“思考-行动-观察”循环

    • 思考与行动(FLOW #1):这是循环的起点。LLM接收系统上下文和用户指令(create a hello world function in python)后,经过内部推理,决定采取行动——使用Write工具,它生成了完整的代码内容并指定了文件路径。
    • 观察与完成(FLOW #2):这是循环的下一步。经用户确认后,Write工具被执行,系统于是接收到了执行结果(File created successfully at...)。LLM观察到这个反馈后,判定任务已成功完成,于是决定结束循环,并生成最终响应给用户(Created 'hello_world.py' with a simple hello world function.)。
  2. 工具使用的链式依赖

    整个任务的完成依赖于工具的链式调用和结果传递:

    • tool_use(FLOW #1 响应中):LLM发起一个工具调用 (Write)。
    • tool_result(FLOW #2 请求中):环境执行工具后返回结果,并作为新的上下文输入给LLM。

    这表明自主系统的每一步决策都依赖于上一步执行后的环境状态,形成了一个闭环反馈。

  3. 状态保持与会话连续性

    • FLOW #2 的请求中包含了完整的对话历史(Messages[0], [1], [2]),其中包括最初的用户指令、LLM发起的工具调用以及工具执行的结果。
    • 这保证了LLM在每一步都有完整的上下文,能够理解当前任务所处的状态,从而做出连贯的决策。tool_use_id等字段确保了工具调用和结果之间的正确关联。

上述流程其实也是一个LLM工具使用的标准范式,感兴趣的读者可以参考Tool use with Claude

轻量级原型实现

通过前面的分析,我们已经深入了解了Claude Code的自主式编程原理。现在,让我们利用Anthropic Python SDK,构建一个轻量级的自主编程助手原型,将理论转化为实践。

下面的实现包含了简化的自主循环和Write工具,展示了如何让LLM突破纯文本生成的限制,真正与环境进行交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# Initialize Anthropic client
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

MODEL = "claude-sonnet-4"

# System prompt for Claude Code
SYSTEM = [
{
"type": "text",
"text": "You are Claude Code, Anthropic's official CLI for Claude.",
},
{
"type": "text",
"text": f"""
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.

Here is useful information about the environment you are running in:
<env>
Working directory: {CWD}
</env>
""",
},
]

# Write tool definition
WRITE_TOOL = {
"name": "Write",
"description": "Writes a file to the local filesystem.",
"input_schema": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "The absolute path to the file to write (must be absolute, not relative)",
},
"content": {
"type": "string",
"description": "The content to write to the file",
},
},
"required": ["file_path", "content"],
"additionalProperties": False,
},
}

def execute_tool(tool_name: str, tool_input: dict[str, Any]) -> str:
"""Execute tool function with user confirmation"""
if tool_name.lower() != "write":
return f"Error: Unknown tool `{tool_name}`"

# User confirmation
while True:
file_name = os.path.basename(tool_input["file_path"])
confirmation = (
input(f"""
```
{tool_input["content"]}
```
Do you want to create `{file_name}`? (Y/N) > """)
.strip()
.lower()
)
if confirmation == "y":
return write_file(tool_input["file_path"], tool_input["content"])
else:
return f"User rejected write to `{file_name}`"

def write_file(file_path: str, content: str) -> str:
"""Write content to a file at the specified path"""
try:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, "w", encoding="utf-8") as f:
f.write(content)
return f"File created successfully at: {file_path}"
except Exception as e:
return f"Error writing file: {str(e)}"

def run_agent_in_loop(messages: list[dict], max_turns: int = 5) -> None:
"""Run the coding agent in a loop"""
for turn in range(max_turns):
# Call Claude
response = client.messages.create(
model=MODEL,
max_tokens=1024,
system=SYSTEM,
tools=[WRITE_TOOL],
messages=messages,
)

response_content = response.content[0]

# Text response - task completed
if response_content.type == "text":
print(f"\n⏺ {response_content.text}")
break

# Tool call
elif response_content.type == "tool_use":
tool_use = response_content

# Execute tool
tool_result = execute_tool(tool_use.name, tool_use.input)

# Update message history
messages.extend(
[
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": tool_use.id,
"name": tool_use.name,
"input": tool_use.input,
}
],
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": tool_result,
}
],
},
]
)

安装相关依赖,并设置API密钥:

1
2
pip install anthropic
export ANTHROPIC_API_KEY=<your_api_key>

运行上述代码lite_claude_code_v1.py完整版),输入指令create a hello world function in python,即可看到类似Claude Code的交互式执行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ python lite_claude_code_v1.py 
==================================================
🤖 Lite Claude Code (^C to exit)
==================================================

> create a hello world function in python

```
def hello_world():
print("Hello, World!")

if __name__ == "__main__":
hello_world()
```
Do you want to create `hello_world.py`? (Y/N) > y

⏺ I've created a simple Python file with a "Hello, World!" function.
...

>

至此,我们成功地构建了一个轻量级的Claude Code,并让它完成了一个基本的编程任务!感兴趣的读者可以进一步扩展这个原型,例如添加更多的工具(如ReadBash等),以增强其功能和实用性。

结语

通过本文的探索,我们剖析了Claude Code作为自主式编程工具的核心架构,揭示了其“目标驱动、多步执行”的工作原理。从简单的Hello World案例到背后的数据流分析,我们见证了其如何通过思考-行动-观察的自主循环,打通从自然语言指令到实际代码生成的完整路径。

此外,我们还构建了一个轻量级的自主编程助手原型,展示了如何利用Anthropic SDK,将复杂的交互逻辑简化为可执行的Python代码。这一过程不仅能加深我们对自主式编程的理解,也为未来开发自己的AI辅助工具奠定了基础。

当使用Claude Code这样的AI编程助手,在惊叹于它神奇能力的同时,你是否也曾好奇:它究竟是如何工作的?作为一款AI应用,Claude Code的魔法隐藏在它与大型语言模型(LLM)的每一次对话中。但遗憾的是,Claude Code是闭源软件,我们无法从源代码中寻找答案。

那么,如何才能揭开它的神秘面纱呢?突破口就藏在网络通信中!通过抓取和分析Claude Code与后端服务的网络流量,我们可以从中洞悉它的核心工作机制。今天,就让我们借助mitmproxy这个强大工具,开启这段技术探索之旅。

为什么需要网络分析?

在AI应用领域,提示词工程堪称核心机密。Claude Code的出色表现,既得益于Claude模型本身强大的能力,也离不开其精心设计的提示词模板和与LLM的交互策略。通过网络流量分析,我们可以:

  • 还原系统提示词(System Prompt)的设计
  • 分析不同任务对应的用户提示词(User Prompt)
  • 探索Agent的内部工具调用(Tools)
  • 研究上下文管理机制(Context Management)
  • 了解模型参数的配置细节和优化策略

这种“逆向工程”不仅能满足技术好奇心,更能为我们的AI应用开发提供宝贵参考。

mitmproxy:网络分析的瑞士军刀

mitmproxy是一个免费、开源的交互式HTTPS代理,可以用于拦截、检查、修改和重放网络流量。它提供了三个命令行/控制台工具:

  1. mitmproxy:一个基于ncurses的交互式工具,提供了一个带有快捷键的终端界面,允许用户实时查看和操作流量。这是最强大的交互模式。
  2. mitmdump:一个类似于tcpdump的命令行工具,不提供交互式界面,而是将捕获的流量直接输出到控制台或保存到文件。它非常适合与其它命令行工具(如grep,jq)结合使用,进行自动化日志记录和批量处理。
  3. mitmweb:一个基于Web的图形化界面,在浏览器中提供一个类似开发者工具的界面,对于不习惯命令行操作的用户来说更加友好。

对于Python开发者而言,安装mitmproxy只需一行命令(查看其他安装方式):

1
2
uvx mitmproxy
# or `pip install mitmproxy`

由于现代应用普遍使用HTTPS,我们还需要安装CA证书::

1
mitmproxy

运行后访问mitm.it,下载并安装对应系统的证书即可。

Claude Code流量初探

为了演示更直观,我们这里启动mitmweb:

1
mitmweb

mitmproxy默认监听8080端口(可以通过-p参数指定其他端口),因此需要将系统代理设置为该地址:

1
2
export http_proxy=http://127.0.0.1:8080
export https_proxy=http://127.0.0.1:8080

随后,我们可以选择一个项目,打开Claude Code并输入hi

claude-code-input-hi

如果一切顺利,mitmweb将会捕获到相关的网络流量:

claude-code-hi-flows-request

claude-code-hi-flows-response

至此,我们成功完成了对Claude Code的首次流量捕获!可以看到,这些流量包含了Claude Code与后端服务之间完整的HTTP请求和响应。在每一条数据流中,请求体包含了与提示词相关的输入信息,而响应体则包含了LLM生成的回答内容。

虽然请求和响应数据已被捕获,不过仔细观察我们会发现:

  1. 请求中的JSON数据内容较多,我们主要想分析与提示词有关的内容,如systemmessagestools等字段。
  2. 请求中的提示词往往是多行文本,都被显示在同一行,导致信息冗长且难以阅读。
  3. 响应中的SSE(Server-Sent Events)数据是以流的形式返回,导致它们被拆分成了多个片段,需要重新拼接起来。

为了解决上述几个问题,我们需要进一步使用mitmproxy的插件机制,来对流量进行定制化的捕获和解析。

mitmproxy插件机制

mitmproxy的插件机制基于事件驱动,其核心原理是:mitmproxy在代理流量的生命周期中,会在特定时刻(如收到请求、收到响应、发生错误等)触发预设好的事件。

开发者编写的插件(称为addon)实际上就是一个事件监听器。你通过创建一个类,并定义对应的事件处理方法(如request(self, flow)response(self, flow)),mitmproxy就会在相应事件发生时自动调用你的方法。

在这个方法内部,你可以通过操作flow对象(它包含了单个HTTP请求/响应的所有信息)来:

  1. 查看(Inspect):读取URL、头、内容等。
  2. 过滤(Filter):只处理你关心的特定流量。
  3. 修改(Modify):改变请求或响应的内容。
  4. 输出(Output):将信息打印到控制台或保存到文件。

这种机制让你能以非侵入的方式,对经过代理的流量进行精确的解析和处理。对编写插件感兴趣的读者,可以参考官方示例了解更多详情。

Claude Code流量的定制化分析

接下来,我们将借助mitmproxy的插件机制,对Claude Code的流量进行优化分析。

为了兼顾数据捕获的完整性和不同用户对格式化的个性化需求,我们将整个分析流程设计为两个独立的阶段:

  1. 流量记录:捕获所有HTTP请求和响应,并以JSON Lines格式逐行输出,确保数据的完整性,同时便于后续的逐行分析。
  2. 格式化输出:逐行解析流量记录,从JSON请求中提取systemmessagestools等核心字段,并以结构化的方式展示;同时将SSE响应数据流还原为完整的JSON对象,并格式化输出以提升可读性。

最有趣的是,在AI编程时代,我们甚至无需翻阅mitmproxy文档或手动编写代码——Claude Code就能帮我们完成大部分工作。

流量记录

首先,从官网下载示例io-write-flow-file.py。然后,打开Claude Code,让它参考示例生成mitmproxy插件:

参考io-write-flow-file.py,编写一个mitmproxy脚本,用于将请求/响应对写入一行JSON Line

claude-code-jsonl-writer-prompt

生成后的jsonl-writer.py插件(完整版)可以这样使用:

1
mitmweb -s jsonl-writer.py

运行后,所有捕获到的流量都会被记录到flows.jsonl文件中:

1
tail -f flows.jsonl

格式化输出

同样地,可以让Claude Code生成一个jsonl-viewer.py脚本,用于解析flows.jsonl文件中的每一行,并对其进行格式化输出:

编写一个解析脚本jsonl-viewer.py,可以逐行解析标准输入中的jsonl内容,每一行JSON Line格式如下:

1
{"request":{"model":"claude-3-5-haiku-20241022","max_tokens":512,"messages":[{"role":"user","content":"Please write a 5-10 word title for the following conversation:\n\n[Last 12 of 14 messages]\n\nUser: \n\nClaude: (no content)\n\nUser: \n\nClaude: (no content)\n\nUser: \n\nClaude: (no content)\n\nUser: \n\nClaude: I've created a mitmproxy addon `json_logger.py` that meets your requirements:\n\n## Key Features:\n\n1. **Request Body Filtering**: For JSON requests, only keeps `system`, `messages`, and `tools` fields\n2. **SSE Response Handling**: Parses Server-Sent Events and concatenates chunks into a structured format\n3. **JSON Lines Output**: Writes each request/response pair as a single JSON line to `requests_responses.jsonl`\n\n## Usage:\n\n```bash\n# Run with mitmproxy\nmitmdump -s json_logger.py\n\n# Or with mitmweb\nmitmweb -s json_logger.py\n```\n\nThe addon captures:\n- URL, HTTP method, and status code\n- Filtered request body (JSON fields: system, messages, tools)\n- Parsed response body (with special SSE chunk concatenation)\n- Content type information\n\nFor SSE responses, it extracts individual chunks and provides both the raw chunks array and concatenated content, making it easy to reconstruct streaming responses.\n\nRespond with the title for the conversation and nothing else."}],"system":[{"type":"text","text":"Summarize this coding conversation in under 50 characters.\nCapture the main task, key files, problems addressed, and current status.","cache_control":{"type":"ephemeral"}}],"temperature":0,"metadata":{"user_id":"user_f3bbbca5b09b9b5ebf3916e21fd50d585787d9b818164f839f3f9b9b4f58bfe2_account__session_08316681-f6ac-4fcb-80c6-142f1e956373"},"stream":true},"response":"event: message_start\ndata: {\"type\": \"message_start\", \"message\": {\"id\": \"msg_2d398cea-df7d-4792-8017-69fd8383b883\", \"type\": \"message\", \"role\": \"assistant\", \"content\": [], \"model\": \"anthropic/claude-3.5-haiku-20241022\", \"stop_reason\": null, \"stop_sequence\": null, \"usage\": {\"input_tokens\": 0, \"output_tokens\": 0}}}\n\nevent: content_block_start\ndata: {\"type\": \"content_block_start\", \"index\": 0, \"content_block\": {\"type\": \"text\", \"text\": \"\"}}\n\nevent: content_block_delta\ndata: {\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {\"type\": \"text_delta\", \"text\": \"M\"}}\n\nevent: content_block_delta\ndata: {\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {\"type\": \"text_delta\", \"text\": \"itmproxy JSON\"}}\n\nevent: content_block_delta\ndata: {\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {\"type\": \"text_delta\", \"text\": \" Logger for\"}}\n\nevent: content_block_delta\ndata: {\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {\"type\": \"text_delta\", \"text\": \" API\"}}\n\nevent: content_block_delta\ndata: {\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {\"type\": \"text_delta\", \"text\": \" Request\"}}\n\nevent: content_block_delta\ndata: {\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {\"type\": \"text_delta\", \"text\": \"/Response Tracking\"}}\n\nevent: content_block_stop\ndata: {\"type\": \"content_block_stop\", \"index\": 0}\n\nevent: message_delta\ndata: {\"type\": \"message_delta\", \"delta\": {\"stop_reason\": \"end_turn\"}, \"usage\": {\"input_tokens\": 284, \"output_tokens\": 11}}\n\nevent: message_stop\ndata: {\"type\": \"message_stop\"}\n\n","flow_id":"75bbd359-8b0c-42ef-a376-fbd4c4660693","duration":2.0904617309570312}

解析要求:

  • 针对request数据,只提取system、messages和tools的内容,相关text和content字段包含多行文本,需要格式化输出
  • 针对response数据,需要将sse拼接成完整的json对象,然后其中的text字段包含多行文本,也需要格式化输出

claude-code-jsonl-viewer-prompt

生成后的jsonl-viewer.py脚本(完整版)可以用于解析flows.jsonl文件:

1
cat flows.jsonl | python jsonl-viewer.py | less

解析后的输出结果如下:

claude-code-jsonl-viewer-output

与原始的mitmweb界面相比,经过处理后的数据可读性有了显著提升:

  • 每条HTTP数据流独立呈现,请求和响应内容清晰分离
  • 提示词内容以整洁的多行文本格式展示,便于直观理解发送给LLM的具体指令
  • 响应中的SSE数据流被拼接为完整内容,更易于阅读

通过这种结构化的展示方式,每次AI对话的完整流程都变得清晰可追溯,大大提升了分析和调试的效率。

结语

通过本文的探索,我们见证了mitmproxy在流量捕获、插件定制与数据解析方面的卓越能力。同时结合Claude Code的AI辅助,我们可以高效编写和调试mitmproxy插件,为揭秘Claude Code自身的工作原理提供了强大的助力。

魔法的大门已然打开,而深处仍有更多奥秘。从提示词构造、Agent工具调用到上下文管理,Claude Code的核心机制,正静候每一位探索者的到来。

如果你对AI编程助手感兴趣,一定听说过Claude Code。遗憾的是,由于网络环境和账号限制,国内用户直接使用它非常困难。目前比较流行的方案是借助Claude Code Router,但经过笔者测试,配置并成功用起来仍有一定门槛。

作为Vibe Coding玩家,笔者尝试实现了一个简易版本的替代方案 —— Claude Code Mate,不仅能让你用上Claude Code,还能自由切换其他主流大模型。

有意思的是,Claude Code Mate项目本身的大部分代码,也是Vibe Coding的产物(由Claude Code生成)。

Claude Code Mate

项目地址:https://github.com/RussellLuo/claude-code-mate

Claude Code Mate(以下简称CCM)是一个专门为Claude Code设计的辅助工具,通过内置的LiteLLM Proxy,让你可以灵活接入多种大模型。它的最大特点是安装便捷、配置简单,适合不想折腾的开发者用户。

CCM能做什么

  1. 无障碍使用Claude Code

    无需直接注册Anthropic账号,只需配置一个第三方API Key(例如通过OpenRouter平台获取),即可在国内网络环境下稳定使用Claude Code。

  2. 一键切换多种模型

    除了Claude系列模型,你还可以方便地切换到Gemini、DeepSeek、GPT等其他大模型。只需在配置文件中简单设置,就能按需选用不同模型。

  3. 比同类方案更便捷

    其他流行方案如Claude Code Router(CCR)也能实现类似功能,但由于CCR有Router(路由器)和Transformer(转换器)等功能,导致理解和配置较复杂。相比之下,CCM只聚焦于切换不同的模型和提供商,并将所有功能打包成一条命令,安装后简单设置就能使用。

快速开始

以OpenRouter为例,只需几步即可完成:

1
2
3
4
5
6
7
8
9
10
11
12
pip install claude-code-mate

# 启动LiteLLM Proxy
export OPENROUTER_API_KEY=your-api-key
ccm start

# 设置环境变量(按`ccm start`的输出指示)
export ANTHROPIC_BASE_URL=http://0.0.0.0:4000
export ANTHROPIC_AUTH_TOKEN=sk-xxx

# 现在可以正常使用Claude Code了
claude --model claude-3.5-haiku

总结

Claude Code Mate的优势在于开箱即用和配置简便,特别适合以下用户:

  • 想用Claude Code但受限于网络环境的国内用户
  • 希望尝试不同大模型并集中管理配置
  • 喜欢简洁方案,不想花费太多时间进行其他定制

如果你正在寻找一个简单有效的方式使用Claude Code和切换各种大模型,不妨试试Claude Code Mate这个工具。

0%