首页
看点啥
插画图片
首页 热点时事 Pi源代码学习-2:Session 的两层含义及 createAgentSession() 基本使用

Pi源代码学习-2:Session 的两层含义及 createAgentSession() 基本使用

2026-06-25 0

关注我~第一时间学习如何更好地搭建AI Agent。

重要的不是我们是否会被AI替代,

而是我们要比被替代的人更懂AI。

前期导览:

LangGraph学习笔记年度总结:以ReAct框架为视角的知识体系全景图

Pi源代码学习-1:Model->Agent->Session 的三层架构概览

大家好,上一期我们梳理了Pi的三层架构,本期开始进入 SDK 实战。

动手前的准备

动手之前,先把环境搭好。

首先,确认node版本,要求 >= 22.19.0

然后, cd 切到你打算放项目的文件夹,用 git clone拉取源代码。指令会新建一个pi-mono文件夹,继续 cd进去。

在pi-mono目录下安装依赖并构建项目。

配置模型API key,这里我用的是minimax国内版,使用其他模型的请参考官方文档:

完成以上步骤之后,一般情况下你就可以使用 Pi 了。若想先试 TUI,在仓库根目录( pi-mono )执行 npx pi;Linux/macOS 也可用 ./pi-test.sh。若已全局安装 @earendil-works/pi-coding-agent,则可直接输入 pi。用法类似 Claude Code,在终端里与 Agent 对话即可。

不过本期从 coding-agent 包的 SDK 示例入手,你可以执行以下指令,运行第一个示例 01-minimal.ts:

Why createAgentSession ?

上一期我们从下往上梳理了 Pi 的三层分工: pi-ai 管模型调用, pi-agent-core 管 agent loop, pi-coding-agent 把它们组装成能直接用的 Coding Agent。三层里对外的总出口,就是 createAgentSession(),官方 SDK 文档也把它称为创建 AgentSession 的主工厂函数。

换言之,只用 createAgentSession() 一行代码、不传参数,我们就能把模型、工具、会话持久化、skills 发现等 Harness 能力全部配齐。想改哪一块,往参数里加就行; examples/sdk/0213 的示例,都是在这个入口上换不同配置。所以,先从这里入手,能最快看清 Pi 对外提供了什么;

之后继续读源码时,从 session.prompt() 开始往底层探索的话,会依次经过内部的 Agent.prompt()、agent loop,最后在 pi-ai 里调用 streamSimple() 向模型发请求。从这个意义上, createAgentSession()也是我们最合适的入口。

什么是 Session?

进入示例代码之前,我们需要理解一下 Session 这个概念。在 Pi 里,「Session」其实至少涉及两个层面:

第一个层面,是「一次对话的存档」——Pi 把你在某个项目里跟 Agent 来回聊的整段记录,当作一个 Session 来管理。

通俗来说,这个层面的 Session 就是一份有编号的对话档案: 它有唯一的 sessionId,记录了当时的工作目录( cwd )、创建时间,以及从第一条用户输入到最新一轮回复的全部往来——用户说了什么、模型回了什么、调了哪些工具、中途换过什么模型,都记在里面。你在 TUI 里 /resume 挑一条旧对话继续聊,或在 /fork 里从某条消息分叉出新路线,操作的其实都是某一份这样的档案。

这个层面的session如何保存到我们的设备中,由 SessionManagerpackages/coding-agent/src/core/session-manager.ts )负责。

第二个层面,是运行时对象 AgentSession——也就是 createAgentSession() 返回给我们的那个 session对象。

session对象 的定义在 packages/coding-agent/src/core/agent-session.ts,它是Pi的 interactive、print、rpc 等所有运行模式共享的核心抽象。我们调用的 session.prompt()、 session.subscribe()、 session.dispose(),操作的都是这个对象。

它内部持有一个已经配好工具、模型、扩展的 Agent(上一期讲过: session.prompt() 最终会转到 agent.prompt() 去跑 loop );同时还带有 SessionManager、 SettingsManager 等组件,在 loop 之外处理配置读取、事件推送、自动持久化、压缩等 Harness 事务。

换句话说, AgentSession 解决的是如何跟 Agent 打交道 ,而第一个层面那份 JSONL 档案解决的是事后怎么找回来、怎么接着聊 。当然,两个层面间具有非常紧密的关系, createAgentSession() 默认会把它们绑在一起: 运行中的 AgentSession 会通过调用 SessionManager,把对话实时同步进第一个层面的 JSONL 档案。

下面,我们先通过第一个示例代码来理解Pi SDK,尤其是 createAgentSession() 的基本功能。


最简示例:01-minimal.ts

官方最简示例在 packages/coding-agent/examples/sdk/01-minimal.ts,全文如下:

这段代码就是 Pi SDK 的典型用法。官方 examples/sdk/ 目录下的示例基本都遵循同一套套路:**先 createAgentSession() 创建 session,再使用 session 与 Agent 交互,最后 session.dispose() 释放资源。

这些示例都是一次性 跑完就退出———发一条 prompt、拿到结果、释放资源,进程随即结束。如果想要做成可持续对话的模式,可以尝试在自己的脚本里保持同一个 session 不销毁,循环调用 session.prompt(),每轮输入都会追加到同一段对话历史里即可。

下面我们来具体感受下示例代码。

  1. 创建:AgentSession

在这个示例中,我们没有向 createAgentSession() 传递任何参数。这意味着 Pi 组装出来的 AgentSession在模型、工具、工作目录、存档方式等方面,将全部走默认配置。具体来说:

也就是说:你写一行 createAgentSession(),Pi 就按上表帮你配好一套能用的 Coding Agent。当然,我们也可以通过传递参数的方式对模型、工具等进行修改,这就是后面几个示例代码展示的内容了。

2. 监听:session.subscribe()

示例代码在 session.prompt() 之前写了一个 session.subscribe()。这个 subscribe 是 Pi 对外提供的一条底层事件通道。原理上讲,就是把一个回调函数注册进 AgentSession 的监听器列表,运行过程中 Pi 每产生一个 AgentSessionEvent,就会调用这个回调,把事件对象传进去。

当 Agent 开始工作,就会连续抛出过程事件,比如模型流式输出、工具开始执行、工具返回、本轮结束等。对 TUI 而言,这些事件就是界面更新的数据源:UI不会在问题跑完后再去翻 messages,而是订阅同一条事件流,事件描述「此刻发生了什么」,界面就据此增量刷新。你在终端里看到的对话、工具进度,本质上是 Agent 运行过程通过这条通道实时投射到屏幕上的。

我们来具体看下代码:

上述代码的效果,其实就是在 Agent 运行期间,把模型回复的流式文字实时打到终端上。

Pi 运行过程中会不断调用这个回调;每次调用时,会对接收的事件进行2个条件判断:

这是一条「消息内容正在更新」的事件,说明模型还没把整段话说完,Pi 就在往外推中间状态。

在这次更新里,变动的是 正文文字的流式片段event.assistantMessageEvent.delta 就是本次新冒出来的一小段字符串。

只要上述两个条件同时满足,Pi就会把事件里带着的一小段新文字(即 delta ),process.stdout.write 续写出来。由于 process.stdout.write 不带换行,所以字会一行行接在同一行后面,看起来就是一个个蹦出来的。

值得一提的是, subscribe 能接的不止流式文字。按官方 examples/sdk/README.md 的写法,你还可以监听 tool_execution_start(工具开始执行 )、 tool_execution_end(工具跑完 )、 agent_end(本轮结束 )等——TUI 的 handleEvent() 也是按这个思路分事件处理的。但在 01-minimal.ts 里,这一步不是必须的:不写 subscribe,后面照样可以向 Agent 发指令并正常跑完;只是脚本里看不到过程输出,你得等 Agent 结束后,再从 session.state.messages 里一次性翻看完整记录。

3. 使用:session.prompt()

前面两步准备好之后,真正跟 Agent 对话就靠这一行了:

session.prompt() 是 SDK 里最核心的用法——你把用户输入传进去,Pi 负责剩下的事。

上一期讲过,第二层agent包里的 agent.prompt() 会先把用户输入包装成一条 user 消息,追加到 state.messages,再进入 agent loop 调模型;若模型返回 tool call,就执行工具、把结果写回,并自动再调一轮,直到本轮不再产生工具调用才结束。那是 Agent 的核心工作: 维护内存里的对话状态,跑完 ReAct 式的内层循环。

session.prompt() 并不是另起炉灶。 AgentSession 内部持有一个已经配好的 Agent,也就是说,你调用 session.prompt(),等准备做完,最终还是转到 agent.prompt() 去跑 loop。

差别在于,Session 在 loop 之外又包了一层 Harness。对 Pi 来说, session.prompt() 在把消息交给 Agent 之前,已经替用户做过一轮「发送前处理」——扩展可以拦截或改写输入,skill / 模板命令会先展开,系统提示词会按扩展要求刷新,模型和鉴权也会先校验一遍。

消息进入 loop 之后,内层行为和 agent.prompt() 并无二致;loop 推出来的事件, AgentSession 会接住并往外转:前面注册的 subscribe 能实时收到, SessionManager 也会把消息写入本地档案。loop 跑完也不是到此为止——若需要自动重试、上下文压缩等,Harness 还会继续善后,必要时再驱动 agent.continue() 补跑。

换句话说,** agent.prompt() 解决「单一轮怎么跑」; session.prompt() 解决「发之前做好准备、跑的过程中有人记账和广播、跑完之后有人收尾」**。示例里虽然只写了一行字符串,看起来很简单,其实外面那层 Harness 的脏活累活都已经代劳了。

示例问的是「当前目录有哪些文件」。这类问题 Agent 没法凭空回答,通常会调用默认工具里的 bash(比如跑 ls )去查,再把结果组织成自然语言回复给你。前面如果注册了 subscribe,流式文字和工具进度会边跑边打印;这里加 await,则是等整轮 loop 彻底跑完 再往下走——期间可能经历多轮模型调用和工具往返,但对你而言就是「发一条指令,等它干完」。

4. 收尾 session.dispose()

prompt() 返回之后,示例没有立刻退出,而是先把对话记录翻出来看一遍,再释放 session:

session.state.messages 是当前内存里的完整对话列表,本质上就是内部 Agentstate.messages。跑完一轮 prompt() 之后,这里面通常已经攒下了好几条消息:你的用户输入、模型的助手回复、中间的 tool call 和 tool result 等。示例用 forEach 把它们逐条 console.log 出来,方便你对照「Agent 实际走了哪些步骤」——如果前面没开 subscribe,这里就是一次性查看全程记录的主要方式。

session.dispose() 则是告诉 Pi「这个 session 用完了」。它会断开与内部 Agent 的事件连接、清理监听器,释放本轮占用的资源。示例把它放在 finally 里,是为了无论 prompt() 成功还是抛错,收尾都能执行。值得一提的是,对话记录这件事在运行过程中已经由 SessionManager 做完了, dispose() 并不负责「保存聊天记录」,而是负责把运行时对象干净地关掉。

总结

本期我们从上一期的三层架构落到了 SDK 实战,用官方最简示例 01-minimal.ts 走通了 createAgentSession() 的基本用法:

  1. createAgentSession():

一行代码、不传参数,Pi 就按默认配置帮你配好模型、工具、工作目录和会话持久化——这是第三层 Harness 对外的总入口。 2. session.subscribe()

(可选 ):注册回调,接收 Agent 运行过程中的事件流;示例只处理了 text_delta, 让你实时看到模型回复,TUI 的实时展示也是同一套机制。

  1. session.prompt():

表面写法与第二层的 agent.prompt() 相近,但多了一层 Harness——发前处理、跑中广播与存档、跑后善后,内层 loop 仍由 Agent 执行。

  1. session.dispose():

负责干净释放运行时资源。

跑通这个示例之后,我们就掌握了 Pi SDK 的标准套路:创建 session → 按需订阅 → 发指令 → 收尾释放examples/sdk/ 里从 0213 的后续示例,都是在这个骨架上替换模型、工具、存档方式等配置——这些定制化我们后面再研究。

好了,以上就是本期的主要内容,希望对大家有帮助,喜欢的朋友别忘了点赞、收藏、转发~祝大家玩得开心。

—— END——

往期精华:

1.LangGraph教程

LangGraph学习笔记年度总结:以ReAct框架为视角的知识体系全景图

2.OpenDeepResearch源码学习

LangGraph实战研究 — Open Deep Research源代码学习:多Agent系统的动态模型配置方法

LangGraph实战研究 — Open Deep Research源代码学习:多Agent系统架构的设计思路与实现方法

LangGraph实战研究 — Open Deep Research源代码学习:多Agent系统的State设计思路

3.COZE教程

零基础搞定!萌新的 Coze 开源版保姆级本地部署指南

AI工作流编排手把手指南之一:Coze智能体的创建与基本设置

AI工作流编排手把手指南之二:Coze智能体的插件添加与调用

AI工作流编排手把手指南之三:Coze智能体的工作流

Agent | 工作流编排指南4:萌新友好的Coze选择器节点原理及配置教程

Agent | 工作流编排指南5:长文扩写自由 — Coze循环节点用法详解

Coze工作流编排指南6:聊天陪伴类智能体基本工作流详解-快来和玛奇玛小姐姐谈心吧~

PPT自由!Coze工作流 X iSlide插件-小白也能看懂的节点参数配置原理详解

4.MCP探索

Excel-MCP应用 | 自动提取图片数据到Excel的极简工作流手把手教程

markitdown-mcp联动Obsidian-mcp | 一个极简知识管理工作流

【15合1神器】不会代码也能做高级图表!这个MCP工具让我工作效率翻了不止三倍!

【效率翻倍】Obsidian自动待办清单实现:MCP联动Prompt保姆级教程(萌新3分钟上手 )

萌新靠MCP实现RPA、爬虫自由?playwright-mcp实操案例分享!

高德、彩云MCP全体验:让Cherry Studio化身私人小助理的喂饭版指南!

5.Prompt设计

干货分享 | Prompt设计心法 - 如何3步做到清晰表达需求?

打工人看了流泪的Prompt设计原理,如何用老板思维让AI一次听懂需求?

不会Prompt还敢说自己会用DeepSeek?别怕!10分钟让你成为提示大神!

喜欢(0)

上一篇

一个朋友在 Hermes 装了一个神秘技能:结果他的电脑成了肉鸡:为此我写了一个杀毒技能:希望对你有用:开源!

一个朋友在 Hermes 装了一个神秘技能:结果他的电脑成了肉鸡:为此我写了一个杀毒技能:希望对你有用:开源!

下一篇

期刊配图:SHAP框架下的二分类RF模型特征贡献解析与交互网络分析

期刊配图:SHAP框架下的二分类RF模型特征贡献解析与交互网络分析
猜你喜欢