☰
PI——一个适合入门学习Agent
2026/10/4 2:54:25 网站建设 项目流程

引言

假设你是一个刚开始学习 Agent 的开发者。你已经跟着教程写过几次 LLM 调用:给模型一段 prompt、定义一个天气或搜索工具、把工具结果再发回模型。示例能够跑通,但当你想继续追问时,问题很快出现:

  • 多轮任务怎样结束?
  • 工具报错后模型如何调整?
  • 上下文越来越长怎么办?
  • 任务中断后怎样恢复?

这时转去阅读成熟的 Coding Agent,体验通常又是另一种挫败。
最典型的代表便是Claude Code和Codex,它们作为生产级产品,还要考虑处理界面、权限、跨端会话、复杂错误处理、平台适配和生态集成等问题。对初学者来说,Agent 的核心循环往往被这些产品层代码包围,难以从中切入去学习。

简单教程不够深入,成熟项目又无从下手,学习路径恰好断在了这里。

PI 恰好位于两者之间:它不是只演示一次工具调用的玩具项目,也没有把 Agent 的关键控制流淹没在大量产品实现中,覆盖了理解Coding Agent核心运行机制所需的关键部分:上下文处理与压缩,工具调用,Skill注入,模型适配,会话持久化和Agent Loop等等。PI的价值不在于功能更少,而在于把Agent的核心控制流程都保留在了清晰的代码中

同时,Pi具有极大的拓展性,你可以在学习更高级的Agent知识的同时在Pi上实现相应的功能:

  • 更成熟的上下文压缩策略
  • 多Agent调用与子Agent派发
  • 接入MCP服务
    PI的设计也在鼓励使用者在其上持续扩展,设计出结合具体场景的生产级Agent

1. PI 总体介绍

1.1 PI 是什么?

PI 是一个用 TypeScript 编写、运行在 Node.js 上的开源 Coding Agent。它提供终端 CLI,也把模型调用、Agent Loop、会话和终端交互拆成独立模块。

模块功能
packages/ai统一调用不同模型与 Provider
packages/agent实现 Agent Loop、消息状态和工具调用
packages/coding-agent提供 CLI、内置工具、会话与上下文管理
packages/tui负责终端交互界面

1.2 核心循环与拓展接口

PI 的核心循环只负责推进任务:向模型发送当前上下文,执行模型返回的工具调用,再将工具结果写回上下文。

packages/agent/src/agent-loop.ts 直接实现了这条流程:

  • 循环从 assistant message 中取出toolCall,执行工具后追加toolResult
  • ReAc执行流程下,如果还有工具调用,就继续请求模型。

2. 完整Agent Loop的流程

Agent Loop 可以完成短任务。要让 Agent 在真实项目中连续工作,还要解决模型上下文、工具执行和任务恢复的问题。PI 将这些问题拆为四条链路:

机制要解决的问题PI 中的主要实现路径
会话存储对话如何持久化,以及回退后如何形成分支?pi/packages/coding-agent/src/core/session-manager.ts、pi/packages/coding-agent/src/core/agent-session.ts
上下文本次请求需要给模型哪些规则、历史和外部信息?pi/packages/coding-agent/src/core/resource-loader.ts、pi/packages/coding-agent/src/core/system-prompt.ts、pi/packages/coding-agent/src/core/agent-session.ts
压缩策略历史超过模型窗口后,怎样保留可继续工作的信息?pi/packages/coding-agent/src/core/compaction/compaction.ts
工具调用如何根据当前任务选择、执行并返回工具结果?pi/packages/agent/src/agent-loop.ts、pi/packages/coding-agent/src/core/tools/

下面先看 PI 提供的工具,再按这四条链路分析 Agent Loop。

2.1 Tools

PI 默认启用四个工具,足以完成基本的代码阅读、修改和验证:

工具作用
read读取文件内容
bash执行命令,例如搜索代码或运行测试
edit修改已有文件中的指定内容
write写入文件

仓库还内置

  • grep:搜索文件内容
  • find:查找文件
  • ls:列出目录
  • powershell:执行 PowerShell 命令。
  • 这些工具有现成实现,但不属于默认启用的四个工具;需要时可以在工具配置中选用。

源码入口:全部内置工具、默认工具组合。

2.2 上下文

每次API调用都是无状态的,单次API调用的不会自动知道当前Agent对话的上下文,所以Agent Harness需要在每次请求前组装上下文。而且相应的上下文组装策略也需要斟酌,不能一股脑地将不同来源的上下文杂糅到一起,这样会稀释上下文,需要将不同来源的信息放在不同层次。

信息来源PI 的处理方式
项目规则加载AGENTS.md、CLAUDE.md项目约束提示词
Skill先提供名称和描述;任务匹配时再读取 Skill 正文
会话历史只投影当前会话分支,而不是整棵会话树
工具结果作为toolResult留在消息历史中,供下一次模型请求使用

请求发送前,PI 会先按需调用transformContext。它接收当前的应用层消息列表,可用于筛选历史消息、调整顺序或注入额外信息;未配置时,消息保持原样。随后,convertToLlm 将其中的自定义消息转换为通用的 LLM 消息,并过滤不应进入模型上下文的消息。具体 Provider 的请求格式由后续模型调用层处理。

源码入口:资源加载、系统提示词、消息转换。

2.3 会话存储

PI 默认用一个 JSONL 文件保存一次会话。首行是会话信息,后续每行是一条 entry;节点共有的id、parentId和timestamp字段定义在 SessionEntryBase。

model_change记录模型切换:

{ "type": "model_change", "id": "b2c3d4e5", "parentId": "a1b2c3d4", "timestamp": "2024-12-03T14:05:00.000Z", "provider": "anthropic", "modelId": "claude-sonnet-4-5" }

compaction记录压缩摘要及从哪个节点开始保留原始内容:

{ "type": "compaction", "id": "c3d4e5f6", "parentId": "b2c3d4e5", "timestamp": "2024-12-03T14:10:00.000Z", "summary": "已完成项目结构分析,下一步检查工具调用。", "firstKeptEntryId": "a1b2c3d4", "tokensBefore": 50000 }

branch_summary把离开旧分支时生成的摘要接到新分支上:

{ "type": "branch_summary", "id": "d4e5f6a7", "parentId": "a1b2c3d4", "timestamp": "2024-12-03T14:15:00.000Z", "fromId": "c3d4e5f6", "summary": "原分支尝试了方案 A,但尚未完成验证。" }

此外还有记录推理级别的 thinking_level_change、记录用量的 usage,以及标记节点的 label 等节点分类

PI 按节点增量记录会话,而不是等任务结束后一次性保存。AgentSession 的 message_end 事件处理会把完成的用户、助手和工具结果消息交给 SessionManager.appendMessage。

新建会话时,PI 先把会话信息和用户消息暂存在内存里,此时还没有创建 JSONL 文件。模型的第一条回复生成完毕后,PI 才创建文件,把内存中已有的记录一次写入。此后每产生一条新记录,就直接追加到文件末尾。

PS:PI 进程在第一条助手消息写入会话前就退出,这次会话可能无法恢复。

回退

回退时,PI 不删除旧节点,而是通过 navigateTree 移动当前节点。

  • 如果选中一条旧的用户消息,PI 会把当前节点移到它的父节点,并把原消息放回输入框供修改
  • 再次发送后,新消息以该父节点为parentId,形成另一条分支。

回退后,getBranch 只沿当前节点的parentId找到这条分支。模型收到的消息再由 buildSessionProjection 生成。会话树中选中一条旧消息,只是把 PI 当前的位置移过去。这一步不会修改会话文件。你从这里发送新消息后,PI 才会把新消息接在所选消息后面,形成一条新分支,并写入同一个 JSONL 文件。原来的分支仍然保留。如果选择“总结旧分支”,PI 也会写入一条摘要消息。

PS:切换会话分支只影响聊天记录,不会把此前工具修改过的项目文件恢复原状。

2.4 压缩

长任务会不断积累用户消息、模型回复和工具结果。PI 不直接删除旧消息,而是在当前分支的上下文接近模型窗口上限时,将较早的内容概括成摘要。启用自动压缩时,判断条件由 shouldCompact 实现,有两个关键的变量:

  • reserveTokens:当上下文窗口的剩余token达到reserveTokens就会触发压缩
  • keepRecentTokens:是压缩时尽量保留的近期消息量的Token数。

前者决定何时压缩,后者决定保留的消息token

  1. 确定边界。prepareCompaction 根据当前分支的路径,从最新消息向前累计 token,找到尽量保留约keepRecentTokens的位置。firstKeptEntryId指向第一个原样保留的节点。切点不能直接落在toolResult上,否则可能只保留工具结果,却丢掉对应的工具调用;如果单次用户任务本身太长,也可以在助手消息处切开,并单独概括此前的部分。切点选择

  2. 生成摘要。compact 将边界之前的消息交给模型总结,而不是机械截断文本。摘要重点保留任务目标、约束、进度、关键决策和下一步;如果之前压缩过,还会结合旧摘要更新。摘要请求

  3. 保存并重建上下文。PI 追加一个包含summary、firstKeptEntryId等字段的 compaction 节点。下一次请求时,buildContextEntries 选出该压缩节点和从firstKeptEntryId起保留的节点;消息投影 再将压缩节点展开为系统提示词状态和摘要。旧节点仍在 JSONL 会话文件中;压缩改变的是模型看到的历史,不是原始记录。

触发方式:

  • 自动触发:会在工具结果写入后、下一次助手回复前等时机执行
  • /compact手动触发

若模型请求发生上下文溢出时,PI 压缩完上下文后,会重试发送对话

3. 从 PI 开始扩展自己的 Agent

学习 PI 的下一步,是在不断的Agent学习中,以PI为基础,扩展出更多Agent能力

扩展方向可以做什么
压缩策略根据任务定制摘要,保留关键约束、进度和待办事项
MCPMCP Server 提供的工具注册成 PI 工具
多 Agent将派发子Agent注册为tool,要处理独立子任务时调用,结束后向主 Agent 返回结果
Hook在特定的时间节点注入信息、拦截工具调用或处理结果

扩展时可以参考PI官方源码自带的示例,参考其实现,慢慢推进。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询