跳到正文
返回

语言模型开始拥有文字世界的物理

8分钟阅读

模型写下“我已经把文件里的 2 改成了 3”,文件里的数字不会因此变化。可如果这段输出进入了编辑工具,工具真的完成写入,下一次读取就会得到 3。同样是文字,一种留在对话里,另一种穿过接口,在环境中留下了后果。语言模型开始能够做事,就发生在这条连接建立之后。

我把这种环境叫作“文字世界”。文件、目录、命令、检索结果和错误信息,都是模型通过文字接触到的对象;对象之间又有稳定的关系。文件移走之后,旧路径就找不到它;测试要等进程运行才有结果;一个操作成功,后续能够做的事情也会变化。这些规律构成了文字世界的物理:行动会改变状态,状态会约束下一次行动。

下面有一个只存在于页面里的小文件,任务是把它改成 count = 3。可以先试着宣布完成,再运行检查;也可以实际写入,随后读取或检查。留意文件的状态、对话里的说法和工具返回的结果:它们会在不同的操作之后变化。

让文字产生后果目标:让文件包含 count = 3。可以先宣布完成,再运行检查。
环境 · settings.txtcount = 2
对话

还没有行动。

最近一次工具结果

还没有行动。

仅在页面内部模拟小环境,不操作真实文件、不执行命令、不调用模型。

先熟悉这个世界,再学会在里面做事

有了接口,模型还要学会如何使用它。什么时候该读取文件,工具需要哪些参数,报错意味着什么,都需要相应的经验。Toolformer就把工具调用插入训练材料,筛选有帮助的调用,再让模型学习何时调用、传什么参数,以及怎样利用返回结果。工具使用由此可以成为训练获得的能力,像阅读、翻译一样,被后来的任务调动起来。

中间训练可以沿着这个方向理解:通用语言能力形成之后,再用更有针对性的材料,让模型积累新领域的知识与问题结构。代码与文档让它逐渐理解构建、依赖和程序行为,这些理解会参与后来的排查与修改;针对长序列的训练则让它适应更长的信息结构。比如 Llama 3 的训练报告把长上下文训练安排在预训练后段。不同配方对阶段的命名有所不同,共同的作用是扩充模型理解和处理任务的基础。外部接口再把这些基础接到具体项目、文件和服务上。

后训练则进一步塑造它在这些情境里怎样选择。优秀的操作轨迹可以示范,为什么先确认文件位置再修改,为什么失败后要换一个假设;能够执行和评分的环境,还可以让模型从尝试的结果中学习。中间训练积累的熟悉程度,与后训练培养的行动技巧接在一起,模型就更容易把一串工具调用组织成有目的的工作。

有了物理,还要把真实任务搬进来

现在,让这个模型去处理一次真实的上传故障。它已经会读日志、调用接口、修改文件,但“帮用户恢复上传”仍然留下很多空白:是哪位用户的哪一次上传,文件现在在哪里,重试会不会重复创建记录,怎样才算恢复?给模型一个终端,并不会自动回答这些问题。文字世界已经能动起来,接下来要做的是把业务世界里的对象、关系和约束表达进去。

上下文工程的核心,是把真实任务建模成模型能够理解、观察和操作的处境。 输入窗口只是这份模型当前展开的部分。更完整的内容分布在任务说明、工具接口、文件、数据库和历史记录里,通过观察逐步进入窗口。我们要设计的是这整套表达:哪些差别必须让模型看见,哪些状态可以查询,怎样把下一次行动接到当前事实之上。

这件事可以沿着上传故障具体展开:

  • 目标:恢复到哪个状态? 用户可能希望继续上传原文件,也可能只想取回已经上传的部分。任务说明要保留这个区别,还要交代不能丢失原文件、不能产生重复记录等约束。“修好上传”只是方向;“这次上传完成,原文件可读取且内容一致”,才开始形成可观察的完成条件。

  • 对象:正在谈论哪一个东西? 用户、文件、上传会话和最终生成的对象,各有不同的身份。把它们都叫“文件”,模型就可能拿文件名去查询上传记录,或者把旧会话的成功当成新会话的成功。稳定的标识、对象之间的关联和必要的字段说明,使日志、接口与用户描述能够指向同一件事。

  • 状态:现在知道什么,还不知道什么? 页面显示失败,可能只是页面没有收到回包;服务端也许已经收齐数据,正在合并。两份观察各自来自哪里、发生在什么时候,需要随结果一起出现。“用户看到失败”“服务端尚未完成”“服务端没有数据”是三个不同判断。上下文把它们混成一个“失败”,模型再强也只能在被压扁的事实里猜测。

  • 行动:每个入口究竟能改变什么? 查询进度、补传缺失分块、重新创建会话,承担的职责不同。工具说明应让模型知道需要哪个标识、动作会影响哪个对象、什么前提下可执行,以及报错之后怎样继续查证。一个只返回“参数错误”的接口,把后续调查的负担留给了猜测;返回“该会话已完成,可查询对象状态”,则把错误变成了继续工作的线索。

  • 反馈:哪份证据说明目标达到了? 重试请求被接受,只证明系统接下了动作;最终对象可以读取、内容校验一致,才支持文件已经恢复。用户是否仍遇到报错,又是更贴近使用结果的一层观察。动作回执、状态观察和任务验收各有用处,把它们分开,模型才能知道自己还差哪一步。

能力如何形成模型与一个可操作的世界相连
  1. 预训练:通用知识与关系
  2. 中训练与后训练:领域知识与交互经验;指令 · 输出格式 · 工具使用
  3. 任务与当前上下文:目标 · 事实 · 约束
  4. 模型:理解处境,选择下一步
  5. 任务与环境建模:对象 · 状态 · 行动 · 完成条件
  6. 任务环境:工具执行动作,返回观察
  7. 实际结果:产物 · 状态变化 · 执行记录
    看图中的关系

    任务建模保留目标、对象与约束;工具和上下文让模型看见状态、采取行动,再利用返回的观察。

    点选节点查看含义。每个视角展开对应文章的概念。

    同样的事实,可以形成完全不同的工作环境

    下面两种表达保存的是同一件事。第一种接近把日志直接扔进对话,第二种把日志放回任务、对象和时间关系里。切换后留意:模型是否已经知道下一步该查什么,以及眼前的“成功”究竟能证明什么。信息量相近,能够支持的判断却不一样。

    这些事实,能告诉我们下一步做什么?同一次上传故障,分别表示为原始记录和关系明确的任务。
    10:00 页面:上传失败10:01 POST /uploads/u-17/retry → 已接受10:02 会话 u-17:分块 8/8;合并中;对象 f-42
    “失败”“已接受”和“8/8”说的是不同环节,还需要重建它们之间的关系。
    记录示意。两种表达都尚未证明文件已经恢复。

    这样的表达也不需要把每一条日志都人工翻译一遍。我们可以在工具层统一返回对象标识、时间、状态与后续查询入口,让模型自己阅读更详细的记录。面向智能体的工具设计强调返回有用、易理解的上下文,正好落在这个位置:接口的质量,取决于结果能否参与下一步判断。一个业务化的查询结果,往往比几十个内部字段更能帮助模型继续工作。

    这里还藏着一个重要取舍:表达得过粗,关键差别会消失;表达得过细,模型又得在庞杂信息里重建任务。合理的抽象保留那些会改变行动选择的差别。例如,为了决定是否补传,需要知道缺少哪些分块,通常不需要知道每台存储节点的磁盘编号;但如果任务变成排查存储故障,后者就可能成为核心上下文。上下文的粒度,跟着要做的决定走。

    上下文要随着工作更新

    早期的上下文工程,常常由程序提前检索资料,再把片段注入提示。系统替模型判断它需要看什么。具备交互能力之后,模型可以自己提出新的观察需求:先查会话,再查对象,发现校验不一致时继续读详细记录。ReAct所呈现的观察与行动交替,是这份任务模型在运行时展开的一种方式。交替本身很容易写成循环,真正影响工作质量的,是循环里每一次观察有没有带回足以改变判断的信息。

    所以,当前窗口适合常驻目标、约束和重要的状态索引;大批日志、完整文档和历史轨迹,则可以留在外部按需获取。检索负责沿问题找到材料,文件负责保存中间产物,压缩负责把长过程整理成可继续工作的状态。按需获取与长期上下文管理解决的都是同一个矛盾:世界很大,而模型每次用于判断的工作空间有限。把资料放在随时能找到的位置,可以同时保留广阔的观察范围和清楚的当前重点。

    长任务里的记忆尤其要保留“事实、推测、未完成事项”的区别。“怀疑服务端丢了分块”如果在压缩后变成“服务端丢了分块”,下一轮就会把待验证的解释当成事实。更有用的接续状态是:已确认哪些观察,排除了什么,当前假设还缺哪份证据,原始记录可以从哪里重新读取。压缩保留下来的这些关系,比一份流畅的故事更能让工作接着走。

    还要记住,环境会在模型思考期间变化。刚读到的“上传中”过一会儿可能已经变成“完成”;另一位操作者也可能改过同一份记录。上下文应把观察时间和版本带回来,让模型在重要动作前知道哪些前提需要重查。至于动作执行时如何阻止版本冲突,则由工具与运行设施落实。任务模型表达关系,执行系统保证约定生效,两者在这里接上。

    这样看,一个智能体在某个领域里迟迟不好用,未必是它少背了一份知识,也可能是我们还没有给它一个表达清楚的工作世界。模型看不见任务之间的区别,就无法稳定地作出不同选择;看不见行动的后果,就无法利用反馈修正。上下文工程做得好的标志,是现实中影响成败的关系,在模型面前仍然清晰可见。文字世界的物理,让模型有机会行动;任务与环境的建模,才让它知道自己究竟在做什么。