本文学习过程中主要参考了https://github.com/shareAI-lab/learn-claude-code

s01:Agent Loop

最基本最小型的保证一个agent跑起来的loop,输入接受一个用户提问,然后在一个消息队列message[]中被维护起来,接下来进入一个while true:的循环,每次都根据消息调用LLM,检查是否符合停止信号,是则任务终止,否则执行tool use并重复这个loop

s02:Tool Use

工具调用,非常容易理解,就是在s01的基础上,不再是仅通过一个bash去完成复杂的操作了,而是维护了一个tool table,根据不同的需求场景去分别调用

深入claude code源码:
不同于普通的工作调用,一般而言我们会认为他们是按照顺序一个一个顺着来的,但A出的做法是绑定batch一块用

claude会执行一个**并发安全判断:isConcurrencySafe()**的判断,如果是安全的,就把几个tool use绑定一个batch,也就是判断出几个任务是否可以并行,还是必须分开执行

绑定batch使用的是partitionToolCalls()函数,并且在真正执行前会进行多重验证,同时支持流式工具执行,也就是在model生成过程中已经启动好了tool use的准备(比如read file已经开跑了)

s03:Permission

权限,即一个action的执行是否可行需要走三道判断:

  • 坚决不允许,匹配到直接拒绝
  • 正则匹配,决定什么action需要问用户,指定检查条件
  • user input

深入claude code源码:
权限是一整个流水线一起决定的
检验机制非常丰富

s04:hooks

在agent系统中的某些位置外挂 / 插入一些自定义的辅助的逻辑
一个很简单的例子,你需要在agent完成输出之后去搞日志log,这就是hook的作用体现

深入claude code源码:
hook事件多,且在hook allow的时候也会触发安全检查
stophookactive,即便agent已经达成结束条件,如果某个hook的事件还没有结束,也会再来一轮

s05:TodoWrite

agent的计划书,带有状态的动态更新的list
只能增加规划能力,对于执行能力没有任何影响

cc源码:
分两条线维护TodoWrite:
v1:单会话内的todo list,不写入进数据历史中
v2:整个系统长期运作所需要的整体的所有任务完成状态 情况等

s06:subagent

主agent执行过程中由于任务太多导致关键信息被稀释,所以要引入subagent来解决这个问题,也就是每个子agent单独负责一部分任务

parent agent只给sub agent一个任务描述和干净的上下文,而sub agent返回的就是任务结论

同时禁止一个sub agent再搞这种sub agent套娃的方式,并且保持所有agent同时被allow所约束

cc源码:

  • normal subagent:全新干净窗口,当前prompt,目标是上下文隔离
  • fork subagent:尽可能复用parent agent上下文,细节还涉及到缓存相关
  • general subagent:通用性的,什么任务都差不多,与第一类类似

s07:skill loading

所有的skills都在任务启动的加载进去,但是只有使用的时候真的去看去用
没啥特别的,这个用的也挺多,比较熟悉

s08:Compact context

分为4层压缩结构:

  • tool_res_budget:处理掉超级大输出的工具调用
  • snip_compact:裁剪掉中间的历史会话
  • mirco_compact:把之前使用工具的记录用通用占位符代替,类似于清理旧邮件
  • compact_historty:用LLM做全量的摘要总结

cc源码:

CC 源码 query.ts 中的真实顺序:

  1. applyToolResultBudget(L379):先处理大结果,确保完整内容落盘
  2. snipCompact(L403):裁中间消息
  3. microcompact(L414):旧结果占位
  4. contextCollapse(L441):独立的上下文管理系统(教学版无)
  5. autoCompact(L454):LLM 全量摘要

教学版的 budget → snip → micro 顺序与此一致。教学版没有 contextCollapse 机制。

s09:Memory

存储 加载 提取 整理

  • 存储:markdown文件 + 索引,每次活得新记忆的时候重构索引
  • 加载:索引常驻system / 相关记忆按需输入
  • 写入:每轮对话之后捕捉对话中用户提到的要求 / 偏好
  • 整理:在文件数达到阈值时触发,让 LLM 去重、合并矛盾、淘汰过时记忆

cc源码:
不直接用RAG,而是让agent自行挑选出来最相关的记忆片段

在真实场景中,并不是主agent负责更新memory,而是有一个fork agent在维护

s10:system prompt

主要是prompt的拼接机制

四个 section,两种加载策略:

Section 加载策略 内容 判断依据
identity 始终 你是谁、怎么做事 始终存在
tools 始终 可用工具列表 enabled_tools
workspace 始终 工作目录 始终存在
memory 按需 相关记忆内容 .memory/MEMORY.md 是否存在

按照这个表中的内容分段定义,按照需求进行拼接,基于真实状态,拒绝重复拼接

s11:Error Recovery

模式 触发 恢复动作
输出截断 max_tokens 升级 8K→64K / 续写提示
上下文超限 prompt_too_long reactive compact → 重试
临时故障 429 / 529 指数退避 + 抖动,连续 529 可切换备用模型

s12:task system

将大目标拆解为小任务,且是跨会话储存的
把所有任务构建成一个DAG,构建出来的是一个依赖关系图

s13:BackGround Tasks

把慢操作丢在后台,自己去思考其他工作 / 事宜
让快操作同步执行快速拿到结果,让慢操作挂在后台结束后通知自己

s14:cron schedule

按时间表调度工作,目的是解决一些持续性任务,如”每天早上九点给我跑测试”

执行的时候启动一个调度线程,按照时间进行检查,命中之后插入待完成的队列中

s15:agent team

agent也需要队友,队友和子agent的区别就在于生命周期,子agent就是打黑工的,执行完任务就被销毁了,队友则是多轮的,互相之间共享消息,使用一个public的文件箱来实现消息共享

s16:team protocols

承接上文,队友之间要有约定,在团队的通讯中加入了结构化协议
以结构化请求和响应协议代替单纯的文本信息

s17:Autonomous Agents

让队友之间自动的认领任务,空闲循环 + 自动认领 + work生命周期

分成work / idle两种状态,分别是队友的工作中和空闲(手上没活),然后在空闲的期间扫描工作表,如果还有剩下的待完成的工作就直接拿过来干,在一个时间阈值中没有需要完成的工作就退出(一般60s)

s18:worktree isolation

如果两个子agent同时改了一个文件,怎么办?我们需要worktree
每个agent自己有一个worktree,就在他自己的这个worktree里面改代码

s19:mcp plugin

标准工具 + 外部方法 / 技能接入


ReAct in math model

再接下来的章节中,我会用公式化的方式建模一个最基本的ReAct架构下面的agent运作流程:

我们设最终的目标$$ g = Open \;the \; WiFi $$
真实的环境状态是:
$$ s_t \in S $$
但是agent看不到完整的状态,它的观察只是基于部分事实所获得的:
$$ o_t = \Omega(s_t) $$
对于经典的ReAct模型,每一个step会生成reasoning和action,也就是:
$$ (\rho_t,a_t) $$
这个过程的产生是这样的:
$$ (\rho_t,a_t) = \pi_\theta(o_t,h_t,g) $$
即根据当前agent观测到的状态和历史context,再加上总目标g来得到下一个step的推理和动作

前面我们忽略了一个点,也就是这个$h_t$具体维护了什么?标准的历史会同时维护上文中的三个变量,公式化表达:
$$ h_t = \{(o_t,\rho_t,a_t),...(o_1,\rho_1,a_1)\} $$
接下来来看环境转移,也就是action之后会给agent什么样的变化:

首先是真实环境发生变化,然后agent的观察紧接着一块变:
$$ s_{t+1} = T(s_t,a_t)\\ o_{t+1} = \Omega(s_{t+1}) $$
重复这个循环直到完成整个过程/任务,这就是一个基本的ReAct循环的流程

大模型推理

本章节主要参考deepseek-r1的技术报告关于SFT以及RL的相关内容,加上一堆prompt engineering的部分构成,也算是一个强化学习的入门吧

DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning

原文链接:DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning

Deepseek-R1-zero:强基模下的纯强化学习

在zero版本的研究中,以deepseek-v3作为基座模型,本身就具有很强的推理能力的,然后在此基础上进行了纯粹的rl的后训练

没有任何复杂的类似于CoT这种详细推理流程的数据(without supervised data),都是类似于给一个推理问题,只告诉你对/错这种的样例

PPO

PPO的核心公式就一个,具体几个模型都是基于这个公式自然衍生出来的:
$$ L^{CLIP} = \mathbb{E}[min(r_t(\theta)A_t,clip(r_t(\theta),1+\epsilon,1-\epsilon)A_t)] $$
其中这个比率$r_t(\theta)$表示的是两个策略之间对于某一个动作的偏好,也就是高reward的决策,公式表示为:
$$ r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{old}(a_t|s_t)} $$
说人话就是如果新策略相比于老策略能获得的reward更高,这个ratio的值就大于1,模型倾向于学这个策略,但是仅仅靠这个ratio是没法调整的,他只是一个衡量强化了多少的一个尺度,要想知道模型是否要优化 / 削弱模型的这一步就要靠这个$A_t$:
$$ A_t = R_t - V(s_t) $$
这里的两个参数分别是某一步实际得到的reward值和模型预测”这一步应该得到多少reward”的差值,比如说实际上这一步得到了0.9的reward,但是模型预测它只能获得0.4的reward,那么这个差值就是0.5,显然一个正的差值表明这一步需要被强化,所以在公式中体现就是一个正的loss

大致的流程就是这样,但是受限于他的训练中需要好几个roles,计算开销非常之大,现在页不太用了,我们接下来来看ds中的主角GRPO

GRPO

在PPO的基础上,GRPO提升的实际上是从绝对的优势打分($A_t$)变成一个group中的相对值,也就是说模型可能会给一个问题若干个答案,我们认为这些答案是在同一个group里面的,然后根据答案在group里面的排名来决定给多少强化/削弱

优化的目标函数:
$$ L = \frac{1}{G}\sum_{i=1}^{G}(min(r_t(\theta)A_t,clip(1+\epsilon,1-\epsilon,r_t(\theta)A_t)) - \beta KL(\pi_\theta || \pi_{ref})) $$
结合KL散度的定义,这是一个衡量信息之间的差距的函数:
$$ D_{KL}(\pi_{\theta},\pi_{ref}) = \sum_x P(x)log \frac{\pi_\theta(x)}{\pi_{ref}(x)} $$
好我们先解释清楚这个KL散度是干啥的,就是说给出两个策略,我需要衡量他们之间的差距有多大,考虑到策略$\pi$实际上是概率的累积的乘积,即:
$$ \pi_\theta = \prod_{t} \pi_\theta(s_t|a_t) $$
KL散度做了这样的一个计算,对于某一个step $t$,因为LLM是next-token的预测的,所以在不同的策略之下都会给出一个next-token的概率词表,那这个输出的token实际上在当前策略$\pi_\theta$和$\pi_{ref}$之间是不一样的,为了防止偏离的太远,于是加入这二者之间的KL散度惩罚项,在实际的GRPO当中,这个惩罚项是仅对于当前策略预测出来得token在两个策略之间的差异算入KL中的,并不是一整个词表,再把一整条rollout的误差累积起来就有了KL正则项。

除了这个KL正则项之外,因为GRPO是对于一整个组而言的,所以前文提到的优势$A$也可以用组内的相对差距来衡量,也就是:
$$ A_i = \frac{r_i - mean(r_{group})}{std(r_{group}) + \epsilon} $$

以上就是基本的PPO / GRPO的算法,回到ds的技术报告看

R1-zero探究了这样的一个问题:在一个相对较强的基模上,仅通过RL能训练出推理能力吗?

基模选用deepseek-v3,RL算法采用GRPO,奖励函数只由两部分组成:答案是否正确和输出是否符合指定格式

在格式的约束上非常简单,只提供了一个如下的template:

<think>
reasoing process
</think>
<answer>
answer content
</answer>

除此之外,不加入任何具有偏好性质的规定,如”请在每一步推理后思考一下是否正确”

实验结果证明,zero的推理能力取得了巨大提升,在这个过程中实现了模型的自进化,也就是推理步数越来越长,性能比肩o1模型

顿悟时刻(aha-moment):给定的用来RL的数据全都是最后只有个结果的,并不像正经的SFT把中间过程的推理全都写的很详细,但是在RL的过程中发现,模型到了一定的step之后学会了加一些停顿反思的环节

看这里出现了”Wait!wait”的反思过程,也就是顿悟了,为啥突然会出现这个玩意呢?因为你Agent的策略$\pi$实际上是在每一步的时候从一些路径中选一条走,所有它本身就是能考虑到aha-moment的情况的,只是可能一开始的概率不大,经过RL的引导之后就会更倾向于做出这样的思考过程了

but…,虽然纯粹的RL还不错,但也产生了诸如语言混乱,可读性差等问题,接下来来看deepseek-r1的设计


Deepseek-R1 全流程训练pipeline

Part1:冷启动

用一批少量的精细化标注的CoT思维推理过程的数据喂给基模进行第一轮的训练,冷启动主要目的是实现以下两点:

  • 输出格式:使模型的输出(markdown/language)这些因素更好地进行统一,防止出现一些不可读的文字内容

具体给出的格式如下所示:

Here, we define the output format as
|special_token|<reasoning_process>|special_token|<summary>, where the reasoning
process is the CoT for the query, and the summary is used to summarize the reasoning
results
  • 更好的推理潜能

Part2:reasoing RL

大规模数据喂给基模开始RL,目的是大幅提升推理能力

与zero中的RL有一点不同的地方是reward函数加入了一个语言项,也就是用于约束和规范输出语言的统一性,比如全都用中文回复这样

$$ R_{final} = R_{acc} + R_{language} $$

其余的部分与zero中的基本饿完全一致,这里不过多赘述

Part3:拒绝采样和监督微调

用第二阶段RL收敛之后得到的这个ckpt去产生优质的数据反复喂给基模,具体的产生过程就是所谓的拒绝采样,即对于同一段prompt给出若干个回答,然后用打分器/判别器选出优质的收集起来作为本阶段SFT的数据集,这个阶段包含了推理型样本和非推理型样本共计800k个,比例约为3:1

Part4:全场景微调

为了让模型回复的时候更符合场景需求和用户要求,进行全场景微调,除了进一步使用推理型数据加强推理能力,灌入大量的general-data让他的结果更符合实际场景

这一部分非常贴合于RLHF的概念,也就是人类偏好对齐,所以在奖励函数中加入了有否有帮助和安全风险的约束项,具体不展开了

以上就是R1的完整训练pipeline了!


实验部分

各种benchmark、baseline之类的跑分证明了性能强,没啥好说的


SFT or RL? An Early Investigation into Training R1-Like Reasoning Large Vision-Language Models

原文链接:SFT or RL? An Early Investigation into Training R1-LikeReasoning Large Vision-Language Models

一个很有意思的研究工作,在Deepseek-R1的成功之后,大家往往认为在大规模RL之前,高质量的SFT数据是有益的,甚至于可以成为后训练的一个范式,然而对于小模型(SLM)而言却是恰恰相反的

VLAA-Thinking Dataset

本文专门构建的一个多模态推理数据集,主要目的是服务于后面探究SFT / RL的训练效果的,其实不用太在意

Findings

  • SFT数据越多,效果越差,甚至不如裸模型!

我们在DS-R1的报告中发现了SFT能够很好地规范模型的输出格式,并给出更好的推理潜能。但是小模型呢?本身的参数量就小,SFT即使标注好了整条rollout的细节和一步一步该怎么推,SLM并不能很好的学到,一方面,模型一直在模仿固定的格式,而不是产生本质性的思考

我们好好想一下这个问题,SFT的本质是一个最大似然:
$$ \max \limits_{\theta} log p_\theta(y | x) $$

所以你SFT是在模仿人家推理,学到的只是往格式上去凑合,但中间怎么推理怎么分析的完全不管

另一个方面,SLM的参数量本身就是严重不够的,你学了新的这些模仿的内容之后,肯定要拿出一部分参数更新吧,这些参数原来可能是负责视觉辨析或者推理的,现在没法了也会导致正经推理变差

其实还有很多可能的原因,上面这两个比较主要

  • SFT数据越多,性能越差劲!

emm我认为和上面的分析差不多,简单来说就是SLM的参数量德不配位,更多的数据也全是对于长CoT的模仿,so效果只会更差


SFT Memorizes, RL Generalizes:A Comparative Study of Foundation Model Post-training

原文链接:SFT Memorizes, RL Generalizes:A Comparative Study of Foundation Model Post-training

这个工作探究了一个问题,SFT和RL作为后训练的两种范式,到底是让模型真正学会了规则迁移,还是仅仅懂了模仿

结论:纯粹的SFT让任务的成功率全都降低了,RL都提升了

实验选用的基座模型为:Llama-3.2-Vision-11B

说实话是个意料之中的结果,跟前面的工作出现的现象较为贴近,SFT由于它的最大似然的训练目的,导致了很多情况下都是teacher模型教什么它学什么,一碰到泛化的任务马上坠机,RL则是根据reward的值到逼着自己学出真东西来

有一组实验设置的特别好,是一个衍生的24点游戏,在post-training的时候采用的是正常的24点的案例作为训练数据,但是test集用了一版新的规则:JQK全都算作10点,在此情况下,两种后训练的差异就拉开了,而且很明显


Give it time, let it pass.