详解Pi Agent的上下文管理机制

当我们在讨论智能体的技能实现时,总会遇到一个绕不开的基础问题:Prompt和Context到底该如何区分?这个看似简单的问题,背后藏着智能体上下文管理的核心逻辑。本文将以Pi Agent为例,拆解智能体的上下文构建、运行管理以及长程任务中的上下文优化方案,完整呈现从静态指令到动态信息环境的完整链路。
在日常讨论中,很多人会混淆Prompt和Context的概念。简单来说,Prompt更偏向相对静态的指令设计,它提前定义了模型“应该怎么做”,比如系统提示词、任务规则等;而Context则是智能体在运行过程中动态形成的信息环境,会随着任务推进不断变化,决定模型“此刻基于哪些信息来做决策”。
本文不会直接讨论技能应该被归类为Prompt还是Context,而是先回到更基础的问题:到底什么是Context,智能体中又是如何实现Context管理的?我们将通过Pi Agent的实际架构,看看Prompt、Skill、工具结果、消息等信息是如何被组织,并在智能体执行过程中逐步形成模型实际看到的完整上下文。
一、上下文的基础构建逻辑
上下文(Context)可以理解为智能体在每个决策点能够看到的全部信息,就像智能体当前的“视野”,决定了模型基于哪些信息进行下一步推理。从一次大语言模型调用来看,上下文通常由五个核心部分组成:
- 系统提示词(System Prompt):定义智能体的身份、规则和工作方式,也可能包含记忆、环境信息等静态配置内容。
- 工具定义(Tool Definitions):描述智能体当前可以使用哪些工具,以及每个工具的参数格式和调用规范。
- 用户消息(User Messages):用户输入的原始内容,也可能包含通过检索增强生成(RAG)动态获取的外部知识。
- 助手消息(Assistant Messages):模型此前产生的文本回复、推理过程和工具调用请求。
- 工具结果(Tool Results):工具执行后返回的实际结果,为智能体的下一步决策提供新的信息支撑。
其中,系统提示词和工具定义通常构成相对静态的上下文前缀;而用户消息、助手消息和工具结果则会随着智能体的执行不断产生和变化。
需要明确的是,这些上下文信息并不是由模型自己管理的,而是分为三个协作模块:模型负责基于当前上下文决定下一步动作;调度框架(Harness)负责组装和管理上下文、校验并执行工具调用;运行环境则负责承载真实任务状态,并产生新的执行结果和观察信息。三者不断循环交互,上下文也会随着智能体的执行持续动态演进。
为了让上下文能够更好地适配大语言模型的KV缓存机制,需要尽量遵守以下三个核心原则:
- 保持前缀稳定:系统提示词、工具定义等静态内容尽量固定,避免频繁修改导致缓存失效,降低模型的推理效率。
- 动态信息向后追加:时间、状态、工具结果等变化内容统一放到上下文尾部,不要回头修改已经存在的前缀内容,确保缓存的一致性。
- 使用标准消息结构:优先使用API原生的system/user/assistant/tool等结构化消息格式,避免自行拼接非标准的Prompt内容,提升上下文的可读性和兼容性。
二、Pi Agent的核心架构基础
由于Pi Agent内部定义了一些专属的基础类型和数据结构,在后续分析上下文管理时会频繁涉及。为了避免反复解释,我们先单独介绍这些核心概念,方便理解后续的细节解读。
2.1 会话运行层:Runtime与Session
在Pi Agent中,AgentSession和AgentSessionRuntime分别对应两个不同的架构层次:
- AgentSession:负责单个具体会话的内部运行逻辑,是会话的直接控制器。
- AgentSessionRuntime:负责管理当前使用的会话实例,以及会话的生命周期和整体运行环境。
AgentSession持有核心的智能体执行器,负责处理一次会话中的主要运行逻辑,包括prompt发起、任务中止、继续执行、任务队列管理、重试机制、上下文压缩,以及消息、系统提示词、工具和扩展事件的统一管理。简单来说,AgentSession管的是“这个会话具体怎么跑”。
而AgentSessionRuntime更像是AgentSession外层的生命周期容器,它持有当前正在运行的AgentSession实例,同时维护会话运行所依赖的一系列服务,比如当前工作目录、设置管理器、模型运行时、资源加载器等。当用户切换会话时,正是由AgentSessionRuntime完成会话的替换和重新绑定,确保新会话的运行环境与当前任务匹配。
AgentSession的核心数据结构包含以下核心模块:底层智能体执行器、会话持久化管理模块、设置管理器、模型运行时、资源加载器、消息队列、上下文压缩状态、自动重试状态、终端命令执行状态、扩展运行状态、工具注册表和系统提示词状态等。
而AgentSessionRuntime则包含当前会话实例、绑定的基础服务组、运行时创建工厂函数、诊断信息和会话替换回调钩子等核心内容。
2.2 Services:运行时与会话的连接面
在Pi Agent中,切换会话并不是简单地替换一个AgentSession对象就能完成的。这是因为一个会话往往与当前的工作目录强绑定,而工作目录又会进一步决定项目配置、智能体定义文件、扩展模块、技能库、提示词模板、系统提示词,以及模型服务商配置等运行资源。如果切换了会话却仍然沿用旧会话的依赖,就可能出现资源错配的问题,比如运行的是仓库B的会话,但实际加载的仍然是仓库A的配置、技能和扩展。
因此,Pi Agent引入了AgentSessionServices,将工作目录、设置管理器、资源加载器、模型运行时等与当前运行环境相关的依赖统一封装起来。当AgentSessionRuntime执行新建、切换、分支或恢复会话等操作时,会先根据目标会话的工作目录重建对应的服务集,再基于这套运行环境创建新的AgentSession实例。
AgentSessionServices解决的核心问题可以概括为:切换会话时,不只切换会话状态,还要同步切换它所依赖的完整运行环境,确保智能体会话与项目配置、资源和运行时始终保持一致,避免不同会话之间出现环境污染或资源错配。
三、Pi Agent的上下文管理实现
3.1 上下文的标准化表示
在Pi Agent中,上下文的数据结构主要包含三个核心部分:系统提示词、消息历史列表和当前可用的工具列表。其中,系统提示词由AgentSession统一构建,来源包括基础提示词、项目规则、技能列表、智能体定义文件、上下文文件和工具说明等,它不会直接进入消息历史数组,而是作为独立字段传给底层模型适配层。
大语言模型API的核心输入是消息历史列表,主要包含三种标准类型:
- 用户消息(UserMessage):表示用户输入的内容,可以是纯文本,也可以包含多媒体内容如图片。
- 助手消息(AssistantMessage):表示模型返回的结果,既可以包含纯文本回复,也可以包含工具调用请求、停止原因和Token使用量等元数据。
- 工具结果消息(ToolResultMessage):调度框架执行工具调用后,会将执行结果包装成工具结果消息,并通过工具调用ID与对应的请求关联,随后这条消息会被追加回消息历史列表,作为模型下一轮推理的输入依据。
因此,Pi Agent中一次典型的智能体循环可以简化为:用户消息 → 助手消息(包含工具调用) → 工具结果消息 → 下一轮助手消息……随着智能体不断执行,新的模型回复和工具结果会持续追加到消息历史中,形成不断变化的动态上下文。
3.2 助手消息的结构与处理
AssistantMessage是Pi Agent内部表示模型单轮回复的数据结构,它不仅记录模型生成了什么内容,还会保存本轮是否触发工具调用、停止原因、模型信息和Token使用量等元数据。其中最关键的是content字段,它并不只有纯文本,而是可以同时包含多种内容类型,比如自然语言说明和工具调用请求的组合。
因此,一条助手消息既可以表示普通的文本回复,也可以表示包含工具调用请求的模型回复。工具调用本身并不是一条独立的消息,而是助手消息content字段的一部分。当助手消息中包含工具调用时,后续的处理流程为:发现工具调用请求 → 调度框架执行对应工具 → 生成工具结果消息 → 通过工具调用ID与原请求关联 → 追加回消息历史列表 → 继续发起下一轮模型调用。也就是说,助手消息是模型决策的载体,而工具结果消息则负责把这次决策产生的真实执行结果重新带回上下文。
3.3 工具结果消息的处理流程
工具结果消息并不是工具的原始返回值,而是智能体循环对工具执行结果进行标准化处理后,重新写回上下文的标准消息格式。完整的处理流程如下:
- 模型在助手消息中产生一个或多个工具调用请求。
- 智能体循环根据配置决定这些工具调用是串行执行还是并行执行。
- 执行前完成工具查找、参数校验,并触发前置工具调用钩子。
- 调用工具的执行方法,执行过程中出现的异常会被转换成标准化的错误结果,而不是直接中断智能体循环。
- 执行结果经过后置工具调用钩子处理后,被封装成标准的工具结果消息,包含角色、工具调用ID、工具名称、内容和错误标记等字段。
最终,工具结果消息会被追加回消息历史列表,进入下一轮模型推理环节。
3.4 技能在上下文中的呈现方式
在Pi Agent中,技能默认不会把完整的SKILL.md文档直接放进上下文,而是先以技能索引的形式出现在系统提示词中。每个技能只暴露三类核心信息:技能名称、适用场景描述和SKILL.md文件的存储路径。
系统提示词中的技能索引只会告诉模型当前有哪些可用技能、分别适合什么任务,以及需要时可以去哪里读取详细内容。因此,技能通常分两个阶段进入上下文:第一阶段是在系统提示词中加载技能索引,仅提供名称、描述和路径信息;第二阶段是当模型匹配到对应技能时,通过调用read工具读取SKILL.md的完整内容,将详细信息加入后续的上下文。
四、长程任务的上下文管理
4.1 循环机制中的上下文增长与沉淀
从智能体的执行流程来看,上下文在runLoop运行期间会持续增长。一次完整的智能体prompt运行流程大致为:接收用户输入 → 调用智能体核心逻辑 → 进入runLoop循环,在循环中完成模型回复、工具执行、结果写回上下文,直到模型停止生成 → 执行后置处理,可能触发重试或上下文压缩 → 清理本轮临时状态但保留完整的消息历史。
在runLoop内部,每完成一轮模型调用和工具执行,产生的助手消息和工具结果消息都会被加入到智能体的消息历史状态中。因此,整个运行期间,上下文实际上一直在持续演进:初始上下文 → 追加助手消息和工具结果 → 形成新的上下文 → 重复该过程直到本轮运行结束。
本轮运行的结束点只是上下文持续更新阶段的结束,并不意味着上下文被清空。智能体的消息历史状态会被完整保留下来,后续的用户输入会基于当前的会话状态创建新的上下文快照,也就是将上一轮运行沉淀的消息历史与新的用户消息结合,作为下一次runLoop的输入基础。也就是说,同一个会话内,新的prompt请求默认不是从零开始,而是基于之前已经积累的完整会话历史。
不过,会话历史并不会无限增长。当上下文超过模型的窗口阈值时,会触发上下文压缩机制,将较早的消息折叠成摘要形式。因此,下一次prompt请求拿到的上下文,要么是完整的历史加上新的用户消息,要么是经过压缩后的历史摘要加上新增的消息和新的用户消息。
从上下文管理的角度来看,整个会话的生命周期可以理解为:多次独立的运行循环,每次循环都会增长上下文,然后将结果沉淀到会话状态中,后续的循环再基于这个状态继续演进,只有上下文压缩操作会真正改变历史的存储形态。
4.2 上下文的预算管理
工具调用的结果是上下文增长的重要来源,尤其是终端命令执行可能产生大量日志、测试结果甚至完整的文件内容,如果全部推入上下文很容易超出模型的窗口限制,而且其中大部分信息模型其实并不需要。因此,如何把工具输出限制在可控的上下文预算内,同时又不真正丢失关键信息,是长程任务管理的核心问题之一。
Pi Agent采用了三级处理策略来解决这个问题:
- 预算内全量保留:当工具输出量较小时,直接将完整内容加入上下文。
- 超出预算仅保留预览:当输出量超过预算阈值时,不再将完整内容加入上下文,只保留有限的尾部预览内容给模型。
- 完整输出落盘按需读取:将完整的工具结果写入临时文件,并把文件路径一起返回给模型,模型如果需要更多细节,可以通过read工具读取文件内容。
在实现上,工具输出是边产生边处理的,而不是等命令结束后再一次性截断。采集过程中会清理ANSI转义序列、乱码和多余的换行符,同时只在内存中维护有限大小的尾部内容,因此即使命令输出非常大,内存和上下文的消耗也不会随输出无限增长。预算限制同时兼顾行数和字节数,比如设置2000行和50KB的限制,任意一个条件先达到就触发截断。这样既能处理大量短行的输出,也能防止单行超大的JSON等内容绕过限制。
最终模型拿到的工具结果不是全部输出,而是截断后的预览内容加上完整输出文件的存储位置,本质上是将工具结果从“全部推入上下文”的模式改成了“有限预览+按需读取”,从而把工具输出对上下文的影响控制在一个可预测的范围内。
4.3 故障转移机制的上下文管理
长程任务的执行过程中难免会遇到各种故障,Pi Agent区分了两个层次的故障转移机制:
- 智能体上下文层:智能体循环内部维护的工作上下文,包括消息历史、系统提示词、工具列表等完整状态。
- 模型输入层:每次调用模型前,由智能体上下文转换和组装得到的实际模型输入数据。
发生故障转移时,也可以分为两个不同的层级处理:
服务商层故障转移:不会修改智能体上下文,而是直接使用同一份模型输入数据重新发起请求。对智能体来说,这一轮模型调用还没有结束,因此不会产生新的上下文状态,只是重新尝试调用模型服务商。
会话层故障转移:则发生在一次完整的智能体循环已经失败之后。此时失败的助手消息已经被加入到智能体上下文中,会话会先回滚这条失败的消息,再从最近一个有效的智能体上下文状态重新启动智能体循环,并重新构建模型输入数据。
两种故障转移方式最终发送给模型的输入数据可能完全相同,但系统经历的状态路径不同:服务商层故障转移是同一次模型调用的底层重发,而会话层故障转移是在智能体状态已经失败后,先回滚上下文再重新发起模型调用。从上下文管理的角度来看,这说明上下文不只是持续追加的,也需要支持回滚、恢复和重新构建的能力。
五、Pi Agent的上下文压缩机制
5.1 上下文压缩的触发时机
Pi Agent的上下文压缩操作并不是在runLoop循环内部执行的,而是由会话层统一管理的。主要有三个触发时机:
- 一次智能体运行结束后:会话会检查当前上下文是否已经接近模型的窗口上限,如果需要压缩,就先折叠历史消息,再基于压缩后的上下文继续处理后续请求。
- 新的prompt请求发送前:如果上一轮运行留下的上下文已经过大,会先执行压缩操作,再将新的用户消息追加到压缩后的历史上下文上。
- 用户手动触发:用户也可以主动执行/compact命令,直接压缩当前的上下文。
真正触发压缩操作主要有两种场景:一是上下文持续增长,已经接近模型窗口的上限,提前执行压缩;二是上下文已经溢出模型窗口,需要先压缩历史再重新尝试运行。从整体上下文管理来看,runLoop负责不断产生和积累上下文,而会话层则负责在运行循环之间或新请求到来前检查上下文状态,并在必要时执行压缩操作。
5.2 上下文压缩的实现细节
上下文压缩的整体逻辑可以理解为:基于当前的原始上下文,根据Token预算寻找合适的压缩切点,将较早的历史消息转换为摘要形式,保留近期的消息原样,最终形成新的压缩后的上下文。具体来说,包含以下核心细节:
确定需要压缩的历史范围
压缩首先要解决的是切点问题:哪些消息应该被压缩成摘要,哪些消息应该继续保留原样。Pi Agent会从最近的消息向前计算Token数量,用固定的近期消息预算保留较新的上下文内容,其余更早的历史则进入压缩范围。切点本质上是由Token预算驱动的,但有一个明确的限制:不能直接切在工具结果消息上,因为工具结果本身依赖前面的工具调用请求,如果只留下结果而丢掉调用它的助手消息,会导致上下文出现明显的逻辑断裂。因此,压缩后的结构通常是:较早的历史被压缩,近期的消息保留原样,两者之间有明确的切割边界。
补充分割回合的摘要
Token预算是硬约束,因此切点不一定正好落在完整的交互边界上。比如,模型回复要读取某个文件,发起了read工具调用,这部分内容刚好被切点截断,而后续的工具结果和进一步的分析回复被保留。如果直接只保留后半段,模型可能会看到一个缺少来路的执行结果。Pi Agent的处理方式不是强行移动切点,而是在确认发生分割回合后,额外对被切掉的那部分生成一份独立的摘要,将历史摘要、分割回合的上下文和近期消息拼接在一起。这里的分割回合摘要是一次独立的大语言模型调用,最后再与主摘要合并,确保不会切断后续推理所依赖的因果关系。
摘要的增量合并机制
第一次执行压缩时,模型会直接总结需要压缩的历史内容。但后续再次触发压缩时,不会把最早的所有原始消息重新喂给模型,因为这些内容已经被上一轮的摘要替代了。此时的输入会分为两部分:上一次已经生成的历史摘要,以及上一次压缩之后新增的、这次需要继续折叠的消息内容。模型负责将这两部分重新合并成一份新的完整摘要,因此多次压缩本质上形成的是一个持续演进的历史摘要,这也是Pi Agent能够支持长时间智能体执行的关键,旧历史会逐渐从原始消息形态转变成摘要形态,而不是无限累积。
摘要本身的预算限制
上下文压缩并不是生成摘要就结束了,如果摘要本身无限增长,经过多轮压缩后最终还是会重新撑满上下文。因此摘要生成也有独立的Token上限,通常会从预留的上下文预算中划出一部分作为摘要专用预算,这保证了压缩后的上下文整体仍然处于可控范围内。
结构化的任务记忆摘要
上下文压缩的目标并不是简单地帮用户总结聊天记录,而是为了让智能体能够继续完成任务,保留继续执行所必需的关键信息。因此摘要会重点保留:当前任务目标、用户的约束和偏好、已完成/进行中/受阻的工作、关键决策、已发现的问题和错误、下一步应该继续执行的任务、关键的文件、函数、路径等工程信息。本质上,它生成的不是普通的聊天摘要,而是一份任务延续状态记录。
程序化保留确定性事实
Pi Agent还做了一层重要的优化:一些能够通过程序直接确定的事实,不会交给摘要模型来“记忆”。比如文件操作相关的历史,系统会直接扫描历史中的工具调用,提取读取过的文件、新写入的文件和修改过的文件列表,整理成结构化的格式追加到摘要中。这意味着语义信息交给大语言模型进行蒸馏总结,而确定性的事实则由程序直接保留,避免摘要模型遗漏关键信息导致智能体重复工作或失去任务锚点。
摘要生成不启动完整智能体循环
上下文压缩的摘要生成并不是再启动一个完整的智能体循环,而只是一次独立的大语言模型调用,不会执行任何工具调用,也不会进入工具循环。这样可以避免“为了压缩上下文,又产生更多上下文”的递归问题,也让压缩行为更加可控。
压缩后重建完整上下文状态
上下文压缩最终不是给现有的消息列表做一个标记,而是重新构造智能体的消息状态。压缩之后,消息历史会变成压缩摘要加上近期保留的消息两部分,其中压缩摘要是一种专门的消息类型,而不是普通的字符串。在真正构造给模型的Prompt时,它会被转换成带有明确标记的摘要内容,模型看到的是:历史摘要加上最近几轮的原始消息,最近发生的事情保留高保真原文,较远的历史则降级为摘要表示。
压缩记录的持久化
生成摘要后,Pi Agent还会保存一条独立的压缩记录,其中包含本次生成的摘要、压缩边界、压缩前的Token信息和时间等元数据。这个记录有两个核心作用:一是告诉下一次压缩操作上一次历史压缩到了哪里,以及上一次的摘要内容是什么;二是让整个压缩过程可追踪,而不是悄悄删除历史内容。多轮压缩实际上是有连续边界的,每一次压缩都会留下可追溯的记录。
综合来看,上下文压缩本质上不是简单的“截断历史”,而是一种上下文表示的转换:将远端历史从高成本的原始消息形式,转换成低成本的结构化摘要形式,同时尽可能保留继续完成任务所需要的关键状态。如果说runLoop负责的是上下文的增长,那么上下文压缩负责的就是上下文的降维与重建,确保智能体能够在有限的模型窗口内完成长程任务的执行。
通过对Pi Agent的上下文管理机制的拆解,我们可以看到,智能体的上下文管理是一个兼顾动态性、可控性和扩展性的复杂系统,从基础的构建原则、架构设计,到长程任务的运行管理和压缩优化,每一个环节都需要精心设计,才能让智能体在有限的模型窗口内高效完成复杂的长程任务。

