之前也偶尔用户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 5
| write 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 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 23
| 1. 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) → 再观察
|
大致学一下这个工具的工具逻辑