来学习下pi agent的原理
之前也偶尔用户pi agent这个工具,当然这个还是个很庞大的系统,我也只能一点点学
首先是工具系统,默认的核心工具主要是四个 read,write,edit,bash
read 主要是读取文件
输入结构式这样1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"path": "src/app.ts",
"offset": 1,
"limit": 200
}
- path:相对或绝对路径
- offset:从第几行开始,1 开始计数
- limit:最多读取多少行
内部的原理大致是
```text
解析路径
→ 检查文件是否可读
→ 判断是文本还是图片
→ 读取内容
→ 按 offset/limit 截取
→ 限制输出大小
→ 返回给模型
对于文本,Pi 将文件按照 UTF-8 解码并拆分为行。默认最多返回前 2000 行或 50KB,哪个先达到就按哪个截断,并告诉模型下一次应该使用哪个 offset 继续读取。1
[Showing lines 1-2000 of 3540. Use offset=2001 to continue.]
write 创建文件或完整覆盖
输入1
2
3
4{
"path": "src/Hello.php",
"content": "<?php\n\necho \"hello\";\n"
}
主要原理1
2
3
4
5解析目标路径
→ 创建不存在的父目录
→ 进入该文件的 mutation queue
→ 完整写入 content
→ 返回写入结果
如果文件已经存在,则完整覆盖。它不是“追加”,也不是“局部修改”。
Pi 会针对同一个文件建立 mutation queue,防止多个并行工具同时修改同一个文件导致内容互相覆盖:1
2
3
4
5write A ──────┐
├→ 同一个文件顺序执行
edit A ──────┘
write B ─────────→ 不同文件可以独立处理
edit 精确替换已有代码
这是四个工具中设计最讲究的一个。
输入
当前 Pi 支持一次提交多个互不重叠的修改:1
2
3
4
5
6
7
8
9
10
11
12
13{
"path": "src/UserService.php",
"edits": [
{
"oldText": "public function find($id)",
"newText": "public function find(int $id): ?User"
},
{
"oldText": "return $this->users[$id];",
"newText": "return $this->users[$id] ?? null;"
}
]
}
内部原理1
2
3
4
5
6
7
8
9读取原文件
→ 去掉 BOM
→ 将 CRLF/CR 统一成 LF
→ 在原始文件中寻找每个 oldText
→ 检查每段是否唯一、是否重叠
→ 执行全部替换
→ 恢复原来的换行符和 BOM
→ 写回文件
→ 生成 diff 和 unified patch
关键要求是:oldText 必须精确匹配,而且在原文件里唯一。
例如文件中有两个:1
return null;
模型如果只提交:1
2
3
4{
"oldText": "return null;",
"newText": "throw new RuntimeException();"
}
工具会拒绝,因为不知道应该修改哪一个。
模型必须提供足够的上下文:1
2
3
4{
"oldText": "public function load(): ?User\n{\n return null;\n}",
"newText": "public function load(): ?User\n{\n return $this->repository->first();\n}"
}
为什么采用精确替换
- 它相当于一种轻量级“乐观锁”:
- 模型看到文件版本 A
- 模型根据 A 生成 oldText
- 如果调用工具前文件被改成版本 B
- oldText 匹配失败
- 工具拒绝覆盖,要求模型重新读取
这比让模型直接按行号修改更安全,因为文件新增一行后,行号可能全部偏移。
执行成功后,Pi 同时生成: - 面向终端显示的彩色 diff
- 标准 unified patch
- 第一处变化的行号
bash 给模型提供“手脚”
输入1
2
3
4{
"command": "php -l src/UserService.php && php tests/run.php",
"timeout": 30
}
原理:1
2
3
4
5
6
7找到当前平台的 Shell
→ 在工作目录启动子进程
→ 注入环境变量
→ 同时监听 stdout/stderr
→ 实时把输出推送到 TUI
→ 等待退出码
→ 将结果返回给模型
它可以完成:
rg、find、ls搜索项目- 运行测试
- 调用编译器
- 执行 Git 命令
- 安装依赖
- 启动构建脚本
- 执行任意系统程序
- 输出处理
与read保留“开头”不同,bash更适合保留“结尾”。
因为测试、编译和日志输出的错误通常在末尾。因此 Pi 保留最后 2000 行或最后 50KB。如果发生截断,完整输出会另外保存到临时文件。
四个工具如何协作
假设用户说:
给项目增加一个 /health 接口并运行测试。
模型可能按下面的流程工作:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
231. bash
rg "Route|router" src
2. read
读取找到的路由文件
3. edit
精确插入 /health 路由
4. write
创建新的 HealthController.php
5. bash
php -l ... && php tests/run.php
6. read
如果测试失败,读取相关源文件
7. edit
修复问题
8. bash
再次运行测试
这就是 Coding Agent 的核心闭环:1
2
3
4
5观察(read/bash)
→ 决策(LLM)
→ 修改(edit/write)
→ 验证(bash)
→ 再观察
大致学一下这个工具的工具逻辑