目录
- 1. 学习教程
- 2. 常见黑话
- 3. 学习课程
- Agent是什么
- Session
- Agent怎么管理工具
- System Prompt与LLM Adapter
- Agent Loop
- Plugin和Extension
- Session Branch
- Context
- MCP
1. 学习教程
- pi框架
- mini-dsh
2. 常见黑话
Session:一次连续的对话(整段对话的历史)
Context:当前这一轮模型实际拿来思考的信息
Event Log:把Session里发生的事,按照时间一条一条记录下来(强调事实记录)
Memory:保存以后值得记录什么(对过往的Session的总结和提取,以后其他的Session也能用)
Tool Registry:工具注册表,agent loop只需要问registry有没有这个工具,如果有就执行它,所以tool registry是能力边界,它决定agent当前有哪些调用能力
Schema:是一个结构规则说明书,它还可以用来检验模型生成的Tool Call是否合法
System Prompt:给模型的最高层行为说明(在用户消息之前,系统先告诉模型你是谁、应该怎么做、规则是什么)
LLM Adapter:LLM是大语言模型,LLM Adapter负责把不同厂商的模型翻译成统一格式(也就是帮助Agent Loop接gpt/deepseek/claude)
Agent Loop
↓
LLM Adapter
┌─────────┼─────────┐
↓ ↓ ↓
OpenAI DeepSeek Claude
context:这一轮模型知道有效的信息
messages:已经发生过的对话和工具调用历史
LLM Runtime:管理adapter、选择,模型、发请求、收结果
Steering:在agent正在执行任务时,用户临时改变方向
stream assistant:assistant的回复是边生成边不断发出来的,而不是等一次性全部生成完再显示
follow-up:当一轮完成以后,用户继续追加的问题或任务
Session branch:同一个session里,从过去节点分叉出去的一条新路线
MCP:一种统一的标准,让agent/llm用同一种方式连接外部工具、数据和服务
agent runtime:agent的调度中心(真正让agent跑起来、管理整个agent loop的执行环境)
mcp client:agent和mcp server之间的连接器
3. 学习课程
Agent是什么
- agent的运行机制
普通聊天:输入信息,模型回答,得到文本
Agent:输入目标,模型判断下一步需要什么,如果需要就请求工具,harness执行工具,工具把结果返回,模型根据结果继续判断,直到任务完成
Agent = LLM+Tools+Context+Loop
while (true) { response = await model(context, toolSchemas) //把当前聊天信息和可用工具告诉模型, if (!response.toolCalls?.length) return response.content //如果模型没有调用工具,那么说明它准备直接给答案 results = await tools.execute(response.toolCalls) //执行模型刚才请求的工具 context = context.append(response, results) }上面的代码只是最小可理解框架,以后还有流式事件、权限、压缩、预算、会话持久化、UI
2. dsh和pi各自如何实现
DSH和pi都有agent loop,但是它们实现这些能力的方式不一样
DSH:把sessions、sysytemPrompt、tools、llm、agents和agentLoop作为组件的能力装进Context
Context不是普通聊天里的上下文,而是一个运行环境的容器,里面可以放很多系统能力,Context像一个插座板
Pi:把整个系统拆成三层
最底层pi-ai:模型协议(provider),负责和模型打交道
中间层pi-agent-core:agent状态与loop
最上层pi-coding-agent:产品体验(TUI、RPC、资源加载)
其中模型供应商和产品体验会变化,但核心Loop尽量稳定
Session
- 事实与投影
事实:SessionEvent[]
session/start
user/message
assistant/tool_calls
tool/result
assistant/message
投影:deriveMessages()
调用它时,事件被转换为模型协议需要的信息
user/message
assistant/tool_calls
tool/result
assistant/message
- 区别
DSH
更像一条时间线:
事件1
↓
事件2
↓
事件3
↓
事件4
Pi
事件1
↓
事件2
↓
事件3
├── 分支A
│ └── 事件4A
│
└── 分支B
└── 事件4B
pi有会话持久性与树形分支的历史结构
Agent怎么管理工具
- 用Tool Registry进行管理,而不是把工具写死在Agent Loop里面,这也是dsh和pi的共同点:能力可以变化,核心循环可以不用跟着重写
- 工具
name/description/parameters要转成能被模型理解的tool schema
execute是真正执行工具功能的函数
DSH:
register(注册工具)
↓
schemas(把工具schema提供给模型)
↓
execute(执行工具)
↓
renderResult(生成可读结果)
Pi:
beforeToolCall(先检查)
↓
tool execution(真正执行)
↓
afterToolCall(记录结果、更新状态)
System Prompt与LLM Adapter
- system prompt
- mini-dsh
//section更像稳定规则 prompt.section({ name: "ritual-rules", order: 20, text: "先陈述牌面,再给出多种可能解释。" }) //context更像动态信息 prompt.context({ name: "today", order: 10, text: () => `今天是 ${new Date().toISOString()}` }) //把前面注册的section和context按order拼好,在拼成最终的system prompt const system = await prompt.assemble()- pi
prompt template / skill / extension + 当前 Session 上下文 ↓ AgentSession 整理 ↓ 交给 pi-ai ↓ LLMprompt template是预先写好的提示词模板
skill是工作流程,可以影响模型怎么做这件事
extension是插件/扩展包,也就是给agent添加新的外围能力
AgentSession是当前这个对话的管理者
pi不是让Agent Loop自己找prompt、skill、extension,而是先由agentsession把当前需要的东西整理好,然后传给大模型
- llm adapter
const reply = await llm.chat({ system,//给模型的规则 messages,//之前的聊天和工作记录 tools: toolRegistry.schemas()//可以调用的工具 }, "deepseek/deepseek-v4-flash")//用什么大模型Agent Loop
- 模型循环
模型决定直接回答还是请求工具
工具真正去执行动作,产生一个事实
事实回到模型,模型决定下一步
模型认为还需要新的工具事实,就继续;不需要了,就结束
- dsh循环
while (true) { const reply = await llm.chat({ messages, tools }) //把message和tools发给模型 if (reply.tool_calls.length === 0) { session.append(reply) // 模型直接回答 return // 结束 } session.append(reply) // 记下模型请求了什么工具 for (const call of reply.tool_calls) { const result = await tools.execute(call) // 执行工具 session.append(result) // 记下工具事实 } // 回到 while:把事实交给模型再问一轮 }- pi循环
开始一轮
↓
先检查 steering
↓
调用模型,stream assistant
↓
模型有没有 tool call?
├─ 有 → 执行工具 → 把结果放回历史 → 再进入下一轮
└─ 没有 → 检查有没有 follow-up
├─ 有 → 继续下一轮
└─ 没有 → agent_end
Plugin和Extension
- 区分Tool和plugin/extension
Tool:模型可以通过Tool Call请求调用的一项能力,通常包含name、description、schema、execute
Plugin/Extension:插件和扩展的作用是给Agent提供新的能力
- dsh与pi
共同原理:核心负责稳定的工作;外围模块负责变化的玩法
核心agent(请求模型、执行循环、保存状态)是比较稳定的,安装模块(Plugin/Extension)可以使agent获取新能力,卸载模块(能力消失,但核心agent不改)
区别(把能力接进去的区别)
dsh:Plugin通过Context往系统里注册能力
pi:如下
// pi.registerTool:给agent增加一个模型可以请求调用的Tool export default function (pi) { pi.registerTool({ name: "draw_card", description: "抽一张牌,只返回牌面事实", parameters: { /* 参数 schema */ }, execute: async () => ({ content: [{ type: "text", text: "太阳" }] }) }) //pi.on是监听事件,当session_start开始的时候 pi.on("session_start", async (_event, ctx) => { ctx.ui.notify("塔罗扩展已加载", "info") }) }一个Extension不一定只能提供一个Tool,它可以提供很多东西
Tarot Extension
│
├── Tool
│ └── draw_card
│
├── Event Listener
│ └── session_start
│
├── Prompt
│
├── Command
│
├── UI
│
└── 其他扩展能力
Session Branch
- pi
用户:帮我看今天的状态
└─ 助手:抽到太阳
├─ 用户:给我积极解读
│ └─ 助手:今天适合主动行动
└─ 用户:给我谨慎解读
└─ 助手:行动前先观察节奏
pi的每个对话,不止有id,还有parentId,
树的当前末端叫active leaf
/tree:在同一个对话里,切换到另一个分支
/fork:从较早的用户信息创建一个新的对话
/clone:把当前branch复制成一个新的对话
Context
- dsh
在mini-dsh里,session的完整历史是怎么变成这一轮真正发送给LLM的内容的
Session Event Log ↓ deriveMessages() ↓ messages + system prompt + tools ↓ llm.chat(...)先从完整事件日志里,整理出来模型真正需要看的聊天记录(messages),然后加上全局提示词和tools一块交给llm
- pi
pi会做compaction(上下文压缩),当旧的context太多的时候,pi会把context压短,这样给llm看的context就变短点了
旧消息很多 ↓ Pi 找出较早的一段内容 ↓ 让模型把旧内容总结成一份摘要 ↓ 保留:摘要 + 较新的消息 ↓ 用新的 Context 继续工作MCP
MCP Server会告诉agent自己的能力,这个过程叫做Tool Discovery工具发现
agent把工具的Schema(调用工具的说明书)给 模型
模型产生Tool Call,模型提出请求
然后agent runtime收到请求,发现tool不是本地的,是mcp server提供的,接着通过mcp client和mcp协议,到mcp server才开始真正执行工具(这一步就是把模型产生的tool call 转发到真正拥有中国tool的外部服务)
有结果以后,再让模型继续思考
外部服务怎么连接,是外围代码负责;Agent Loop 只处理统一的 Tool
- dsh
外部 ┌─────────────────────┐ │ Tarot MCP Server │ │ 真正有 draw_card │ └─────────┬───────────┘ │ MCP ↓ ┌─────────────────────┐ │ dsh-mcp-client │ │ 发现工具 + 转发调用 │ └─────────┬───────────┘ ↓ ┌─────────────────────┐ │ Tool Registry │ │ │ │ read_file │ │ bash │ │ draw_card ← MCP来的 │ └─────────┬───────────┘ ↓ ┌─────────────────────┐ │ Agent Loop │ └─────────────────────┘mcp server是工具提供方,mcp client把外部的tool接进来,tool registry把它和本地的tool统一管理,agent tool不管工具来自哪里
- pi
pi.registerTool({ name: "draw_card", async execute(_id, params, signal) { const card = await remoteTarotService.draw(params, signal) //当这个工具被调用的时候,不是在本地抽牌,而是去远程tarot service,让远程服务器帮我抽,拿到结果以后,返还结果给agent return { content: [ { type: "text", text: card } ] } } })extension是扩展模块,pi仅仅只是一个agent,它不知道什么是draw_card,如果想让它会塔罗。第一种是直接去改pi核心,也就是在agent loop里面改,这样子很麻烦,所以pi希望核心agent不动,从外面装extension。第二种是pi会给extension一个pi(对外提供的extension api),当pi加载这个扩展的时候,把自己的扩展接口pi给它,然后extension可以告诉pi,我要给你添加tool