ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent 的会话实现原理:Pi 的 Session 工作流程全解析(一)——TaoToken 统一 Key 通道下的 JSONL 会话追踪

AI Agent 的会话实现原理:Pi 的 Session 工作流程全解析(一)——TaoToken 统一 Key 通道下的 JSONL 会话追踪 1. 从一次“AI 失忆”说起Pi Agent 的 Session 到底解决什么问题如果你用过一段时间的 AI Agent大概率遇到过这种场景上一轮它刚帮你把main.py里的函数名改好下一轮你问“刚才那个函数现在长啥样”它却像失忆一样重新问你文件路径。这不是模型笨而是大模型本身没有记忆——它每次收到的都是一段全新的文本然后预测下一个 token。让你产生“它在跟我连续对话”错觉的是 Agent 外层的 Session 机制。Pi 是一个开源 AI Agent 项目设计上非常克制尤其在主流 Coding Agent 动辄往上下文里塞上万 token 提示词的当下Pi 的清爽显得别具一格。它的 Session 子系统本质上就是一本“你和 AI 的对话笔记本”你说一句、它回一句就往笔记本上追加两行第二天打开应用想接着聊就把整本笔记本从硬盘读回来找到最后一页继续写聊到一半想换方向就翻回之前某一页插一根书签从那里开新分支。这篇是 Pi Session 工作流程解析的第一部分聚焦 JSONL 落盘与回放机制。我会在 TaoToken 统一 Key/API 通道下带你走完一次多轮会话的完整生命周期从 AgentHarness 配置、JSONL 会话文件长什么样到用 curl 验证会话读写与恢复的三步动作。适合正在做 Agent 二次开发、想搞清楚“会话持久化到底怎么落地”的工程师也适合刚接触 Agent 架构、想找一个干净参考实现来入门的朋友。核心检索词先摆在这AI Agent 的 Session 机制、Pi 的 JSONL 会话追踪、AgentHarness 配置。搞懂这三样你就能看清 Session 从创建到持久化的每一步而不是停留在“连上后就能对话”的模糊认知里。2. TaoToken 统一 Key 通道给 Pi AgentHarness 接上模型能力在动手改配置之前先把模型通道这件事说清楚。Pi 的 AgentHarness 本身不绑定任何模型供应商它通过一层 provider 抽象去调用 LLM。你要做的是给它一个稳定的 Base URL、一个 Key、一个 Model ID这三件套凑齐Agent 才能跑起来。我这边统一走 TaoToken 的 API 通道好处是 Key 和 Base URL 固定切换模型只改 Model ID不用每次动一堆环境变量。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 注意这个地址后面不加任何 UTM 参数配置里写干净就行。先拿 Key。进控制台创建 API Key路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建完复制那串sk-开头的字符串。如果你还没想好具体用哪个模型可以先去模型对话页面试一下 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 确认模型能正常返回再写进配置。长期跑编码类 Agent 的话Coding Plan 会更划算入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。这里有个容易踩的坑很多人把 Base URL 写成https://taotoken.net/api/v1或者带一堆查询参数结果 AgentHarness 拼出来的请求路径变成/api/v1/v1/chat/completions直接 404。记住Base URL 就是https://taotoken.net/api后面的路径由 SDK 自己拼。Key 的权限也要注意创建时如果只勾了“只读”写会话相关的请求会被拒。环境变量建议这样设避免把 Key 硬编码进代码export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODEL_IDclaude-sonnetModel ID 具体填什么以你在模型对话页面看到的为准。Pi 的 provider 配置里provider字段填anthropic还是openai取决于你选的模型走哪套协议这个在下一节的配置片段里会体现。Key 和地址都齐了接下来才是 Session 真正开始工作的地方。3. 可复制配置AgentHarness 与 JSONL 会话落盘Pi 的 Session 横跨运行时和持久化两层。运行时视角它是 AgentHarness 用来读上下文、记状态、追分支的 API持久化视角它就是一个 JSONL 文件每行一个 JSON 对象断电可恢复、分支可追溯。这一节给你一份可以直接抄的配置把这两层串起来。先看 AgentHarness 的配置片段。Pi 的配置通常放在项目根目录的agent.config.json或者通过AgentHarness构造函数传入。下面这份是 JSON 格式路径和字段名按 Pi 的实际结构来{ provider: { type: anthropic, baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, modelId: claude-sonnet }, session: { storage: jsonl, dir: ./.pi/sessions, fileNameTemplate: {sessionId}.jsonl, version: 3, flushOnSettled: true }, agent: { systemPromptFile: ./prompts/system.md, tools: [read, write, bash], steeringMode: one-at-a-time, followUpMode: one-at-a-time } }几个字段值得单独说。session.storage设成jsonlPi 就会把每次会话追加写入./.pi/sessions/{sessionId}.jsonl。flushOnSettled为 true 时每个 turn 结束会强制 flush 到磁盘形成 save point这样即使进程被杀已完成的轮次也不会丢。version: 3是当前 JSONL 的 schema 版本恢复时会校验版本不匹配会拒绝加载避免旧格式解析出错。如果你更习惯 TOML等价写法是这样[provider] type anthropic base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model_id claude-sonnet [session] storage jsonl dir ./.pi/sessions file_name_template {sessionId}.jsonl version 3 flush_on_settled true [agent] system_prompt_file ./prompts/system.md tools [read, write, bash] steering_mode one-at-a-time follow_up_mode one-at-a-time配置写好后Session 文件里到底长什么样假设你输入“帮我写一个 hello world 的 Python 文件”JSONL 会按时间顺序追加这么几行{type:session,version:3,id:sess_01H...,timestamp:2025-01-01T10:00:00Z,cwd:/Users/you/proj} {type:model_change,id:a1,parentId:null,timestamp:2025-01-01T10:00:01Z,provider:anthropic,modelId:claude-sonnet} {type:message,id:b2,parentId:a1,timestamp:2025-01-01T10:00:02Z,message:{role:user,content:帮我写一个 hello world 的 Python 文件}} {type:message,id:c3,parentId:b2,timestamp:2025-01-01T10:00:05Z,message:{role:assistant,content:[{type:tool_use,name:write,input:{path:hello.py}}],provider:anthropic,model:claude-sonnet,stopReason:toolUse}} {type:message,id:d4,parentId:c3,timestamp:2025-01-01T10:00:06Z,message:{role:toolResult,toolCallId:call_1,toolName:write,content:[{type:text,text:written}],isError:false}} {type:message,id:e5,parentId:d4,timestamp:2025-01-01T10:00:08Z,message:{role:assistant,content:[{type:text,text:已创建 hello.py}],provider:anthropic,model:claude-sonnet,stopReason:stop}}从这几行能看出 Pi 的几个设计取舍。第一线性追加但用parentId串成链表每条新条目指向上一条这样天然支持分支——你从c3开新分支d4、e5就留在旧路径上叶子指针移过去就行。第二user、assistant、toolResult 都是message类型地位平等没有“会话主题”这种更高层概念。第三模型切换会单独记一条model_change恢复时就知道当时用的是哪个模型。第四因为是 JSONLcat就能看懂调试时不用专门写解析器。压缩Compaction在树里的表现也值得看一眼。它不是删老消息而是插入一个摘要节点并记下从哪条开始还保留原文{type:compaction,id:f6,parentId:e5,timestamp:...,summary:用户要求创建 hello.py已完成,retainFromId:e5}下次 LLM 看到的消息流就变成[compactionSummary, 后续消息]原来的b2→e5折叠成一段摘要文字物理上还在文件里只是不喂给模型。这就是 Session 既是“账本”又是“指针”还是“压缩机”的含义。4. 验证请求用 curl 走完会话读写与恢复三步配置和文件格式都清楚了接下来用 curl 实际验证一遍。Pi 的 Session 读写最终会落到 HTTP 请求上我们直接打 TaoToken 的 API看会话上下文能不能正确带上、恢复后能不能接着聊。三步动作每步都有明确的预期结果。第一步验证基础连通和模型返回。这一步不带会话历史就是确认 Key、Base URL、Model ID 三件套没问题curl -s https://taotoken.net/api/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: claude-sonnet, max_tokens: 128, messages: [ {role: user, content: 只回复两个字收到} ] }预期返回里能看到content数组第一项text是“收到”。如果这里就报 401说明 Key 不对或没带上报 404多半是 Base URL 写成了带/v1的版本路径重复了。第二步模拟多轮会话把上一轮的 assistant 回复手动拼进 messages验证“上下文携带”这件事curl -s https://taotoken.net/api/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: claude-sonnet, max_tokens: 256, messages: [ {role: user, content: 帮我写一个 hello world 的 Python 文件}, {role: assistant, content: 已创建 hello.py}, {role: user, content: 刚才那个文件里 print 的是什么} ] }这一步的关键是模型能基于前两轮回答“print 的是 Hello World”。这正是 Session 在运行时做的事——buildContext从当前叶子到根把路径上所有消息取出来转成 LLM 看得懂的格式。你在 curl 里手动拼的就是 Pi 自动帮你拼的。第三步验证恢复。把第二步的 JSONL 会话文件读回来确认parentId链完整、叶子指针正确cat ./.pi/sessions/sess_01H....jsonl | jq -c {type, id, parentId}预期输出是一串按时间顺序的条目每条parentId指向上一条的id最后一条的id就是当前叶子。Pi 恢复时会从最后一条往回走重建整条路径。如果中间断了链比如某条parentId指向不存在的id恢复会报session_corrupt这时候就得检查是不是手动改过文件或者写入时进程被强杀导致半行 JSON。三步走完你应该能直观看到Session 的读发生在组装上下文时写发生在每轮消息和工具结果落盘时读和写的边界正好是 LLM 和持久化的边界。curl 只是把这层契约摊开给你看Pi 内部用的是同一套逻辑。5. 常见报错排查401、local proxy failed 与 reading choices实际跑的时候报错基本集中在几个地方。这一节按真实错误信息对照排查每个都给定位思路。401 Unauthorized或invalid api key。先确认环境变量有没有真正导出echo $TAOTOKEN_API_KEY看是不是空。再看配置里apiKeyEnv写的名字和实际导出的名字是否一致大小写敏感。还有一种情况是 Key 创建时权限没给够只勾了只读写会话的请求会被拒。去控制台重新建一个带写权限的 Key 即可。local proxy failed或connection refused。这类错误通常不是 Key 的问题而是请求根本没出去。检查 Base URL 是不是写成了https://taotoken.net/api别多加斜杠或路径。如果你本地有网络层工具在跑确认它没有拦截这个域名。Pi 的 provider 层如果配了自定义fetch也要确认没把请求转发到错误的地址。reading choices或cannot read property choices of undefined。这个报错说明 SDK 按 OpenAI 协议解析响应但实际返回的是 Anthropic 格式或者反过来。根因是provider.type和modelId不匹配。你选的是 Claude 系模型provider.type就得是anthropic响应里是content数组而不是choices。改配置里的type字段重启 AgentHarness 再试。OAuth token expired或authentication_error。如果你之前用 OAuth 方式登录过某个客户端本地可能残留了旧的凭证文件Pi 优先读了它而不是你的 API Key。检查~/.pi/auth.json或项目下的.pi/auth.json把过期的 OAuth 段删掉只保留 API Key 配置。Codex 的auth.json如果存在也要确认里面的OPENAI_API_KEY没覆盖你的环境变量。session_corrupt或invalid parentId。JSONL 文件被手动改过或者写入时进程被强杀导致最后一行是半截 JSON。用jq逐行校验把最后一行不完整的删掉或者从上一个完整 save point 恢复。这也是为什么配置里建议开flushOnSettled每个 turn 结束强制落盘能把损坏范围控制在一轮之内。invalid_state且提示steer/followUp in idle。这是队列用错了。Agent 空闲时你调了session.steer()或session.followUp()这两个方法在 idle 下会抛AgentHarnessError(invalid_state)。空闲时想说话直接走executeTurn或普通 prompt别用队列方法。TUI 里空闲状态只显示 prompt 输入框、不显示 steer 按钮就是这个原因。排查顺序建议固定先看 HTTP 状态码401/404 归到 Key 和地址再看响应结构choices和content对不上就查 provider type最后看本地文件JSONL 链断没断、auth 文件有没有残留。按这个顺序走大部分问题五分钟内能定位。6. 把 Session 通道固定下来继续往下拆走到这里你已经把 Pi 的 Session 从配置到落盘、从 curl 验证到报错排查走了一遍。回头看Session 的核心职责其实一句话能概括把用户输入、AI 回复、工具结果、配置变更、压缩快照、分支导航这些事件当成一份按时间追加、有父子关系的可恢复历史并提供“读出当前叶子路径上的消息”这个基本能力。实际开发里我建议把模型通道固定成一套环境变量Key、Base URL、Model ID 三件套不要散落在代码各处。TaoToken 的 API 根地址https://taotoken.net/api配上控制台创建的 Key就能覆盖大部分调试场景。需要看模型原始返回时去模型对话页面需要长期跑编码 Agent 时切到 Coding Plan接入细节查文档页 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。Claude Code 相关的接入配置在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode_anthropicutm_campaignrewrite 需要的话可以对照着改。下一篇会继续拆 Session 的树形分支、压缩触发时机和三种队列Steer / FollowUp / NextTurn的排空逻辑尤其是steeringMode设成all时多条 steer 消息怎么一次性灌进上下文。这部分和本篇的 JSONL 结构是连着的建议先把本篇的配置跑通拿到一个真实的.pi/sessions/*.jsonl文件下一篇对着文件看会更清楚。
返回列表