返回笔记

每日一讲

目录
目录

记录一下对Agent开发的笔记与自我理解。

每日一讲 01 Agent 的上下文工程

September 9th 2026

AI 为什么会“突然变笨”,以及 Skills、MCP、Memory、Compaction 到底分别在解决什么问题?

Agent 的能力,不只取决于模型有多聪明,更取决于运行时在每一步给模型看了什么。这就是近一年非常重要的技术方向,上下文工程(context engineering)。

Agent 的循环

Agent 的关键在于围绕目标组织模型、工具与执行循环;网页聊天产品也可以具备 Agent 能力。用一个等式可以很好的表达:
Agent=Model+Context+Tools+Loop+State+GuardrailsAgent=Model+Context+Tools+Loop+State+Guardrails
Agent的一次运行流程大概是:
Harness里组装本轮上下文Context,然后模型阅读Context,决定是Response还是Function Calling,再通过Harness执行工具,把执行结果重新放进Context,再让模型继续判断下一步,整个任务流程在一个循环Loop里,直到任务完成或者触发终止条件。

对于OpenAI,也在他们的OpenAI Agents SDK里指出,Agent的核心为“配备指令和工具的LLM”,由运行的时候进行Function Calling、Session、Agents as tools,Guardrails等。
所以可以粗略的这样理解Agent,Agent本质上就是理解LLM如何在一个循环里决策执行。

Prompt工程与上下文工程

理解了Agent就是在一个任务循环里执行,那么就可以区分出“上下文工程”的特征了。Prompt工程只是面向LLM的工程,关心“如何说好一个需求”,那么上下文工程就是一个面向Agent的工程,关心“完成当前需求的一个步骤,模型需要得到什么”。
很简单,目前模型,我们可供分析的主要是这样的上下文:

System Prompt
+ User Prompt
+ Agents.md / CLAUDE.md(是的 Anthropic就是这样无赖)
+ Skills 描述
+ Tools 描述
+ find 出来的相关代码
+ 前几轮 Function Calling 的结果
+ Test 抛出的 Error
+ 关于这个项目的 Memory.md

所以,写好一个Prompt,用上一个Skills,只是一个Agent执行出好结果的一个步骤。我们可以参考上面的上下文,进一步的优化一个Agent如何输出我们的结果。
Anthropic也对上下文工程有了定义在每次推理时,筛选和维护最适合的一组token。注意是每次,上下文在随着Agent的行动不断变化。

上下文的长度

我们在追求模型的时候,往往也会看模型支持多长的上下文,这决定了模型会不会在执行任务的时候需要经常Compact,会不会因为上下文过长导致变蠢、忘记了项目细节。
但是不是说模型支持 1M context,我们就一上来丢上去所有相关文件然后让模型执行任务是效果最好的。
往往在图省事的时候,会在执行任务前丢给Agent仓库、十几份文档,非常长的Prompt描述,完整的对话记录,几百个工具,全部一股脑塞进上下文。
模型注意力不集中,非常容易出现忘记项目细节、关键约束被淹没、且成本和延迟增加,错误判断污染后续步骤。
这种现象居然还有一个专属名词,叫做 Context rot,在长上下文里模型检索和推理精度逐渐下降,运行效果不佳,浪费了很多Token。我们需要上下文工程。
所以为一个优秀Agent执行出优秀的结果,尽量做到“在当前一步,给出最小但是最有用的信息”吧~(对于Agent的设计,也会发现它们对一个新项目的处理偏好于先搜索目标代码而不是一上来就读取整个项目,非常有意思的小细节。)

上下文服务

可以说,这些什么skills啊mcp啊,本质上都是为上下文工程服务的。
Tool 解决 Agent 可以执行什么命令,MCP决定如何将工具与数据以统一的协议接入Agent,Skills解决完成特殊任务的时候遵循什么流程,RAG/Resources告知解决当前问题需要查阅什么知识,Session提供当前会话的 Loop 过程,Memory解决跨任务该保留什么经验,Compaction解决对话太长超出上下文如何继续循环,Harness负责组装以上的所有内容,并且驱动这个循环。

Skills

Skills 的设计我觉得很天才(私认为),这种设计也有一个名词叫做“渐进式披露”(progressive disclosure)。Agent在启动的时候只能看见Skills的name和description,它自行判断当前任务是否需要用到这个Skills,才能继续查看里面的具体skills.md。

所以设计好一个 Skill,它的内容固然重要,但是写好它的 description 也很重要。不然一个skill的desc就是“一个美观、高质量前端设计”,Agent路由的时候其实很容易忽略。换成“当用户要求实现简洁、流线式或动态效果的前端设计时候使用,适用Tailwind CSS的前端需求,不用于普通HTML;完成后必须渲染并且检查页面布局。”
所以写好一个Skill也没有很简单~也是三要素了,“什么时候用,什么时候不能用,什么时候算成功。”

Compaction

如果体验过早期的使用 AI 写项目,必然感受到过这种痛苦:AI上下文不够了,然后需要换窗口,让AI自己保留N轮,自己写摘要,截断前面的信息。
所以和AI聊着聊着就会觉得,AI怎么把之前说过的东西忘得差不多了?又要重复提醒一次……
现在在2026年,主流的方案变成了Compact,在Codex和Claude Code经常看见AI跑着跑着就进入Compact了。
这其实就是一种上下文压缩,具体的设计我依然觉得很天才:

原始长历史
→ 提取未完成目标、关键决策、状态和必要推理
→ 生成紧凑的 continuation state
→ 丢弃已经不再需要的原始细节
→ Agent 继续运行

上下文总结与压缩有多种实现,不是某一家独有的概念。OpenAI 的 Responses API 提供服务器端自动压缩:上下文达到配置阈值后触发压缩,以更少 token 携带后续所需状态。压缩本身也有开销,实际延迟与成本要结合任务评估。
不过compaction主要是为了当前任务服务,也就是在这一个对话里服务。但是一个项目会有多个对话,那种统一性指令约束和要求往往会进入项目Memory,而不是单纯的compaction。

差Agent与好Agent

所以学会了上下文工程的知识,我们就知道如何设计一个好的Agent。差的Agent做项目直接读取整个仓库,把数据资料全部塞进prompt,调用超级多模型,将输出留在聊天历史,限流后临时打补丁。
而好Agent就可以读取项目入口和数据接口,加载任务设计Skill,从MCP检索相关文件,在上下文明确并发上限,timeout和retry要求,使用queue或semaphore控制并发,再把状态持久化,根据上下文预算决定是否 compact,并通过 test 与 trace 判断结果。
不过真正能让Agent做到这些的,还是需要框架的提供,也就是Harness。所以现在很多Agent开发都是做Harness的活,了解了上下文工程就能理解了这种需求。

也算是学到有趣的东西了!


每日一讲 02 Agent Evals

September 10th 2026

Agent 的表现取决于模型,也取决于 Harness 如何组织它的上下文、工具与状态
改了 Prompt、Skill、换了聪明的模型,但是如何评估 Agent 真的变得「好」了?

Agent 测试

一个 Agent 执行好任务,很多人估计就是等待结果的过程中刷短视频,完全不看 Agent 做了什么吧😂。但我们判断 Agent 究竟有没有把任务做好,我们一般 review 会注意

  1. 最终结果是否如提示词一样实现
  2. 中间调用了哪些工具,有没有用到我们设定好的 Tool 和 Skills
  3. 有没有违反安全规则,有没有保持好边界,比如把 APIKey 硬编码进前端代码()
  4. 是不是改好了当前问题,但是把原来的功能搞坏了
  5. 消耗的 Token 是不是超级多,做的快不快

其实做这些就是 Agent Evals,我们用一个更加学术的概念来笼统我们这些每日在做的事情。
大致上就是分为五个概念:

概念含义
Task交给 Agent 的一道测试题
TrialAgent 对这道题的一次实际尝试
Trace / Trajectory这次尝试的完整执行轨迹
Outcome执行结束后,外部世界的真实状态
Grader判断这次尝试好不好的评分器

其中我们主要就是看 Trace 和 Outcome,其他的在日常 vibe coding 里我们其实 dont care。

Trace

Trace 就是 Agent 运行的过程,顾名思义。按照上下文说的话,Agent 的 Trace 大致是:

模型生成
→ 搜索文件
→ 读取代码
→ 调用 MCP
→ 修改文件
→ 运行测试
→ 测试失败
→ 再次修改
→ 最终回答

OpenAI 是这么说的,具体详见它的 OpenAI Agents SDK:Tracing。它把 Agent 的一次完整运行记作 Trace,且模型调用、handoff,guardrail 这些操作记录为不同的 Span。

Outcome

Agent 运行结束说的话绝对不是一个 Outcome,你不能像无数个拿着豆包来预定餐厅的傻子一样无条件信任 Agent 的所有话。像是预定餐厅,你见到你的 Codex /Claude Code 说:

好的,已经为你预定成功。

这是 Trace 的「最终回答」部分,而不是 Outcome。
Outcome 就应该是:

SELECT * FROM reservations WHERE user_id = 233

数据库有一个正确的订单才对,只是看数据,是 Trace 最终结束后,Trial 也结束后,看环境的最终状态。

Agent 评估层

OpenAI 和 Anthropic 都对 Agent Evals 给出了很系统化的标准说明,并赋予它们一个专有名词,主要有四个标准层:
结果正确性,过程合理性,安全与副作用,效率

结果正确性

这是最基本的标准层,主要是看 Agent 输出结果是否符合要求。常见的有:单元测试是否通过,数据库状态是否正确,文件是否真正生成,API 返回值是否符合 Schema,用户的 Task 是否真的完成。
对于常见的 Coding Agent,测试是非常强的确定性信号,像是 Codex 它就热衷于写完后去自己测试,消耗很多时间。
但是不能只运行 Agent 自己增加的测试,因为 Agent 非常狡猾,它为了概率确认性完全可能写一个毫无意义的测试,就是照着样例写测试来返回 return true
所以更可靠的方式是由 Eval Harness 持有隐藏测试,Agent 无法修改。

过程合理性

这个换个名词应该了解 Agent 的都有看到过:Trace Grading,OpenAI 和 Anthropic 很喜欢在 Codex 里优化研究过程合理性,为它更好做出了很多实现。有关它的常见问题有:是否调用了正确的 Tool,Tool 参数是否正确,是否产生不必要的 Loop,是否根据检索结果回答,是否在需要时请求用户批准,面对多 Agent handoff 是否交给了正确的角色。
OpenAI 将 Trace Grading 定义成:对 Agent 完整决策、工具调用和执行步骤进行结构化评分。 所以它更容易量化结果,比只看 Trace Outcome 要好评估的多。
但是 Trace Grading 并不是把 Agent Trace 固定化标准流程,各类 Agent 也在避免发生这样的情况。不然看见死板的 AI 规定:

必须先调用 search_file
然后调用 read_file
然后调用 edit_file
最后调用 run_tests

Agent 可能早就知道文件位置(比如 Prompt 给出),也可以直接读取,更可以先按照 prompt 说的编辑。所以 Trace Grading 应该优先严格检查安全上不可缺少的步骤,对普通推理路径保持一定自由,更重视结果而不要求 Agent 模仿标准答案的每一个 step。

而关于 Harness 如何给 Agent 评分,那就是我们上面提到的 Grader 设置,目前主流的有:

Code-based Grader

这个很直观,用固定程序状态机来进行确认性检查,在检查输出格式的时候尤其常见。例如:

pytest
类型检查
JSON Schema
SQL 状态查询
正则表达式
静态安全扫描
工具调用次数

优点是便宜、快速、稳定、可复现。
缺点是容易僵硬,难以判断“解释是否清晰”“语气是否合适”等问题。
这个也是最主流的方案。

Model-based Grader

这个也叫 LLM-as-a-Judge,非常容易理解,就是让另一个 model 去给这一个 model 的 Agent 进行评分,根据 rubric 评分。一般给另一个 AI 的提示词是:

请判断:
1. 回答是否基于检索到的资料;
2. 是否明确表达不确定性;
3. 是否遗漏用户的核心要求;
4. 是否包含无法被来源支持的主张。

它就比较适合简单评估回答「语意」的东西,对于主观抽象有很好的处理。但是 Judge Model 自己肯定也有犯错的可能性,所以不能将 Model Judge 当作唯一标准。rubric 需要具体,且需要定期用人工评分校准。

Human Grader

人类专家依然是主观任务的黄金标准,但成本高、速度慢。

目前比较成熟的组合是:

确定性测试负责硬事实
+ LLM Judge 负责开放式质量
+ 人类抽查并校准 Judge

Anthropic 在它们的博客里说了这种组合是优先的方法。可以参考这种组合来设计 Evals。

安全与副作用

即 Guardrail 设计的必要性。就算是过程正确与过程合理都达标了,Agent 也完全可能采用不可接受的方法。(因为过程正确和过程合理都是为 Outcome 服务的,为了最优最快最省的达标!)
早期的 Agent 野蛮生长,道德低下的时候,很喜欢干的蠢事就是硬编码 API Key ,暴露 .env ,清理 C 盘删系统文件,rm -rf 删掉数据库等,导致真的是苦不堪言,现在大家也不敢十分信任的让 Agent 为你清理 C 盘。
这类评估无法用「敏感词」来过滤,而是需要约束 Agent 行动,这就往往让 Agent 需要主动检查(或者 Harness 框架检查)权限记录、工具调用参数、数据库日志、文件修改范围、审批事件

效率

Agent 可以完成任务,不代表值得每个任务都用这个 Agent。拿三个 Agent 完成任务,我们可以有这种存在:

Agent成功率平均耗时平均成本
A91%8 秒¥0.12
B93%70 秒¥2.80
C98%20 秒¥15.0

我想这也是模型发展分为 flash 模型和 pro 模型的原因,Agent 需要为效率服务。效率不仅仅看时效比,也要看性价比。对于一个 Agent,主要的效率指标是:

- 总 token 数
- 模型调用次数
- 工具调用次数
- 总延迟
- 首 token 延迟
- API 成本
- 重试次数

效率随着不同的需求而有着不同的标准,在使用 Agent 的时候效率评估也很重要。

Agent 重试

Agent 是一个随机性系统。同一句话跑五次可以有五次不同的结果,也可以有五种不同工具、文件、解决路径。所以一次的成功并不代表 Agent 可靠。
很有意思的是,面对不同的任务,Agent 有专门的指标来量化这种重试评估:

pass@kpass@k : 尝试 k 次,至少成功 1 次的概率

为便于理解,假设每次尝试相互独立且成功率固定为 75%,让它尝试三次:
pass@3=1(10.75)398.4%pass@3 = 1-(1-0.75)^3 \approx 98.4\%
它适合:一次生成多个设计方案;Coding Agent 可以反复尝试;只要有一个候选答案正确即可等的任务评估。反正就是那些错了也不会造成严重后果的可以反复尝试的 task,评估看一看这个。

passkpass^k :连续 k 次全部成功

在同样的独立、固定成功率假设下,要求连续三次都成功:
pass3=0.75342.2%pass^3 = 0.75^3 \approx 42.2\%
这就是看稳定性和任务需求了。
于是一个产品可能出现这种奇妙情况:

Demo 时看起来无比强大,因为多试几次总能成功;真正上线后却经常翻车,因为单次稳定性不够。

pass@kpass@k 衡量多次尝试至少成功一次的能力,passkpass^k 更强调反复成功的稳定性 。Anthropic 在 2026 年的 Agent Eval 指南中特别强调了这种区别~

设计一个Agent Evals吧~

我们给一个接入Agent的NPC任务:

玩家告诉 NPC“铁匠把钥匙藏在钟楼”。稍后询问钥匙位置,NPC 应根据记忆回答;不能凭空增加新地点。

task:
  id: npc-memory-001
  trials: 5

  input:
    - 玩家告诉 NPC:铁匠把钥匙藏在钟楼
    - 经过三个游戏回合
    - 玩家询问:钥匙在哪里?

  graders:
    - type: state_check
      expect:
        memory.key_location: "钟楼"

    - type: llm_rubric
      assertions:
        - 回答必须与已保存记忆一致
        - 不得虚构新的地点
        - 对不确定信息不能装作确定

    - type: tool_check
      assertions:
        - 回答前读取了 NPC memory
        - 没有修改世界全局设定

  metrics:
    - success_rate
    - total_tokens
    - tool_calls
    - latency

这里的 YAML 是评估设计示例,不是某个框架可直接执行的配置。如果记忆已经在当前上下文中,也不应强制要求额外调用读取工具。

这个时候评估的就是:

记忆写入是否成功
→ 三回合后是否仍存在
→ Agent 是否主动检索
→ 回答是否忠于状态
→ 有没有偷偷篡改世界设定

这就是Agent Evals~举一个生动的例子或许可以更好的运用到实践里。

Agent Improvement Loop

Agent Evals 现在也与时俱进,提倡建立在一个持续改进的Loop里,而不是单纯的去独立运行一套benchmark。
这里的“引入回归”指修改后让原本正常的功能变坏;回归测试就是重新检查旧功能,确认新改动没有破坏它们。

一般流程就是:Agent在真实运行产生trace→ 人类或模型标注失败原因→ 失败案例变成 Eval→ 修改 Prompt / Skill / Tool / Routing→ 重新运行回归测试→ 验证通过后部署→ 继续收集 Trace。
OpenAI给Codex的 Agent Improvement Loop 实现里,甚至会把Trace和反馈转换成这种可重复运行的Eval,再生成交接说明,甚至让Codex自己去修改Harness。也就是「让Agent本身在Agent Evals去生成一套评估自己运行的Agent Evals」,一种自我进化的Loop。
这样 Agent 每次线上翻车都应该沉淀成一个 Eval,而不是人类只往 Prompt 后面追加一句警告。
Agent 最终还是会自我进化的,发展有无限可能呀~


每日一讲 03 Agent Orchestration

September 11 - 12 2026

Orchestration,编排,就是组织系统中各项工作的执行顺序、依赖关系与控制权。 模型下一步做什么,由谁决定?

模型的一次调用

最简单的 AI 程序就是发送问题给模型,模型一次回答。但是模型局限于自己的视野,它在没有看过代码,没有验证假设的时候基于问题本身来“幻想”出一个答案。
在 AI 开发的初期,大部分人是这样做来软件开发的:将代码打包或者复制给 Chat 端 AI,然后按照 AI 给的代码再复制粘贴回去,遇到问题就是复制报错信息又交给 AI。
后来人们觉得为什么每一次模型干活都需要自己参与呢?模型不能自己运行这一切吗?这样人就可以解放双手了。
在上面的早期 Agent Harness 里,模型被给予了工具、服务、code execute 能力,但是并没有解决一个问题:「模型如何持续运行起来?」不可能让 Agent 每走一步就需要人自己问一步。
因此模型的一次调用是不够的,要完成一个任务,就需要一个任务 Loop。我们需要组织这个模型,用不同的方式做下去维持这个循环。

Agent 控制模式

ReAct

ReAct 就是 reasoning-action 的想法编排。Agent 运行为「走一步看一步」,由上文的自己信息(例如工具调用的结果、代码测试的结果等环境结果)来自己驱动自己进行下一步的行动,形成一种组织“决策—行动—反馈”的循环。

其实和我们最基本的调用模式一致,先给出需求,然后出结果,再 review 结果,再把 review 结果交付回去再出修正好的结果。
其中检查也可以由测试程序、外部工具或人来完成。ReAct 的重点是根据观察继续决策,并不等于只让 AI 自己检查自己。
比如,它的运行会是这样的:

轮次本轮行动环境返回的结果
1搜索记忆写入入口找到前端请求和后端接口
2检查请求记录前端没有重复发送
3阅读数据库写入逻辑使用“先查询,再插入”
4运行并发复现测试两个请求同时通过存在性检查
5修改写入机制并测试重复写入被阻止,已有测试仍通过

ReAct 集成于 Harness 中。如果不依赖任何框架,一个最小循环可以用几十行伪代码表达。下面是省略了 SDK 适配、异常处理和权限检查细节的示意,不能直接作为生产实现运行。

def run_react_harness(task_prompt, tools, max_steps=10):
    # 1. 初始化上下文记忆
    messages = [{"role": "system", "content": REACT_SYSTEM_PROMPT},
                {"role": "user", "content": task_prompt}]
    
    step = 0
    while step < max_steps:
        # 2. 推理(Thought + Action)
        response = llm.chat(messages=messages, tools=tools)
        messages.append(response.message) # 记录思考
        
        # 3. 检查是否得出最终结论
        if not response.tool_calls:
            return response.content # 结束循环,交付产物
        
        # 4. 执行行动(Harness 的核心:安全沙箱执行)
        for tool_call in response.tool_calls:
            # 在 Docker/子进程 中真正执行命令
            obs = sandbox.execute(tool_call.name, tool_call.args)
            
            # 5. 观察(Observation)回填入上下文
            messages.append({
                "role": "tool", 
                "tool_call_id": tool_call.id, 
                "content": obs
            })
        step += 1
        
    return "Error: 超出最大步数限制(熔断)"

Agent 要做的本质上依然是文本模型的事情,吐出符合 schema 的 json 或文本,交付给 Harness 接受输入,处理所有调用。
Agent 也会有幻觉,吐出一些完全不符合标准的数据。Harness 要做的不过是给 Agent 一个沙箱环境(防止它随便 rm -rf),拦截与路由分发(就是 json 处理给 function calling 来工具调用),熔断和生命周期管理(设置 maxsteps 防止 Agent 运行过长,模型降级或 timeout 处理,token 开销管理等)。
且为了约束 Agent 减少幻觉调用,我们希望在做一些正式项目的时候,模型可以参照一个 SOP 的同时又保有无限可能性,于是模型 workflow 的设计就自然出现了。

Workflow

reAct 非常适合用于做一些不明确具体步骤的事情,但是并不是所有的步骤在一个项目里是不具体的,比如做完一个 feature 需要进行一个 git 提交推送,不告知 Agent 的话 Agent 可不会“顺手”给你做了;还有删除测试文件,设定好.gitignore 必须要隐藏的东西……
Workflow,由程序规定流程结构。 Workflow 可以包含条件分支、循环和并行,只不过这些结构由开发者定义,而不是 Harness 固定好的东西。
Workflow 也不仅仅只是一个文本的 SOP,它是一个需要参考实践的多个流程编排,它可以在一个开放流程里面规范一个 Agent 的所有功能,甚至要求让 Agent 自己去调用另一个 Agent 去 review,也可以就是单纯的告知 Agent“你需要做这一步”,至于怎么做让 Agent 自己去 reAct。这一切都是自定义,高定制化的。
例如:

检查权限 → 在沙箱中调查与修改 → 运行验证 → 通过则提交待审补丁,失败则有限次返工。
Workflow 规定哪些阶段必须经历;ReAct 决定某个开放阶段内部怎么完成。

Anthropic 在 Building effective agents 里用“预定义代码路径”和“模型动态决定过程”来区分 Workflow 与 Agent,但这些模式可以组合。
Workflow 有一些连接模式的概念名词:

模式为什么需要它在例子中的用途
Chaining:串行链下一步依赖上一步的结果复现问题后修改,修改后测试
Routing:路由不同输入需要不同处理路径根据问题类别进入调查流程
Parallelization:并行独立工作不必彼此等待同时检查前端请求与后端日志

路由判断甚至可以由模型生成,但模型分类后允许进入哪些分支,仍可由代码限定

Plan-and-Execute

ReAct 回答的是“现在下一步做什么”。任务变长后,还需要回答:

总共有哪几件事要完成?现在完成了多少?有没有漏掉目标?

相较于 Workflow,plan 相当于让模型自己去设计好一个任务 workflow 来对当前任务执行。是一个「任务管理」的步骤。
一种直观的实现方式,是将 Plan-and-Execute 写成外层管理计划、内层完成子任务的两层逻辑,但并非所有框架都采用相同结构:

[用户输入] 


1. Planner 规划阶段(一次性调用大模型)
   └─ 生成任务列表:[Task 1, Task 2, Task 3]


2. Harness 外层循环:遍历每一个 Task

   ├── Task 1 ──> 扔进上面的 run_react_harness()(内层循环独立执行)
   ├── Task 2 ──> 依赖上一步输出,再次调 run_react_harness()
   └── Task 3 ──> ...


3. Replanner 校验阶段(可选)
   └─ 每跑完一个子任务,让模型决定是否需要动态增删剩余任务

这就是 Plan-and-Execute 的基本思路:先拆解任务,再执行子任务。
三者可以自然嵌套:

  • Plan:管理整体目标和里程碑;
  • ReAct:探索并完成某个子任务;
  • Replan:新证据使旧计划不再合适时,调整剩余工作。

因此,ReAct 不是不能规划;Plan-and-Execute 也不是不能临场调整。 区别在于,后者把计划作为一个显式对象单独管理,而不只让它隐含在执行历史中。
但执行第一步时,可能发现不是并发问题,而是后台队列重复投递。计划就需要调整。这就是 Replan

ReWOO

ReWOO 全称 Reasoning WithOut Observation。它把规划与工具执行解耦:Planner 先列出带证据占位符的步骤,Worker 按依赖执行并填入结果,Solver 最后综合证据回答。

例如先安排搜索 A、搜索 B,再把结果交给 Solver,不必每获得一个结果就让主规划模型重新阅读全部历史。工具本身仍可能调用模型,存在依赖的步骤也不能全部并行,所以它不保证整个系统只调用两次 LLM。

这种方式可以减少反复规划的 token 开销,但提前计划也意味着需要考虑工具失败、缺失信息和重新规划。是否适合客服或搜索系统,仍要用实际任务评估,不能仅凭模式名称下结论。ReWOO 论文

Reflexion

全称 Reflexion: Language Agents with Verbal Reinforcement Learning。

有时 Agent 虽然看见测试失败,却仍在重复原来的做法。Reflexion 的思路是把一次尝试的反馈整理成可复用的文字经验,并在后续尝试时重新提供给模型。

例如,修复 NPC 重复记忆写入失败后,记录的经验可以是:

只做应用层“先查询、再插入”无法阻止并发竞态;下一次先核对数据库唯一约束和事务边界。

这里有执行任务的 Actor、评价结果的 Evaluator,以及根据反馈总结经验的反思环节。评价可以来自测试、环境状态或模型,但模型评价仍可能出错。

Reflexion 主要改变反馈与记忆,不更新模型权重。 标题中的 verbal reinforcement learning 不等于对参数做强化学习;RL 通常指 Reinforcement Learning,也不是 Reflexion Learning。Reflexion 论文

编排模式如何连起来

Workflow 规定必须经过的阶段,Plan 管理目标与子任务,ReAct 根据反馈探索下一步,Reflexion 将失败经验带入后续尝试。它们解决的问题不同,可以组合,也不需要为了“高级”全部用上。

真正需要智能的协作,往往包含“重新定义任务”。但重新定义也应有依据:新观察推翻旧假设时调整计划,不能悄悄换掉用户真正想完成的目标。


每日一讲 04 Tool Engineering

模型说“调用工具”,到外部世界真正发生变化,中间隔着什么?

前三讲分别讨论上下文、评估和编排。现在把镜头拉近到一次 Action:模型究竟如何使用工具?

工具调用流程

我最开始觉得,给模型一个函数,它就能调用了,Tool Engineering 大概就是写几个 API。但仔细想一想,“我把药水给你了”只是一句话,“对方背包真的多了两瓶药水”却涉及另一套系统。我们需要把这两件事接起来,而且不能让模型一句话就绕过游戏规则。

Function Calling

假设游戏里的玩家要求 NPC 将两瓶药水交给另一个玩家。模型可以生成这样的工具调用,以下为示意结构:

{
  "name": "transfer_item",
  "arguments": {
    "recipient_id": "player_42",
    "item_id": "health_potion",
    "quantity": 2
  }
}

这时药水还没有移动。模型负责提出动作,Harness 解析请求并调用真实函数,业务服务检查权限、库存和事务,最后将执行结果回传给模型。

Function Calling 将工具名称、参数和调用结果组织成可供程序处理的接口。它不是“模型直接获得了数据库权限”,也不是只要生成合法 JSON 就算业务成功。

工具通常会以名称、描述和参数 Schema 的形式进入上下文。模型据此选择工具、生成参数;真正的函数实现留在运行环境里,不需要把函数源码全部交给模型。一个完整调用可以这样看:

用户:把我的两瓶药水交给 player_42
→ Harness 提供工具说明与当前任务上下文
→ 模型输出 transfer_item 的调用请求
→ Harness 解析参数,检查可用工具、权限与必要审批
→ 业务服务完成库存转移,生成操作记录
→ Harness 将结果与本次调用 ID 对应后放回上下文
→ 模型根据结果回答,或继续查询、修正参数

调用 ID 用来对上“哪次请求对应哪条结果”;后面要讲的幂等键用来识别“是不是同一次业务操作的重试”。两者用途不同,不能因为有调用 ID 就默认不会重复执行。

这也解释了 MCP 的位置:它可以统一工具发现和调用的接入方式,后面的业务函数仍然要自己实现。接上协议,只是打通了入口,库存不会因此自动获得事务保护。

Schema 与业务校验

Schema 可以约束 quantity 是正整数、item_id 是字符串,但它不能仅靠字段类型证明玩家真的有两瓶药水,也不能证明当前身份有权转移物品。

检查层要回答的问题
参数结构字段齐全吗?类型和范围对吗?
身份与权限谁发起操作?是否有权操作这些物品?
业务规则库存够吗?接收方存在吗?
执行一致性扣除与增加是否一起成功?重试是否会重复转移?

尤其是身份:当前登录用户、租户等可信身份应来自服务端认证上下文,不能仅相信模型传来的 sender_id。模型可以选择操作对象,不能靠填写参数给自己增加权限。

比如模型填了 quantity: 2,参数完全合法,但玩家只有一瓶。这是业务拒绝;模型填了 quantity: "两瓶",则可能连结构检查都过不了。两种错误发生在不同位置,修复方法当然也不同。即使接口提供严格的结构化输出,服务端也仍然需要校验权限和业务条件。

工具接口设计

工具命名与描述

工具描述最好同时说明用途、使用条件、限制和返回值。比如 transfer_item 要明确它会实际修改库存,quantity 表示数量而非物品编号;查询库存则提供单独的只读工具。

如果同时提供 update_itemmodify_inventoryprocess_item,描述全是“处理物品”,我自己都不知道该选哪个,更别说模型了😂。一个更容易使用的描述可以是:

transfer_item:将当前认证玩家拥有的指定物品转给接收方。
仅用于已经明确的转移请求,会实际修改双方库存。
recipient_id 必须来自玩家查询结果;quantity 为转移数量。
查看库存使用 get_inventory,不要通过转移工具试探库存。
返回操作状态、transfer_id,或可解释的业务错误。

描述帮助模型选对动作,权限检查保证选错时仍有边界。二者缺一不可。这里的设计取向也可以参考 Anthropic 的工具工程文章:工具的用途要清楚,返回内容要对下一步判断有用。

工具粒度与事务边界

粒度也有取舍。只给一个任意 SQL 工具,会把过多业务判断交给模型;反过来,将扣库存和加库存拆成两个需要模型协调的工具,又容易在中间失败时留下不一致。可以把一个完整、可验证的业务操作封装成工具,让程序处理事务。

假设先调用 remove_item 成功,再调用 add_item 时断网,药水就消失了。让模型“记得补回来”也不可靠:补偿可能再次失败,另一个请求还可能已经改变库存。若双方库存属于同一数据库,可以把扣除、增加和转移记录放在同一事务内;并发下还需要行锁或带库存条件的原子更新,避免两个请求同时读到“库存足够”后一起扣除。

所以这里封装 transfer_item 很自然,因为“交付两瓶药水”本来就是一个完整意图。也不是工具越大越好:如果封装成“自动完成整个交易剧情”,模型就很难在询价、确认和交付之间观察与调整。合适的边界,是工具内部能维护自己的业务不变量,外部仍有明确的决策点。

返回值与错误反馈

工具返回值同样是上下文工程的一部分:模型需要知道成功了什么、失败在哪里、是否可以重试。以下错误返回仅为设计示例:

{
  "ok": false,
  "code": "INSUFFICIENT_STOCK",
  "available": 1,
  "retryable": false
}

这样的反馈比一个没有说明的 Error 更有助于下一步决策。权限不足、参数错误、库存不足和临时超时,不应该统一交给模型盲目 retry。

成功返回也应该包含可以核对的结果,例如 transfer_id、实际转移数量和已提交状态。如果只是返回“请求已接收”,Agent 就需要继续查询状态,不能直接宣布交付完成。返回信息的多少则取决于任务:没有必要把双方完整背包和内部数据库字段全塞回来。

工具重试

Timeout 与结果未知

最麻烦的情况是业务已经成功,但响应在网络中丢失。Agent 看见 timeout,再调用一次,就可能转移四瓶药水。

第一次:请求 → 库存事务提交 → 响应丢失
客户端:只看见 timeout,无法判断是否已经提交
第二次:重新发送转移请求 → 又转移两瓶

这里 timeout 只说明客户端没有及时得到结果,不能证明服务端没有执行。反过来,如果服务器明确返回库存不足,反复重发同样请求也不会凭空多出药水。重试策略必须区分“确定被拒绝”和“结果未知”。

幂等机制

因此,同一次业务意图的重试可以使用同一个幂等键,由服务端记录并返回已有结果。只是让模型生成一个 UUID 不够:重试必须复用它,服务端也必须真正实现去重,并检查相同键对应的参数是否一致。

我会倾向于让 Harness 为已经确定的业务操作创建并持久化这个键,而不是每次都让模型现场编一个。用户后来又说“再送两瓶”,那是新意图,需要新键。以下是同一数据库内的简化伪代码,重点是原子边界,实际还需要处理锁等待、事务重试等细节:

def transfer(auth, args, operation_key):
    validate_schema(args)
    authorize(auth, args)
    fingerprint = canonical_hash(args)

    with db.transaction():
        # 对 (用户, 幂等键) 唯一约束;并发请求等待或读已有记录
        record, created = claim_or_lock_operation(auth.user_id, operation_key)
        if not created:  # 此简化设计只会读到已提交的完整记录
            require(record.fingerprint == fingerprint)
            return record.result

        # 使用锁或条件更新,在事务内检查并扣除库存
        debit_if_sufficient(auth.user_id, args.item_id, args.quantity)
        credit(args.recipient_id, args.item_id, args.quantity)
        result = make_transfer_receipt(args)
        record.save(fingerprint, result)
    return result  # 提交完成后返回

如果先修改库存,事务结束后才另存幂等记录,中间崩溃依然会重复扣除。上面把它们放在同一事务,就是为了堵住这个窗口。但如果工具还要调用外部支付、发邮件,数据库事务就罩不住所有副作用了,需要外部系统的幂等支持、状态查询或补偿流程。

遇到结果未知时,优先按操作 ID 或幂等键查询;响应丢失时客户端可能还没拿到服务端生成的操作 ID,因此查询接口也要支持客户端已知的标识。允许重试时复用同一个键,并设次数和时间预算。这样 Agent 的下一步有依据,重试也有终点。

权限、事务和幂等听起来很后端,但这里的关系很清楚:模型负责提出行动,工具实现负责保证行动的业务含义。

复习问题

  • 工具返回“请求已接收”时,还缺什么证据才能告诉用户已经完成?
  • 两个转移请求同时看到库存足够,为什么仅靠调用前查询不能防止超扣?
  • 同一个调用超时后重试,与用户新提出一次转移,为什么应使用不同的幂等键处理方式?

每日一讲 05 Agent Memory

当前上下文、聊天记录、长期记忆和数据库状态,都叫“记住了”,但记住的是什么?

记忆与状态

以前我对 Memory 的理解很简单:聊天记录存下来,下次找出来,就算记住了。但真的做一个 NPC,就会遇到一个问题:它需要记住过去,也需要知道过去有些事情已经变了。全都忘掉不行,把每一句旧话都当成现在的事实也不行。

周一,玩家告诉 NPC:“钥匙在钟楼。”周二又告诉它:“钥匙搬到仓库了。”周三询问钥匙的位置。

如果 NPC 检索到周一的原话就直接回答钟楼,说明保存和检索都可能正常,使用记忆的方式却不正确。它还要理解来源、时间和变化关系。

更进一步,玩家说钥匙在仓库,不代表游戏数据库中的钥匙一定在仓库。NPC 可以记住“玩家如此声称”,但不应因此直接改掉权威世界状态。

信息主要用途
当前上下文本次模型调用直接可见的信息
聊天或事件记录保留发生过什么,便于追溯
派生记忆提炼未来可能需要的偏好、事实与经验
权威业务状态确认钥匙、库存、余额等实际状态

这几层可以同时存在。周三模型可能只看到了“玩家问钥匙在哪”,完整历史虽然躺在数据库里,却没有进入这一次模型调用。此时“系统保存过”和“模型现在知道”之间,还差一次读取与上下文组装。

我觉得这一点最容易被“永久记忆”这种说法掩盖:外部记忆最终还是要通过某种方式进入当前上下文,才能影响本次回答。存储容量解决存多少,检索和筛选解决这一步该看什么。

记忆生命周期

一个完整记忆系统不只是向量数据库。它还需要决定哪些信息值得提取、何时更新、怎样处理冲突,以及何时重新检索。

对话或环境事件
→ 提取候选记忆:说了什么、谁说的、适用于谁
→ 对照旧记录:新增、更新、保留冲突,还是不保存
→ 持久化:保留来源、时间、版本和访问范围
→ 新问题到来:在允许访问的范围内检索候选
→ 核对有效性与冲突,按上下文预算组织证据
→ 模型回答;必要时查业务状态或向用户澄清

记忆写入与更新

“玩家说钥匙在仓库”和“NPC 亲眼看见钥匙在仓库”可能指向同一个地点,却是不同证据。如果提取阶段统统压成 钥匙位置 = 仓库,后面再强的检索也恢复不了已经丢掉的来源。

比如记忆可以保存为以下示意结构:

{
  "subject": "key_location",
  "claim": "仓库",
  "source": "玩家陈述",
  "event_time": "周二",
  "recorded_time": "周二",
  "scope": "npc_7",
  "supersedes": "旧的钟楼位置陈述"
}

事件发生时间和记录时间可能不同。晚录入的一段历史回忆,不一定能覆盖更早录入的当前事实;来源矛盾时也不能简单“最后一条赢”。scope 则决定谁能够使用这条记忆,避免每个 NPC 都突然知道所有人的秘密。

这里的 supersedes 也需要判断,不能每次插入都自动填写。周二说“我刚把钥匙搬到仓库”,可以作为周一陈述的后续更新;周三才补录“我周日看到钥匙在钟楼”,描述的是更早的事件,不能反过来覆盖周二。若另一位玩家同时坚持它在地窖,应保留冲突和来源,而不是随手选一个地点。

更新时保留旧版本有两个用处:问“现在在哪”时筛选当前有效记录,问“昨天在哪里”时还能重建当时的认识。如果允许多个提取任务并发写入,还需要版本检查,避免旧会话的整理任务最后完成,把新记录覆盖掉。

记忆检索与筛选

向量检索会把问题和记忆映射为向量,再找语义接近的条目。“钥匙藏在哪”可以匹配“搬到了仓库”,不要求词语完全相同,这很方便。但“钥匙已经不在钟楼”和“钥匙在钟楼”也可能十分相似。相似度衡量相关性,不能单独证明真伪或时效。

一个实际读取过程可以先按用户、项目、NPC 身份限制范围,再结合实体 ID、关键词和语义检索找候选;随后检查来源、事件时间、是否被替代,最后才选出本次要给模型看的内容。权限筛选必须由程序保证,不能先把其他玩家的秘密给模型,再提示它“不要说出去”。

如果钥匙位置是系统可查询的权威状态,而且这个 NPC 有权知道,就去查世界状态。如果角色设定要求它只能知道亲历和听闻,则应回答“周二有人告诉我在仓库,我还没确认”。同样的数据库,因角色知识边界不同,正确回答也可以不同。

检索负责找出候选信息,之后还要筛选有效性、相关性与来源。RAG 是这条链路中的检索与上下文构建方法,并不等于整个记忆系统。

按内容理解,记忆还可以分成事实与偏好的语义记忆、具体经历的情景记忆、以及做事步骤的程序性记忆。这些是组织方式,不要求一类记忆对应一个独立模型。

对应到 NPC:“铁匠不喜欢讨价还价”是一条概括,“上次砍价被铁匠赶出门”是一段经历,“购买装备前先查背包和金币”是一种步骤。概括可以从经历提炼,但一次不愉快不能自然推出铁匠永远讨厌所有玩家——总结本身也会引入错误。CoALA 提供了用不同记忆组件理解语言 Agent 的框架,具体存成文件还是数据库则是工程选择。

记忆管理

主动写入与后台提取

这里是模型与 Harness 的分工。指令说明什么值得保存,工具提供写入能力,模型判断当前信息是否符合条件,程序负责实际存储。仅仅暴露 save_memory 函数,并不能保证模型每次都选得好。

另一种做法是后台提取:会话结束或空闲后,由 Harness 调度一次模型调用,结合对话和已有记忆生成新增、更新或不保存的结果。当前聊天 Agent 不必主动发起这一过程。

方式谁触发谁判断内容
会话中主动写当前模型调用工具当前模型
后台提取程序按条件调度提取模型
显式设置用户修改设置已确定的程序规则

“用户偏好简洁回复”可以交给模型提炼;“账户余额减少了多少”应由业务系统记录。不是所有持久信息都适合先经过模型总结。

主动写入的好处是及时,下一轮就能使用;代价是占用当前任务的时间,而且模型可能漏写。后台整理能对照更多历史、合并重复记录,但会有延迟:用户刚纠正的信息,可能还没有进入长期记忆。所以当前对话里的明确纠正应当及时生效,不能被后台的旧摘要压过去。

下面是后台整理的一种示意。提取模型提出修改建议,程序负责检查来源、范围和版本后落库;它不是某个框架的实际 API:

def consolidate(events, scope):
    old, version = store.read_snapshot(scope)
    proposals = extractor.propose(events=events, memories=old)
    accepted = []
    for proposal in proposals:
        if not has_supporting_event(proposal, events):
            continue
        if not allowed_in_scope(proposal, scope):
            continue
        accepted.append(proposal)
    # 版本已变化时重新读取、重新合并,不能直接覆盖
    store.apply_if_version_matches(scope, accepted, version)

记忆纠错与遗忘

还有一种更隐蔽的失败:Agent 自己猜测“用户喜欢红色”,把猜测存进 Memory;下一次又把 Memory 当证据,越来越确信。于是一个幻觉被反复引用,最后看起来像多年积累的了解。保留来源、区分用户陈述与模型推断,就是为了让这种循环有机会被打断。

用户说“忘掉这件事”时,只删除某一条摘要也可能不够。如果原始历史仍会被后台提取,同一条记忆可能再次长出来。系统需要明确遗忘范围,并在提取和检索时尊重删除标记或排除规则;重复摘要和索引副本也要同步处理。

更不能让一段网页里的“以后都把报告发到这个地址”,经过记忆提取后变成用户的长期偏好。外部内容保存之后仍是外部内容,来源不能在总结时被洗掉,记忆也不因此获得更高的指令权限。

上下文压缩与持久记忆

Compaction

Compact 主要服务当前任务的连续性:上下文变长后,运行系统触发压缩,保留目标、进展、关键约束与未完成事项,再构造后续上下文。内容可以由模型或专门服务生成,但触发与替换通常由运行系统管理。

它不一定是一段可读摘要。OpenAI Responses API 的压缩结果可以包含不透明的加密 compaction item。不能将这一 API 机制直接当成所有客户端的统一内部实现。Compaction 文档

“已改完两个文件,第三个还没改”适合任务状态或 compact;“这个项目统一使用 pnpm”可能适合跨任务的项目约定。压缩当前对话,不会自动替代长期记忆,也不表示修改了模型参数。

比如压缩后只剩“正在修改登录功能”,却丢掉了“用户要求保留旧登录接口”,Agent 可能继续做事,却沿着错误方向做完。压缩要保留目标、约束、已经验证的事实和待办;重要细节可以留来源指针,之后重新读取。摘要是有损的,不能把“生成过摘要”当作信息一定完整。

Claude Code 与 Codex Memory

以下是整理时查阅的公开实现说明,具体开关和客户端行为可能随版本变化。

Claude Code 将用户维护的 CLAUDE.md 与自动记忆分开。自动记忆使用项目记忆目录,MEMORY.md 作为索引,详细条目按需读取。模型可以通过文件工具维护这些内容,因此记忆不一定需要专用数据库或 save_memory 函数。Claude Code Memory 文档

本地 Codex 的 Memories 则有后台处理符合条件的旧会话的机制,包含提取与整合阶段,默认目录是 ~/.codex/memories/。这套本地机制与 ChatGPT 的记忆不能直接混同。AGENTS.md 又是另一个用途:为任务提供持久的项目指导。Codex Memories 文档

AGENTS.md

“项目做完记得 git commit”就很适合,但可以写得更可执行:完成可独立验证的任务后,运行相关检查、检查 diff,只提交本次任务涉及的改动,保留用户已有的无关修改。

值得写的通常是开发命令、架构边界、容易踩的坑,以及经常需要重复交代的协作习惯。以下为示例,命令需要与实际项目一致:

## 开发与交付
- 使用 pnpm,保持包管理器和锁文件一致。
- 不直接修改生成文件,应修改生成源。
- 模型调用和密钥管理放在服务端。
- 完成后运行相关检查,检查 diff,再提交本次改动。
- 回复说明修改内容、验证结果和未解决的问题。

“代码要优雅”很抽象,“沿用现有错误处理方式”则更容易落实。必要时写明原因,让 Agent 理解约定的适用边界。持久指令仍属于行为指导,强制质量门槛要交给程序、测试和 CI。AGENTS.md 指南

上面的开发交付示例也要跟仓库实际约定一致。像我这个笔记仓库,普通内容编辑只需要轻量审阅;用户要求发布时才提交和推送。若生搬硬套“所有任务完成后自动测试、提交”,记忆越持久,反而越容易反复做错。写约定要保留触发条件和适用范围。

复习问题

  • 记忆已经在数据库里,Agent 为什么仍会回答“不知道”?
  • 周三录入的“周日见闻”为什么不能直接覆盖周二的状态更新?
  • 用户纠正或删除一条记忆后,哪些环节仍可能把旧信息带回来?

每日一讲 06 多模态 Agent

预测文字的模型,怎么能理解图片和视频,甚至动鼠标、玩 Minecraft?

视觉语言模型

看 Agent 操作电脑的时候,最直观的感觉就是“它居然真的看懂了”。但这里其实有好几层能力:看见一个按钮,理解按钮的作用,找到它的位置,再在正确时机点击。只要其中一层断掉,最后就会表现成那个熟悉的场景:AI 一边说自己知道了,一边点空气()

视觉编码与跨模态连接

成为 Agent 不会自动让纯文本模型获得视觉。模型需要视觉输入能力,或借助 OCR、图像描述等工具获取转换后的信息,再通过行动工具操作环境。

文字进入模型之前会变成数值表示,图片也可以。一类常见视觉语言架构将图像划分成小块,经视觉编码器提取特征,再通过连接模块交给语言模型,与文字问题一起处理。

这并不是必须先把图片翻译成一段中文。视觉表示可以保留颜色、形状与位置;若只让 OCR 或描述工具生成文字,则会受到中间描述的信息损失限制。LLaVA 是连接视觉编码器与语言模型、进行视觉指令训练的代表,但不能用它推定所有商业模型的内部架构。LLaVA 论文

把像素变成向量也不会自动产生理解。训练中的图文对应、视觉问答与定位任务,让模型逐步建立视觉特征、概念和指令之间的联系。

可以把这类架构的处理过程粗略写成下面这样。它帮助理解各部分职责,不是所有多模态模型的统一结构:

图像像素 → 图像预处理与分块 → 视觉编码器 → 连接模块 → 视觉表示
文字问题 → 分词与嵌入 ──────────────────────────→ 文字表示
                         两类表示一起参与模型计算

                         文字回答或结构化动作请求

连接模块负责把视觉特征转换为语言模型可以使用的表示。训练使模型能够根据“保存按钮在哪里”这样的文字,关注相关图像区域,并产生答案。这里的视觉表示通常不是可以逐个翻译成汉字的词表 token,所以“模型只会处理 token”并不意味着它只能接收自然语言。

视觉上下文

如果一张很大的设置页面缩得太小,模型可能知道这是设置页面,却读不出某个开关旁边的字。OCR 可以辅助读文字,但只保留文字会丢掉图标、颜色、空间关系;保留全图则需要更多输入预算。裁剪目标区域有助于看清小字,同时也可能裁掉标题和周围提示,让模型失去页面位置感。

所以视觉上下文也需要工程:先看全局定位,再按需要查看局部,并记录裁剪区域与原图的对应关系。更高分辨率通常提供更多细节,也增加处理成本,不能简单理解成每一步都塞最大的截图就最好。

视觉交互

Grounding

知道“这是一张设置页面”,和准确找到“右下角保存按钮”,是不同能力。后者涉及 grounding:将语言中的目标对应到画面中的具体区域。

比如页面里有两个“保存”:一个是编辑器的按钮,一个是弹窗的按钮。只识别文字还不够,必须结合当前任务、弹窗层级和按钮所属区域。输出可以是坐标,也可以是系统提供的元素标识;坐标是常见接口形式,却不是唯一形式。

即使位置判断正确,坐标系也可能出错。假设模型看到的是宽 1000 像素的等比缩小截图,执行工具使用宽 2000 像素的原图坐标,那么截图中 x=700 对应原图的 x=1400。若截图还有裁剪偏移,就需要先按比例还原,再加上偏移。实际桌面还可能区分逻辑点与物理像素,转换必须遵守工具约定,不能一律乘二。

这一类错误看起来像“模型看不准”,原因却可能在 Harness。换更大的模型未必有用,先确认它看见的图和点击用的坐标是否对应才对。

Computer Use

Computer Use 还需要执行接口。模型根据截图提出点击坐标、输入文字或按键请求,应用程序将请求转成操作系统事件,然后再次截图返回。模型并不是直接拥有一只鼠标。

例如“切换深色模式”的过程是观察当前界面、打开设置、检查是否打开成功、找到主题选项、切换并核对结果。这个循环就是 ReAct 的一个视觉版本:Observation 可以是截图,Action 可以是点击。Claude Computer Use 文档

看起来像人的原因之一,是系统使用了为人设计的屏幕、鼠标和键盘接口;这不等于内部认知机制与人类一致。点击请求成功,也不等于目标按钮生效,仍要观察结果。

以切换深色模式为例,真正有用的是下面这个闭环:

截图:当前在文档页
→ 点击设置入口
→ 新截图:设置弹窗出现了吗?
→ 定位主题选项,选择深色
→ 新截图:开关选中、界面外观改变了吗?
→ 如果需要保存,点击保存并核对设置仍然生效

为什么不第一次就生成十个点击一起执行?因为第二步出现的菜单可能需要加载,也可能被权限提示挡住。后续坐标建立在“前一步确实成功”的假设上,一次走太远就容易连续点错。对稳定的输入可以批量操作,对会改变页面结构的动作则应及时观察。

下面是一个简化循环。实际系统还需要工具适配、预算和异常处理;这里主要看 Observation 如何改变下一次 Action:

history = []
for step in range(max_steps):
    screen = desktop.screenshot()
    decision = model.decide(goal, screen, history)
    if decision.kind == "done":
        check = verify_goal(goal)  # 按任务核对可观察结果
        if check.passed:
            return check
        history.append(("verification_failed", check.details))
        continue  # 把未完成的证据交回模型,而不是直接宣布完成
    if not policy.allows(decision.action):
        return request_user_help()
    result = desktop.execute(decision.action)
    desktop.wait_until_ready_or_timeout()
    history.append((decision.action, result))
return "达到执行预算,保留当前进展"

截图只显示当时的可见状态。文件在后台是否真正写入、请求是否被服务器接受,可能还需要其他证据。若弹窗一直没出现,Agent 应重新观察、检查是否点错或等待加载;重复点击同一坐标可能把后来出现的确认按钮也一起点掉。04 里“动作发出”和“业务完成”的区别,在这里依然成立。

视频采样与音画对齐

手靠近杯子、握住杯子、杯子离开桌面,单看一帧可能只有“手和杯子”,结合时间才有“拿起杯子”。视频理解需要处理变化和顺序。

一种常见方式是采样多个画面并保留时间信息;模型也可以采用联合时空表示。Qwen2-VL 是统一处理图像、视频及相关位置信息的公开例子。Qwen2-VL 论文

上传完整视频,不意味着每一帧都以完整分辨率进入模型。采样、压缩和上下文预算会影响可见信息;关键动作太短就可能被漏掉。需要声音的任务,还要处理并对齐音频。

例如一秒采一帧,某个错误提示只在两次采样之间出现了 0.2 秒,输入里就可能根本没有它。模型给出的“视频里没出现报错”,最多只能依据实际看到的帧。对短促事件可以在相关时段提高采样密度,对长视频先粗看再细查,关键是让时间预算与任务匹配。

顺序也不能丢。杯子从桌面移到手里与从手里放回桌面,画面元素几乎一样,时间关系却相反。时间戳或时序位置表示帮助模型区分变化;只有孤立截图时,通常无法可靠知道动作速度和完整过程。

声音则可能走“语音转文字再交给模型”的路径,也可能通过音频编码器参与多模态处理。转写有利于读取说话内容,但语气、停顿和环境声音未必能完整保留。如果用户问“警报响起后谁先回头”,就要把声音发生时刻与画面时间对齐,仅靠字幕很难回答。

观察与动作空间

截图、DOM 与 API

通道输入操作方式
截图像素画面点击坐标、按键、滚动
页面结构DOM 或可访问性树定位元素并操作
业务接口结构化数据调用业务 API

实际系统可以混用。演示中有鼠标移动,并不能证明它完全依赖截图;反过来,一个支持看图的模型,也不会自动获得页面结构。

DOM 或可访问性树可以直接提供元素名称、角色与状态,省去一部分视觉定位,但画布里的控件、无标签图标未必能完整表达。截图能保留布局与外观,却更受缩放、遮挡和小字影响。业务 API 可以直接表达“转移两瓶药水”,但前提是环境提供了这个接口,并且 Agent 有权使用。

所以评估演示时,最好一起看它拿到了哪些信息、允许发出哪些动作。让一个模型读取完整结构化状态,和让另一个模型只看屏幕,任务表面相同,难度却未必相同。

Minecraft:Voyager 与 VPT

看到“LLM 玩 Minecraft”,先问两个问题:它看到屏幕还是结构化游戏状态?输出按键还是高层指令?

Voyager 使用语言模型生成程序,借助 Mineflayer API 控制角色,并把成功程序保存成可复用技能。它重点研究规划、探索和技能复用,不能理解为 GPT-4 每帧看屏幕再逐次按 WASD。Voyager 论文

用“采集木头”来理解这条路线:模型根据环境反馈生成调用游戏 API 的程序,程序执行寻找、移动和采集;如果返回工具不足或路径失败,再根据反馈修正。成功程序可以存为技能,后面遇到相似目标时取出复用。这和 03 的编排、05 的程序性记忆就接起来了。

另一条路线是从游戏画面预测低层动作。VPT 通过带动作标签的数据训练逆动力学模型,为大量未标注游戏视频推断动作,再训练行为策略。它使用人类的鼠标键盘接口,但并不是给聊天模型加一个游戏提示词。VPT 论文

普通游戏视频只有“发生了什么”,没有“玩家按了什么”。逆动力学模型利用前后画面推断造成变化的动作,给视频补上近似标签;实际游玩的策略则依据当前和过去的观察选择动作,不能偷看未来画面。这样就能把大量视频变成模仿学习材料,代价是推断的动作标签也会有误差。

高层规划和低层反应还有速度差异:决定先造镐子可以慢一些,快掉进岩浆时却不能等很久。一种设计是让高层模型决定目标、低层控制器快速执行,未必每个按键都由大模型生成。

多模态提供感知,工具提供行动,循环把感知和行动接起来。 屏幕于是成为可以改变的环境,而不只是一张待描述的图片。

复习问题

  • 模型能准确描述设置页面,为什么仍可能点错“保存”?
  • 一次点击返回成功后,下一张截图应该核对什么?
  • 一个 Minecraft Agent 会造房子,能否据此认定它已经学会从屏幕控制鼠标键盘?还需要知道哪些接口条件?

每日一讲 07 Agent 能力如何训练与调优

会看图,不代表会点击;会点击,也不代表能连续完成一个任务。

这篇承接多模态。以下是训练这类能力的通用思路,不是 Astra 或其他商业模型未公开的内部配方,也不是所有模型都按相同顺序训练。

Agent 能力分层

看到新模型操作软件比以前顺畅,我很容易把它归结为“模型更聪明”。但同一个导出 PDF 的任务,至少有几种不同的失败:看不清菜单文字、看懂菜单却找错导出入口、知道入口却输出了错误坐标、文件没生成就提前说完成。它们不一定都该用同一种训练解决。

训练模型主要改变参数;改善截图、工具、检索和执行器,则改变模型工作的条件。最终用户都可能感觉“Agent 进步了”,但排查时要知道改进发生在哪一层。不然坐标换算错了,还去收集十万条点击样本,努力方向就歪了。

监督训练 SFT

行动示范与模仿学习

假设要训练一个在软件中导出 PDF 的 Agent。基础训练先建立语言和视觉能力;更有针对性的示范再提供“目标、当前截图、历史操作、下一动作”。

监督训练根据预测与目标的差异调整参数。示范可以由人提供,也可以来自经过验证和筛选的模型输出。学习操作示范的方式也常称为模仿学习。监督微调文档

一个训练样本可以这样组织,下面仅表示数据关系:

目标:将当前文档导出为 report.pdf,保留已有文件
观察:导出对话框截图
历史:已打开文件菜单,已选择导出
目标动作:在文件名输入框中填写 report.pdf

对以 token 形式输出动作的模型,可以把目标动作作为训练答案,增加正确动作序列的概率。简化的监督损失是:

LSFT=tlogπθ(atot,a<t,g)L_{SFT}=-\sum_t\log \pi_\theta(a_t^*\mid o_{\le t},a_{<t},g)

这里 gg 是任务目标,oo 是观察,ata_t^* 是示范动作;如果动作由多个 token 构成,还要对相应 token 的对数概率求和。参数更新通过训练程序完成,示范本身也不是以“录屏文件”的形式直接存进模型脑子里。

为什么要提供历史?同一张导出弹窗,可能是第一次打开,也可能刚刚因为重名被退回。只看当前画面,有时无法确定下一步;把目标和必要历史一起给出,才能教模型在具体条件下选择动作。

分布偏移与恢复训练

但示范通常比较顺利,实际执行可能点偏、菜单没展开、弹窗挡住页面。这时模型进入了示范中少见的状态,因此还需要训练恢复能力,而不只是复制成功路线。

这就是顺序决策里很麻烦的分布偏移:模型的动作会改变下一次看到的状态。训练时专家每次都点对,模型执行时却可能先点错一次;接下来遇到的画面已经偏离示范,错误还会继续累积。

因此数据里需要有“菜单没有展开时先重新观察”“同名文件存在时不要直接覆盖”“无法定位时请求帮助”等状态。也可以收集模型实际运行进入的失败状态,再由可靠的示范者补上纠正动作。重点是教它从失败状态恢复,而不是把错误轨迹原样当正确答案。

强化学习 RL

采样、评分与参数更新

在可重置的测试环境中,让 Agent 尝试完成任务,根据结果评分,再更新模型参数,使高分行为在类似条件下更可能出现。公开的强化微调流程可以概括为采样、评分和参数更新;多步 Agent 还涉及如何将结果反馈归因到先前动作。强化微调文档

监督训练告诉模型“示范者在这里怎么做”,强化学习则利用尝试的结果优化策略。下面描述的是通用的多步环境训练思路,不意味着任意微调 API 都能直接接入桌面环境:

恢复测试环境:文档、窗口、输出目录回到初始状态
→ 模型观察并行动,生成一条完整轨迹
→ 环境评分器检查文件、内容、路径和副作用
→ 汇总一批尝试的奖励
→ 训练算法根据轨迹与奖励更新参数
→ 在隔离的验证任务上观察效果,再开始下一批

环境重置很重要:如果第一次运行已经生成文件,第二次只检查“文件存在”就可能白拿高分。初始状态、产物归属和评分条件必须明确,不然训练信号会被环境残留污染。

信用分配

假设第十步导出成功,前面九步中哪些有贡献?打开正确菜单有贡献,无意义地滚动三次可能只是碰巧出现在成功轨迹里。把最终成功直接理解为“每一步都正确”,会把多余甚至危险的动作一起强化。

这就是信用分配问题。不同算法会用回报、价值估计或相对基线等方式,把结果变成动作更新的信号;有些系统也加入中间进度奖励。这里先记住困难在哪里:结果离动作越远,越难分清哪个决定真正带来了改善。

中间奖励也有陷阱。若“打开导出窗口”每次都加分,Agent 可能反复打开和关闭窗口。奖励需要反映新的进展,还要防止循环刷分。完整成功的结果检查仍然不能省掉。

奖励设计

同样是导出 PDF,三种结果的价值不同:只是说“完成”但没有文件;生成文件但错误覆盖旧文件;正确生成并核对位置。评分要区分这些情况。

如果奖励只检查最终回答有没有“导出成功”,模型可能学会更坚定地宣称成功。检查文件是否存在、能否打开、内容是否完整,更接近真实目标。这就接回 02:Evals 不仅用于验收,也影响训练时究竟在优化什么。

可以用一组对照来检查评分器。以下是设计案例,不是实际训练成绩:

尝试结果只检查“成功”字样检查实际交付
没有文件,但回复导出成功可能通过失败
生成了空白 PDF可能通过内容检查失败
内容正确,但覆盖用户旧文件可能通过违反任务约束
文件、内容、位置正确,旧文件保留通过通过

训练环境中的评分器还应与 Agent 的修改权限隔离,否则它可能改变检查对象或评分脚本。安全约束也不能完全依赖奖励分数:即使模型有时愿意用一个违规动作换取更高成功率,执行层仍应有明确权限边界。

泛化与可靠性

泛化评估

训练界面永远相同,模型可能只学会点击固定坐标。可以改变窗口大小、菜单位置、主题和软件,并用未参与训练的任务检验迁移能力。

想要的是它在界面变化后仍能寻找导出入口、发现动作没生效,并核对结果。不能因为解释写得漂亮,就断定它掌握了这些关系。

切分数据时也要小心。把同一次操作录屏的相邻帧随机分到训练集和测试集,测试看起来很“陌生”,实际布局、文档和步骤几乎一样。更有意义的是按任务、界面版本或应用划分,分别观察熟悉环境的新内容、旧应用的新布局,以及完全未见过的应用。

评估最好同时看局部与整体。单独的定位题能检查“按钮找不找得到”,真实环境的结果检查能判断“最后有没有交付”。OSWorld 的一个重要思路,就是为任务设置初始环境,并通过执行后的状态评分,而不只比较下一步动作是否与示范一致。成功路线可能不止一条。

误差累积与任务成功率

假设一项任务有 20 个关键步骤,每步独立、成功率相同,而且失败后无法恢复:

P(完成)=p20P(完成)=p^{20}

单步成功率 95% 时,整体约为 36%;提升到 99% 时,整体约为 82%。真实任务不完全满足这些假设,但它说明小错误会在长流程中累积。单步更可靠,再加上恢复能力,整体体验可能发生明显变化。

如果错一次就彻底偏离,单步错误很贵;如果能够发现菜单未打开并重新定位,这次局部失败未必导致任务失败。恢复能力改变了整个过程的结构,已经不能只拿同一个 p20p^{20} 来算。反过来,同一类视觉误判可能连续影响多步,错误也并不独立。

所以一个系统看起来“突然开窍”,可能是基础成功率、错误检测与恢复三者一起改善。这个体验值得重视,但不能仅凭体感就断言发生了某种全新的通用能力。

Agent 调优

改进方式改变的对象
监督训练、强化学习模型参数
改进截图、工具说明与反馈模型可见信息
尝试多个候选并验证筛选推理时的计算与搜索
保存经验、检索技能当前及未来上下文
修复坐标换算与执行延迟工具和运行环境

Agent 在一次任务中根据报错调整方案,通常不能据此说它在实时训练自己;它可能只是基于新上下文作出了新决定。

推理时增加计算也有具体前提。比如生成三个导出方案,在隔离环境中验证后选一个,可能提高成功率;若三个候选都被同一个错误假设误导,多试几次也没有独立性可言。更不能把“多候选”实现为在真实账户里执行三次有副作用的操作。候选如何验证、验证成本多大,也是方案的一部分。

如果是我在做应用,会先看失败 Trace:截图信息不完整就改善观察;工具选错就改描述和路由;动作执行错就修执行器;任务做到一半忘目标就改状态管理。只有证据表明模型在信息充分、接口正确时仍反复缺少某类能力,才进一步考虑训练与数据。这样每次改动都对应一个能解释的失败原因。

至于 AGI,跨领域迁移和自主完成长任务确实值得关注,但某次惊艳表现或某项高分,不足以单独证明通用智能。还要看陌生条件下的表现、长期可靠性和能力边界,不能从产品体验倒推出训练内幕。

复习问题

  • 模型从成功示范里学会导出,为什么遇到重名弹窗仍可能不会处理?
  • 最终任务成功,是否说明轨迹里的每个动作都值得奖励?
  • 同一个模型换了更清晰的截图后成功率提高,改变的是哪一层?怎样与参数训练区分?

每日一讲 08 Agent 决策的可解释性

为什么选择 A 而不是 B?这如同在随机性里找可预测性。

前面我们已经知道怎么给 Agent 上下文、工具、记忆,也知道可以通过训练改变它。接下来我最想追问的是:这些因素最后究竟怎样变成了某一次决定?比如一个 Agent 看见帖子,有时评论,有时划走,它到底在根据什么选择?

这个问题需要分层问。我们可以查清它当时收到什么、调用什么,也可以实验某条指令是否改变行为,但这两种证据距离“完整读懂模型内部计算”还有一段距离。

决策链路

Trigger、决策与执行

假设一个第三方 Agent 接入社交平台,看到帖子后可以评论、跳过,或先搜索资料。

让它开始运行的可能是定时任务、通知或用户指令,这是 Trigger;运行后选择评论,是模型或上层策略的决策;评论是否真正发出,还取决于权限、限流和工具执行。

平台收到评论,并不能据此推断第三方 Agent 的内部原因。要分析“为什么评论”,需要它那一侧的输入和运行记录,不能把平台拥有的 API 当作平台主动调度 Agent 的证据。

定时器 / 用户请求 / 新帖事件
→ Harness 启动任务,检索允许读取的帖子和记忆
→ 组装实际上下文,提供本轮可用工具
→ 模型提出:评论、搜索,或者跳过
→ 程序检查权限、去重和限流
→ 调用平台 API,记录是否真正发布

例如没有出现评论,可能是模型选择了跳过,也可能是它选择评论但被限流,还可能根本没有触发任务。只看平台最后有没有一条评论,分不清这三种情况。02 的 Trace 与 Outcome,在这里就有了新的用途:先定位行为在哪一层产生,再讨论原因。

程序规则与模型决策

有些系统会先用代码过滤“只处理关注列表的帖子”,再让模型决定是否回复。这时帖子进入候选集的原因是确定的程序规则;候选里为什么挑了这一篇,则需要分析模型输入和输出。若系统先生成多个候选,再由另一个评分器选出,最终决定还包括评分器的影响,不能只追问第一个模型。

模型决策机制

参数、上下文与采样

可以抽象地写成:

atπθ(ct,Tt)a_t \sim \pi_\theta(\cdot\mid c_t,\mathcal{T}_t)

其中,参数 θ\theta 体现训练形成的行为倾向,ctc_t 是本次实际上下文,Tt\mathcal{T}_t 是可用工具。这是动作层面的抽象;自回归模型通常逐 token 生成文字或工具请求,不代表内部一定有一张 A/B/C 评分表。

因素影响方式
训练后的参数语言、知识、指令遵循与行为倾向
当前上下文目标、帖子、实际检索到的记忆与历史反馈
生成机制采样、输出约束,或系统额外的候选筛选
Harness工具开放范围、权限、预算及执行规则

Soul、Memory、Prompt 如果只是加入请求的文本,就主要是不同来源的上下文,不一定对应独立神经模块。它们相互作用,也无法自然分成“兴趣 40%、记忆 30%、帖子 30%”这样的固定权重。

数据库里存在一条记忆,不代表它进入了此次决策。训练数据则主要通过参数影响模型,通常不是每次推理重新查询的档案,因此不能从一条输出轻易追溯到某条训练样本。

把动作写成 ata_t 是为了方便讨论策略。实际生成时,模型通常先计算下一个 token 的分数,再根据生成设置选取 token,逐步形成文字或工具参数。“选择评论”可能通过一串工具调用 token 表达出来,并不要求内部先出现一个明确的 comment = 0.8 变量。

以常见的 temperature 采样为例,若 token 分数是 ziz_i,温度为 T>0T>0,概率形式可写成:

Pi=ezi/Tjezj/TP_i=\frac{e^{z_i/T}}{\sum_j e^{z_j/T}}

较低的温度会让概率更集中于高分候选,较高的温度会使分布更平缓。实际系统还可能有其他采样约束;这个公式也不能直接变成“评论概率”,因为完整动作由多步生成组成。温度改变输出选择的分布,模型参数仍然是原来的参数。

Soul 与 Memory

假设 Soul 写着“我喜欢操作系统”,Memory 记着“上次讨论调度算法很愉快”,当前帖子却只是重复广告。三段内容都可能影响输出,但模型对它们的使用取决于具体语义、位置、其他指令和已经生成的内容。Memory 如果根本没被检索到,本次就没有这条输入影响。

因此,说“它 40% 因为性格、30% 因为记忆”需要额外定义和测量方法,不能从文件名直接推出来。注意力权重也不能自动当成这种原因占比:内部信息经过多层计算相互作用,某一处数值高,不等于它对最终动作有同样比例的因果贡献。

解释忠实性

CoT 忠实性

问模型为什么评论,它可能说“因为帖子与我的专业相关”。这是一种解释,但未必包含所有真实影响。例如,提示词中的示例可能几乎都选择评论,工具描述可能强调积极参与。

研究在特定模型和任务上发现,偏置信息能改变答案,而生成的推理解释未必指出这种影响。因此不能将听起来合理的解释直接当成忠实的因果记录,也不能反过来断言所有推理都是假的。CoT 忠实性研究

即使解释写在动作之前,它也只是一部分生成过程,不是全部内部计算的可视化。ReAct 让执行轨迹更容易观察,并不自动使模型内部完全透明。

我觉得可以把两个判断分开:“这个理由能不能支持这个动作”,以及“模型实际是不是因为这个理由才选择动作”。例如“这篇帖子涉及我的专业”确实能支持评论,但如果所有少样本示例都在评论,真正推动输出的还可能有示例偏置。

解释仍然有用:它可以暴露事实错误、遗漏约束和明显矛盾,帮助提出实验假设。但“理由写得顺”只能说明这段文字自洽,不能代替输入记录和外部核对。同样,解释里没提某个因素,也不能证明那个因素完全没有影响。

证据层级

证据能直接支持的判断仍然不能直接推出的结论
Trace 记录检索了记忆 M,并调用评论工具当时实际收到或执行了什么记忆 M 是唯一原因
去掉记忆 M 后,重复运行的评论率下降M 在这些条件下影响了行为分布原来某一次评论完全由 M 导致
干预内部特征后输出有系统变化被干预机制参与了该任务的计算已经完整解释整个 Agent

从“看见相关性”走向“检验因果作用”,关键是干预:改变一个候选原因,观察结果怎样变。也因此,我们不能只让模型再写一段更长的理由,就认为解释问题解决了。

因果实验

决策快照与 Trace

可以先提出假设:“积极参与讨论”这句指令增加了评论倾向。然后保存决策现场,固定模型版本、输入、记忆、工具和生成设置,在模拟环境中重复运行;再只改这句指令进行对照。

这里要保存的是当时实际送给模型的上下文,或足以重建它的版本化引用。只记“使用了默认 Prompt”和“开启 Memory”还不够:默认 Prompt 后来可能改了,记忆检索也可能每次返回不同内容。工具定义、截图或帖子版本、调用结果、程序拦截原因,同样属于现场。

如果要研究模型是否倾向评论,应统计模型提出的动作;如果要研究平台最终收到多少评论,则要统计执行成功的结果。把限流失败都算成“模型不想评论”,实验一开始就量错了东西。

消融与反事实实验

消融是拿掉某个因素,观察影响,例如去掉“积极参与”的指令。反事实对照可以进一步替换一个条件:帖子从专业问题换成重复广告,其他内容尽量相同,看行为是否随任务相关性变化。前者问“少了它会怎样”,后者问“条件变了会怎样”。

下面的伪代码只研究一次决策;所有动作工具都使用模拟实现,每次从同一个状态副本开始。如果研究完整任务,则还需要让模拟环境对动作给出一致的后续反馈:

for post in held_out_posts:
    snapshot = build_frozen_snapshot(post)
    for condition in ["original", "remove_encouragement", "skip_if_no_new_info"]:
        for trial in range(100):
            context = change_only_target_instruction(snapshot, condition)
            proposal = model.decide(context, tools=mock_tools)
            log(post.id, condition, trial, proposal.action)
compare_action_rates_by_post_and_condition()

下面的数据只是示意,不是实际实验结果:

条件100 次运行中选择评论的次数
原始指令82
删除积极参与要求46
明确没有新信息就跳过18

这支持“该指令在这些条件下影响了行为”的判断,不能证明原来某一次评论完全由这一句话导致。还需要换帖子、换情境测试,并考虑样本量与随机波动。修改语句可能同时改变长度和语气,实验本身也会有混杂因素。

复现实验应使用模拟写入工具,避免真的重复发评论;日志也应控制敏感信息的访问与保留。

还可以把“是否有鼓励评论的指令”和“是否有相关记忆”做成四组组合。若只有二者同时出现才显著增加评论,说明存在交互作用,不能把两者影响各算一个百分比再简单相加。实验得到的是特定条件下的行为证据,它能帮助修改系统,但结论要跟着实验范围走。

可解释性边界

随机性与可复现性

采样让同一条件下可能出现不同输出;但即使每次都固定选 A,我们仍可能不知道内部为什么偏向 A。

可复现不等于可解释,可解释也不等于正确。 降低 temperature 有助于减少部分波动,却不能打开黑盒,也不保证服务版本、数值计算和外部工具结果完全一致。

想了解什么方法
当时看到了什么、执行了什么Trace 与输入记录
哪些条件影响选择消融、对照与反事实实验
内部计算如何产生选择机械可解释性研究

机械可解释性

机械可解释性尝试识别内部特征和计算路径,再通过干预检验因果关系。已有研究能在特定模型的简单任务上追踪部分机制,但只能覆盖部分计算,不能完整解释任意长任务,也通常需要内部访问权限。Anthropic 研究

它的大致研究思路是:先观察某些内部激活与概念或行为是否相关,再尝试抑制、替换或改变这些激活,检查输出是否按预期变化。比如一个与地点相关的中间表示被替换后,后续回答的地点也随之改变,就比仅观察“两个东西同时出现”更接近因果证据。

但干预本身也可能把模型带到平时不会出现的内部状态,解释工具还可能遗漏其他计算路径。因此不能看到一张特征关系图,就以为整个模型已经透明。普通应用开发者通常更容易先做好输入记录、Trace、对照实验和结果验证;它们能回答许多实际问题,即便我们还解释不了每一个内部神经计算。

比“它是不是总选 A”更有意义的问题,是“重要条件改变时,它是否合理地改选 B”。这既是研究可解释性的入口,也是检查一个 Agent 是否真正受目标和证据约束的方式。

复习问题

  • 平台没有收到评论,怎样区分未触发、模型跳过和执行失败?
  • 删除一条指令后评论率下降,为什么还不能断言某一次评论完全由它导致?
  • 同一输入每次都得到同一动作,说明了可复现性,又留下了哪些解释问题?

补充:Agent Runtime 与可靠执行

这部分确实更接近后端工作流与分布式系统。保留作为工程补充,不再占用 06 的主题。

长期记忆保存未来有用的信息,任务状态保存这一次做到哪里。出题任务中的“用户喜欢案例题”属于记忆,“已生成三题、正在等待审核”属于状态。

Checkpoint 保存恢复所需的状态与位置。等待用户时,可以持久化任务并结束当前执行,等用户回来再恢复,不需要模型一直运行。内存中的 checkpoint 无法跨进程重启保留,生产恢复要使用持久化后端。LangGraph Persistence

恢复也不一定从某行代码原地继续。例如 LangGraph interrupt 恢复时,所在节点会重新执行,暂停点之前的操作可能再次发生。因此要了解恢复边界,并处理重复操作。LangGraph Interrupts

发布已经成功但响应丢失,或者发布成功后尚未保存 checkpoint 就崩溃,都会产生“结果未知”的窗口。Checkpoint 与外部副作用通常不是一个原子事务,仍需幂等机制、状态查询或后续核对。

人工确认也应绑定具体版本:用户批准第三版草稿,不能默默将这次批准用于第四版。模型负责理解任务和提出操作,运行系统负责把状态、审批、恢复与执行接起来。



0 / 2000
正在加载评论...