1. 先厘清一件事:PI 框架到底是干嘛的如果你最近也在折腾大模型相关的东西,不管做 RAG、智能对话还是自动化脚本,一定越来越频繁地听到一个词:AI 编程代理。简单说,编程代理就是让模型不光回答问题,而是真正上手改代码、跑测试、查日志、提交修复。理论上,它能把大量重复劳动自动化掉;实际上,我见过不少团队在这里翻车——不是模型不够聪明,而是框架太重了,搭建成本远超预期。PI 框架是我最近一直在实践的一个开源框架,主打轻量、可控、容易上手。核心思路浓缩在名字里:P 是 Plan,I 是 Implement,合起来就是"先规划、再实施"。它把编程代理的所有复杂逻辑收敛成一条清晰环路:先让模型把大任务拆成可执行的小步骤,再逐步实施,每一步都留出验证和回退的空间。跟那些动辄要起一堆服务、配一堆中间件的 Agent 框架比,PI 框架更像一个"拎起来就能跑"的工具箱,特别适合作为构建 AI 编程代理的底座。哪些人适合花时间看它?我的判断是三类。第一类是独立开发者,想给自己写个自动修 bug、自动补测试的助手;第二类是小团队,想把重复的代码审查、格式化、单测生成交给代理做;第三类是搞内部工具平台的工程师,需要在一个看得懂、改得动的框架上做二次开发。这三类人的共同点是:不想要黑盒,也不想要庞然大物,要的是一个能读懂、能扩展、能快速部署的工具。1.1 名字里的方法论:为什么规划必须先行早期很多 Agent 框架的思路是"让模型自由发挥",给一个目标,然后连续调用工具直到完成任务。这种方法处理简单问答没问题,一旦落到编程场景立刻暴露问题:模型很容易在第一步就走偏,越走越远,等你发现时已经浪费了十几步、烧掉了大量窗口额度。PI 框架把"规划"强制放在第一步,并且把规划结果当成可追踪、可修改的中间产物。它要求模型在真正动代码之前,先把目标拆成若干子任务,每个子任务都要标注输入来源、输出产物、依赖关系。这样有两个直接好处:第一,你可以在执行前检查规划,发现不合理的地方直接修正,不必等模型跑完整个流程才后悔;第二,规划本身可以作为上下文压缩的依据——执行到某个子任务时,只保留与此相关的信息和对话片段,而不是把整段历史全塞进模型。这种"先规划再实施"的模式,本质上就是成熟工程师的工作方式。经验丰富的开发者接任务后不会直接动键盘,而是先估一下影响面、依赖顺序、风险点,列个清单再动手。PI 框架做的就是把这种思维固化成一个可运行的机制。规划越细,后面的执行越不容易失控。1.2 编程代理的核心闭环:无非四件事轮流转不管框架怎么包装,一个能稳定干活的编程代理底层都是同一套闭环:理解任务、制定计划、执行动作、验证结果,然后循环。PI 框架把这四件事映射成四个可插拔的组件:任务解析器负责把自然语言需求转成结构化目标;规划器负责生成行动清单;执行器负责调用工具;验证器负责判断每一步做没做对。重点在于,这四个组件的接口都极其简单,基本是"输入一段结构化上下文,输出一个结构化决策"。接口简单意味着替换容易——你今天可以接对话能力强的模型,明天可以换响应速度快的小模型,甚至可以在规划阶段用推理更强的模型,在执行阶段用经济性更好的模型。这种组合打法往往比单一模型扛全部流程效果更好。这一点我在后面调优部分会专门展开,因为它是影响成本和成功率最大的杠杆。2. 核心架构拆解:PI 框架的三大支柱2.1 规划器:把模糊需求变成可执行的行动清单规划器是 PI 框架的大脑。它接收一个目标,通常包括用户的自然语言需求、当前项目结构、相关文件路径,然后输出一份有序的步骤列表。每一步都包含四个字段:意图说明、涉及文件、所需工具、预期验收标准。我正在用的版本里,规划器的输出是 JSON 格式,方便后续每一个环节直接消费。这里有一个容易被忽略的关键设计:规划器输出的步骤不是一次性定死的,每一轮执行完之后,规划器会根据验证结果决定下一步是继续、重做还是中止。也就是说,规划器和执行器之间是闭环的,规划是"滚动规划",不是一锤子买卖。这个设计非常重要,因为模型在执行中一定会发现新信息——文件结构和预想不一样、某个依赖版本对不上、某个接口签名在文档里是错的——这些新信息必须能反馈到后续计划里,而不是让代理埋头照着错误的计划跑到底。从实现角度看,规划器本质上是一段精心设计的系统提示词加上结构化输出约束。PI 框架内置了两种规划策略:顺序规划适合逻辑关系清晰的改代码任务,直接拆成第一步做什么、第二步做什么,依次执行;目标树规划适合涉及多模块、可并行处理的场景,把大目标拆成并行的子目标,分别推进后再汇总。实际项目中,我建议大家先无脑用顺序规划,等任务粒度变粗了再切换目标树。另外,规划器还做了防重复设计。如果验证器报告某一步失败,规划器会先检查失败原因:如果是可修复的执行问题,它会把"修复该问题"作为一个新步骤追加进去;如果连续三次在同一处失败,它就会触发回退机制,重新审视顶层目标,而不是在原地打转。2.2 执行器:把计划翻译成一次次工具调用执行器负责真正动手。它的核心职责是把规划器输出的步骤变成一系列工具调用。在 PI 框架里,"工具"是个很宽泛的概念:可以读取文件、写文件、跑测试、执行 shell 命令、调接口,甚至可以调用另一个模型去完成子任务。PI 框架对工具的抽象很对我胃口。它没有强迫你继承一堆复杂的基类,也不要求你手动编写 JSON Schema,而是用"函数签名即协议"的思路:只要写一个普通 Python 函数,加上类型注解和一个注册装饰器,框架就会基于函数名、参数名和类型注解自动生成模型可用的工具描述。这意味着你写工具时的命名和注释质量,直接决定了模型调用的准确率。举个例子,如果你写一个参数名叫path的函数,模型能理解;如果你写个p或者不写注释,模型大概率会在调用时猜错。我自己有过一次惨痛教训:工具没有写清楚路径是绝对路径还是相对路径,代理把相对于项目根目录的路径当成系统绝对路径用,导致一连串误删和重建操作。后来我学乖了,每个工具的描述里都明确写上"输入为相对项目根目录的路径"这类信息,调用准确率立刻就上来了。执行器还有一层调度职责:它要控制工具调用的并发度和超时时间。PI 框架默认串行调用工具,因为大多数代码场景里后续步骤依赖前一步的输出;但如果你把工具标记为parallel=True,执行器会自动将相互独立的步骤合并成一批,同一批内并发执行,显著降低整体耗时。2.3 验证器:让代理对自己的产出负责验证器是很多 Agent 框架最容易忽略的一环,但恰恰是它决定了一个代理到底能不能闭眼交付。没有验证器的代理,本质上是"写完就再也不管"的实习生;有了验证器,才变成"做完还自己检查一遍"的可靠工程师。PI 框架的验证器分两类。内置验证面向常见代码任务,比如检查生成的 Python 文件能否编译、测试是否全部通过、文件改动是否落在预期路径;外部验证则允许你自己写验证函数,比如校验某个 API 的响应格式、核对生成的表单与模板是否一致、检查数据库迁移脚本能否在临时库上跑通。验证器输出的结果会被写回规划上下文,作为下一轮规划的重要输入。这个反馈信号是持续提升代理成功率的关键:模型能看到自己哪一步做错了,才有机会在下一步修正。没有这个闭环,代理就会变成一个"每次都在同样的坑里跌倒"的复读机。我测试下来,在同样的任务集上,加上验证器之后,一次通过率从不足三成提升到了七成以上,这个数字非常直观。3. 从零动手:5 分钟搭一个自动补单测的代理光说架构有点虚,直接上实操。这一节我带大家从空环境开始,搭一个真正能用的编程代理。目标很具体:项目里新增了一个计算模块calc.py,代理需要为这个模块自动生成单元测试,并跑通测试框架。整个过程我都会标注每一步的用途,方便你对照着改。3.1 安装框架与确认环境先确认环境,Python 3.10 以上版本最好。PI 框架本身依赖的包很少,核心就一个异步事件循环加一个 LLM 客户端封装。使用虚拟环境安装:python -m venv .agent-env source .agent-env/bin/activate pip install pi-agent因为编程代理要读写文件、执行测试,我建议在一个隔离的目录里做实验,不要直接在自己的主项目上试。我一般习惯建一个sandbox/文件夹,里面放若干个模拟项目,代理的所有操作都限定在这个沙箱目录中,这样就算它发疯也破坏不了别的东西。安装完后初始化一个代理配置文件:pi init这条命令会生成pi_config.yaml,里面包含了模型接入地址、规划策略、工具注册表等默认配置。我第一次看到生成的文件时有点意外,居然只有四十多行,比某些框架动辄两三百行的配置清爽太多。3.2 注册三个最基础的工具代理要干活,最少需要三个工具:读文件、写文件、跑测试。用 PI 框架的注册方式写一个工具模块tools.py:from pi import register_tool from pathlib import Path @register_tool def read_file(path: str) -> str: """读取项目内文件,path 为相对项目根目录的路径。""" full = Path(project_root) / path return full.read_text(encoding="utf-8") @register_tool def write_file(path: str, content: str) -> str: """写入文件,自动创建父目录,path 为相对项目根目录的路径。""" full = Path(project_root) / path full.parent.mkdir(parents=True, exist_ok=True) full.write_text(content, encoding="utf-8") return f"已写入 {path}" @register_tool def run_tests(test_path: str = "tests") -> str: """执行测试文件,返回完整输出。""" result = subprocess.run( ["pytest", str(Path(project_root) / test_path), "-q"], capture_output=True, text=True, timeout=60, ) return result.stdout + result.stderr这里我想强调一点:read_file和run_tests的描述我都写了路径的相对性,这就是我在前面说的"函数签名即协议"。模型不会自己猜你的路径语义,全靠描述传递信息。描述写得越精确,调用越少翻车。3.3 定义任务,把核心闭环跑起来在agent_demo.py里组装代理:from pi import create_agent agent = create_agent( name="test-writer", endpoint="http://your-llm-service/v1", model="code-gen-model", planner={"strategy": "sequential", "max_steps": 5}, executor={"timeout_seconds": 30, "retry": 2}, ) result = agent.run( task="为 calc.py 中的 Calculator 类生成完整单元测试", workspace="./sandbox/demo_project", ) print(result.summary)跑起来之后,你能在控制台看到代理的完整行动轨迹。我最常用的观测方式是开verbose=True,它会实时打印规划 JSON、工具调用参数、验证结果。第一次跑通的轨迹大概是这样的:规划器输出三步计划:读取calc.py源码,生成test_calc.py,运行测试。执行器调用read_file,拿到Calculator类的方法列表。执行器调用write_file,写入初版测试文件。验证器调用run_tests,发现有两个用例没通过。规划器追加一步修复计划,执行器重写失败的测试断言。再次运行测试,全部通过,流程结束。整个流程不到两分钟。这个示例虽然简单,但已经把闭环走通了。你后面要做更复杂的事情,基本就是往这个框架里不断加工具和验证器。3.4 看一份真实的规划产物长什么样代理执行中,规划器产出的 JSON 大概是这样的:[ { "step": 1, "goal": "读取目标模块源码,了解 Calculator 类接口", "files": ["calc.py"], "tools": ["read_file"], "acceptance": "已取得全部方法和边界条件" }, { "step": 2, "goal": "生成覆盖常规与异常路径的单元测试", "files": ["test_calc.py"], "tools": ["write_file"], "acceptance": "测试包含整数除法、被零除、类型错误用例" }, { "step": 3, "goal": "运行测试并确认全部通过", "files": ["test_calc.py"], "tools": ["run_tests"], "acceptance": "pytest 结果无失败,无语法错误" } ]注意每一步都有acceptance字段,这是验证器判断"这步算不算完成"的基准。如果你的项目有特殊要求,比如"所有测试必须覆盖参数化用例"、"文档字符串必须包含示例",就把这些要求写进acceptance,验证器会按你的标准来,而不是让模型自己给自己打分。4. 决定成败的关键参数与调优手记框架搭起来只是开始,真正决定代理好不好用的,是几个关键参数的设定。这些参数我前前后后调了很多轮,把最影响结果的几条经验放在这里。4.1 最大步数与滚动规划深度max_steps这个参数控制代理最多执行多少步。设得太小,复杂任务根本跑不完;设得太大,低效循环会白白烧掉大量时间。我的一般经验是:简单任务 5 步以内,中等任务 8 到 10 步,大型重构任务建议设到 20 步以上,但同时在验证器里加上"连续 3 次同样的失败必须中止"的规则,防止原地转圈。还有一个隐藏参数叫规划深度,它决定规划器拆解任务时的粒度。深度 1 表示只拆成"读代码、写代码、跑测试"这种粗粒度步骤;深度 3 则会把"写代码"进一步拆成"编写函数骨架、填充业务逻辑、处理边界条件、添加注释"。深度越大,每一步越容易被验证器精确判断,但规划本身的 token 开销也越大。我实测下来的结论:日常任务用深度 2 性价比最高,复杂模块用深度 3。不要动不动就上深度 4,规划链太长之后,模型自己都容易在规划阶段迷失方向。4.2 上下文预算怎么分这是我觉得 PI 框架做得最聪明的地方。它把模型上下文比作一块总预算,明确分给四个部分:系统提示、滚动规划产物、工具执行记录、对话历史。其中,系统提示只占很小头,规划产物是常驻的,工具执行记录只保留最近几轮的完整内容,更早的执行记录会被压缩成一行摘要。我自己常用的分配比例大概是:规划占三成,工具执行记录占四成,对话历史占两成,系统提示一成。如果你发现代理总是"记性不好",忘掉早期规划步骤,通常是规划产物被截断了,这时候要把规划部分的预算调大,或者减少最大步数,让规划产物变短一些。上下文预算的调优没有银弹,我的土办法是打开 trace 日志,观察每轮实际发送给模型的 token 数量。如果发送量超过总预算的八成,基本可以断定代理要开始"胡言乱语"了。这时候优先压缩对话历史,把一轮执行中无关紧要的中间输出裁剪掉。4.3 重试、超时与并发阈值执行器的三个参数我提一下。timeout_seconds是单次工具调用的超时时间,跑测试、调接口这类工具要给足 60 秒以上,读写文件给 10 秒就够;retry是工具调用失败后的重试次数,我建议设 1 到 2,不要超过 3,因为能重试解决的问题一遍就够,不能解决的问题重试十遍也是白搭;max_concurrency是并行工具的数量,当你注册了多个标记为parallel的工具时这个参数才有意义,默认 1 就行,不宜开大,模型同时处理多个工具的上下文很容易串味。这里有个我踩过的坑:给测试工具设了 15 秒超时,但项目变大后测试本来就跑 30 秒,代理连续三次超时后直接判定任务失败删掉了自己写了一半的测试文件。后来我把超时调成 90 秒,并且在验证器里加了"超时不算失败,只算待重试"的规则,问题才解决。所以那些不可控的工具,超时要给足弹性。4.4 模型选型的"组合拳"PI 框架允许每个阶段用不同模型。我的推荐配置是:规划阶段用推理能力强、但速度和价格都偏贵的模型,因为规划错误是最贵的错误,一次规划失误可能导致后面几十步全废;执行和验证阶段用响应快、价格低的模型,因为这两个阶段动作很简单,用不着顶级推理能力。这么一套组合下来,任务成功率能维持在高位,但成本比全程用顶级模型省百分之五六十。如果你是个人开发者,更在乎成本,可以规划阶段也用普通模型,但要在验证器上加严,用更多轮的迭代来修正规划误差。5. 我踩过的坑:高频问题排查实录做编程代理这件事,最大的障碍从来不是模型能力,而是一堆边界情况。这一节我把自己实操中遇到的典型问题整理成速查表,每条都附上排查思路和解决办法。5.1 代理在同一个决策点上反复横跳现象是:代理在执行到某一步时,反复修改同一个文件,每次修改内容差不多,验证始终不通过,但代理不觉得是方向错了,只以为是细节没写对。这种问题我在自动生成测试用例时遇到最频繁,尤其是涉及浮点数断言、编码格式这类容易有隐式误差的场景。排查思路:先看验证器的失败输出是否包含足够信息。很多时候验证失败只有一句assert False,模型根本不知道错在哪。解决办法是在验证工具的描述里明确要求"失败时返回具体断言行、期望值和实际值",这样模型才能有针对性地修复。另外可以考虑在规划器里加规则:同一文件的修改次数超过 N 次,必须触发重新规划,不要继续原地修。5.2 工具明明失败,代理却当成成功继续往下走这个坑非常隐蔽。模型查看工具执行结果时,如果结果文本比较长,模型可能只看了开头几行就判定成功。最常见的是pytest输出前面一大段测试收集日志,真正失败的报错在最后面,模型看了前 500 个字符直接跳过了失败信息。解决办法有两层。第一层,在工具描述里加上"结果字符串以 SUCCESS 或 FAILED 开头"这样的显式约定;第二层,写一个结果摘要工具,对失败的输出做二次提炼,只把关键错误行返回给模型。我自己是把测试工具的输出做了后处理,把测试摘要压缩成"通过数、失败数、失败用例名列表"三行,模型误判的情况立刻少了很多。5.3 上下文越用越长,代理行为越退化这个问题在对话式 Agent 里几乎必然出现。前几轮代理还很清醒,后面逐渐开始答非所问、重复操作,日志里 token 消耗量也在暴涨。本质是上下文里塞了太多无关紧要的历史记录,模型被噪声淹没。排查思路是看每一轮发送量。PI 框架本身有摘要压缩机制,但如果你打开了 verbose 日志,发现某个轮次的输入 token 数是前几轮的几倍,就要检查是不是有工具返回了超大文本。比如read_file默认返回整个文件内容,一个上千行的文件会直接吃掉大量预算。我的做法是给read_file加max_chars参数,超过后只返回文件的前缀和行号索引,代理需要哪一段再单独按行号去读,省预算又省时间。5.4 给代理的权限一定要有边界最后是安全敬畏。我第一次做自动化时,给代理挂了一个可以执行任意 shell 命令的通用工具,结果它在一次数据库迁移测试里把沙箱环境的临时表数据清掉了,虽然没有真实影响,但也吓得我再不敢放开权限。PI 框架没有很复杂的权限系统,它的哲学是"少给工具,多写验证"。我的习惯是:每个任务只挂绝对必要的工具;通用 shell 执行工具永远不挂,需要跑脚本就写专门的工具,参数白名单验证;所有写文件操作强制限定在 workspace 目录内;涉及删除的操作必须额外确认一次。这些边界规则写进系统提示是不够的,必须在工具函数内部用代码强制,因为模型在压力下真的会忘。6. 后续还能怎么玩:从单代理到多代理到这一步,单代理的闭环你已经能完全掌握了。再往深走,PI 框架支持把多个代理编排成一个小组,这在实际项目里非常有价值。我最近在做的一个内部工具就是这样的结构:一个管理者代理负责接收需求、拆分任务、派发结果;两个执行代理分工,一个专门处理代码生成,一个专门处理代码审查;还有一个测试代理负责跑回归。管理者代理的规划器会把任务分配出去,执行结果汇总回来后再做合并决策。每个代理共享同一套验证器,但各自的工具集完全隔离。这种多代理结构的好处不只是并行,更重要的是各代理可以用不同的模型承担不同的角色。审查代理如果和生成代理用同一个模型,容易出现"自己写的代码自己看不出问题"的盲区;用不同的模型,反而能交叉验证,抓出不少低级失误。关于扩展,还有几个我打算尝试的方向。一是把验证器的结果沉淀到日志里,积累一段时间就能统计出代理最常失败的工具和场景,有针对性地优化工具描述;二是对接项目管理系统,把任务描述直接拉进来作为代理输入,结果再写回去,形成一套半自动的开发辅助链路;三是实验外置的知识库,让代理在规划阶段先检索团队历史代码规范,让生成的代码从第一个版本开始就更贴近团队的约定。总的来说,PI 框架这个工具的定位很清晰:它不替你思考,但帮你把思考过程规范化、流程化,让 AI 编程代理从"碰运气"变成"走流程"。在这个框架上花的每一分钟,都会沉淀成可复用的工具、规则和调优经验,而不是打水漂。
1. 先厘清一件事:PI 框架到底是干嘛的如果你最近也在折腾大模型相关的东西,不管做 RAG、智能对话还是自动化脚本,一定越来越频繁地听到一个词:AI 编程代理。简单说,编程代理就是让模型不光回答问题,而是真正上手改代码、跑测试、查日志、提交修复。理论上,它能把大量重复劳动自动化掉;实际上,我见过不少团队在这里翻车——不是模型不够聪明,而是框架太重了,搭建成本远超预期。PI 框架是我最近一直在实践的一个开源框架,主打轻量、可控、容易上手。核心思路浓缩在名字里:P 是 Plan,I 是 Implement,合起来就是"先规划、再实施"。它把编程代理的所有复杂逻辑收敛成一条清晰环路:先让模型把大任务拆成可执行的小步骤,再逐步实施,每一步都留出验证和回退的空间。跟那些动辄要起一堆服务、配一堆中间件的 Agent 框架比,PI 框架更像一个"拎起来就能跑"的工具箱,特别适合作为构建 AI 编程代理的底座。哪些人适合花时间看它?我的判断是三类。第一类是独立开发者,想给自己写个自动修 bug、自动补测试的助手;第二类是小团队,想把重复的代码审查、格式化、单测生成交给代理做;第三类是搞内部工具平台的工程师,需要在一个看得懂、改得动的框架上做二次开发。这三类人的共同点是:不想要黑盒,也不想要庞然大物,要的是一个能读懂、能扩展、能快速部署的工具。1.1 名字里的方法论:为什么规划必须先行早期很多 Agent 框架的思路是"让模型自由发挥",给一个目标,然后连续调用工具直到完成任务。这种方法处理简单问答没问题,一旦落到编程场景立刻暴露问题:模型很容易在第一步就走偏,越走越远,等你发现时已经浪费了十几步、烧掉了大量窗口额度。PI 框架把"规划"强制放在第一步,并且把规划结果当成可追踪、可修改的中间产物。它要求模型在真正动代码之前,先把目标拆成若干子任务,每个子任务都要标注输入来源、输出产物、依赖关系。这样有两个直接好处:第一,你可以在执行前检查规划,发现不合理的地方直接修正,不必等模型跑完整个流程才后悔;第二,规划本身可以作为上下文压缩的依据——执行到某个子任务时,只保留与此相关的信息和对话片段,而不是把整段历史全塞进模型。这种"先规划再实施"的模式,本质上就是成熟工程师的工作方式。经验丰富的开发者接任务后不会直接动键盘,而是先估一下影响面、依赖顺序、风险点,列个清单再动手。PI 框架做的就是把这种思维固化成一个可运行的机制。规划越细,后面的执行越不容易失控。1.2 编程代理的核心闭环:无非四件事轮流转不管框架怎么包装,一个能稳定干活的编程代理底层都是同一套闭环:理解任务、制定计划、执行动作、验证结果,然后循环。PI 框架把这四件事映射成四个可插拔的组件:任务解析器负责把自然语言需求转成结构化目标;规划器负责生成行动清单;执行器负责调用工具;验证器负责判断每一步做没做对。重点在于,这四个组件的接口都极其简单,基本是"输入一段结构化上下文,输出一个结构化决策"。接口简单意味着替换容易——你今天可以接对话能力强的模型,明天可以换响应速度快的小模型,甚至可以在规划阶段用推理更强的模型,在执行阶段用经济性更好的模型。这种组合打法往往比单一模型扛全部流程效果更好。这一点我在后面调优部分会专门展开,因为它是影响成本和成功率最大的杠杆。2. 核心架构拆解:PI 框架的三大支柱2.1 规划器:把模糊需求变成可执行的行动清单规划器是 PI 框架的大脑。它接收一个目标,通常包括用户的自然语言需求、当前项目结构、相关文件路径,然后输出一份有序的步骤列表。每一步都包含四个字段:意图说明、涉及文件、所需工具、预期验收标准。我正在用的版本里,规划器的输出是 JSON 格式,方便后续每一个环节直接消费。这里有一个容易被忽略的关键设计:规划器输出的步骤不是一次性定死的,每一轮执行完之后,规划器会根据验证结果决定下一步是继续、重做还是中止。也就是说,规划器和执行器之间是闭环的,规划是"滚动规划",不是一锤子买卖。这个设计非常重要,因为模型在执行中一定会发现新信息——文件结构和预想不一样、某个依赖版本对不上、某个接口签名在文档里是错的——这些新信息必须能反馈到后续计划里,而不是让代理埋头照着错误的计划跑到底。从实现角度看,规划器本质上是一段精心设计的系统提示词加上结构化输出约束。PI 框架内置了两种规划策略:顺序规划适合逻辑关系清晰的改代码任务,直接拆成第一步做什么、第二步做什么,依次执行;目标树规划适合涉及多模块、可并行处理的场景,把大目标拆成并行的子目标,分别推进后再汇总。实际项目中,我建议大家先无脑用顺序规划,等任务粒度变粗了再切换目标树。另外,规划器还做了防重复设计。如果验证器报告某一步失败,规划器会先检查失败原因:如果是可修复的执行问题,它会把"修复该问题"作为一个新步骤追加进去;如果连续三次在同一处失败,它就会触发回退机制,重新审视顶层目标,而不是在原地打转。2.2 执行器:把计划翻译成一次次工具调用执行器负责真正动手。它的核心职责是把规划器输出的步骤变成一系列工具调用。在 PI 框架里,"工具"是个很宽泛的概念:可以读取文件、写文件、跑测试、执行 shell 命令、调接口,甚至可以调用另一个模型去完成子任务。PI 框架对工具的抽象很对我胃口。它没有强迫你继承一堆复杂的基类,也不要求你手动编写 JSON Schema,而是用"函数签名即协议"的思路:只要写一个普通 Python 函数,加上类型注解和一个注册装饰器,框架就会基于函数名、参数名和类型注解自动生成模型可用的工具描述。这意味着你写工具时的命名和注释质量,直接决定了模型调用的准确率。举个例子,如果你写一个参数名叫path的函数,模型能理解;如果你写个p或者不写注释,模型大概率会在调用时猜错。我自己有过一次惨痛教训:工具没有写清楚路径是绝对路径还是相对路径,代理把相对于项目根目录的路径当成系统绝对路径用,导致一连串误删和重建操作。后来我学乖了,每个工具的描述里都明确写上"输入为相对项目根目录的路径"这类信息,调用准确率立刻就上来了。执行器还有一层调度职责:它要控制工具调用的并发度和超时时间。PI 框架默认串行调用工具,因为大多数代码场景里后续步骤依赖前一步的输出;但如果你把工具标记为parallel=True,执行器会自动将相互独立的步骤合并成一批,同一批内并发执行,显著降低整体耗时。2.3 验证器:让代理对自己的产出负责验证器是很多 Agent 框架最容易忽略的一环,但恰恰是它决定了一个代理到底能不能闭眼交付。没有验证器的代理,本质上是"写完就再也不管"的实习生;有了验证器,才变成"做完还自己检查一遍"的可靠工程师。PI 框架的验证器分两类。内置验证面向常见代码任务,比如检查生成的 Python 文件能否编译、测试是否全部通过、文件改动是否落在预期路径;外部验证则允许你自己写验证函数,比如校验某个 API 的响应格式、核对生成的表单与模板是否一致、检查数据库迁移脚本能否在临时库上跑通。验证器输出的结果会被写回规划上下文,作为下一轮规划的重要输入。这个反馈信号是持续提升代理成功率的关键:模型能看到自己哪一步做错了,才有机会在下一步修正。没有这个闭环,代理就会变成一个"每次都在同样的坑里跌倒"的复读机。我测试下来,在同样的任务集上,加上验证器之后,一次通过率从不足三成提升到了七成以上,这个数字非常直观。3. 从零动手:5 分钟搭一个自动补单测的代理光说架构有点虚,直接上实操。这一节我带大家从空环境开始,搭一个真正能用的编程代理。目标很具体:项目里新增了一个计算模块calc.py,代理需要为这个模块自动生成单元测试,并跑通测试框架。整个过程我都会标注每一步的用途,方便你对照着改。3.1 安装框架与确认环境先确认环境,Python 3.10 以上版本最好。PI 框架本身依赖的包很少,核心就一个异步事件循环加一个 LLM 客户端封装。使用虚拟环境安装:python -m venv .agent-env source .agent-env/bin/activate pip install pi-agent因为编程代理要读写文件、执行测试,我建议在一个隔离的目录里做实验,不要直接在自己的主项目上试。我一般习惯建一个sandbox/文件夹,里面放若干个模拟项目,代理的所有操作都限定在这个沙箱目录中,这样就算它发疯也破坏不了别的东西。安装完后初始化一个代理配置文件:pi init这条命令会生成pi_config.yaml,里面包含了模型接入地址、规划策略、工具注册表等默认配置。我第一次看到生成的文件时有点意外,居然只有四十多行,比某些框架动辄两三百行的配置清爽太多。3.2 注册三个最基础的工具代理要干活,最少需要三个工具:读文件、写文件、跑测试。用 PI 框架的注册方式写一个工具模块tools.py:from pi import register_tool from pathlib import Path @register_tool def read_file(path: str) -> str: """读取项目内文件,path 为相对项目根目录的路径。""" full = Path(project_root) / path return full.read_text(encoding="utf-8") @register_tool def write_file(path: str, content: str) -> str: """写入文件,自动创建父目录,path 为相对项目根目录的路径。""" full = Path(project_root) / path full.parent.mkdir(parents=True, exist_ok=True) full.write_text(content, encoding="utf-8") return f"已写入 {path}" @register_tool def run_tests(test_path: str = "tests") -> str: """执行测试文件,返回完整输出。""" result = subprocess.run( ["pytest", str(Path(project_root) / test_path), "-q"], capture_output=True, text=True, timeout=60, ) return result.stdout + result.stderr这里我想强调一点:read_file和run_tests的描述我都写了路径的相对性,这就是我在前面说的"函数签名即协议"。模型不会自己猜你的路径语义,全靠描述传递信息。描述写得越精确,调用越少翻车。3.3 定义任务,把核心闭环跑起来在agent_demo.py里组装代理:from pi import create_agent agent = create_agent( name="test-writer", endpoint="http://your-llm-service/v1", model="code-gen-model", planner={"strategy": "sequential", "max_steps": 5}, executor={"timeout_seconds": 30, "retry": 2}, ) result = agent.run( task="为 calc.py 中的 Calculator 类生成完整单元测试", workspace="./sandbox/demo_project", ) print(result.summary)跑起来之后,你能在控制台看到代理的完整行动轨迹。我最常用的观测方式是开verbose=True,它会实时打印规划 JSON、工具调用参数、验证结果。第一次跑通的轨迹大概是这样的:规划器输出三步计划:读取calc.py源码,生成test_calc.py,运行测试。执行器调用read_file,拿到Calculator类的方法列表。执行器调用write_file,写入初版测试文件。验证器调用run_tests,发现有两个用例没通过。规划器追加一步修复计划,执行器重写失败的测试断言。再次运行测试,全部通过,流程结束。整个流程不到两分钟。这个示例虽然简单,但已经把闭环走通了。你后面要做更复杂的事情,基本就是往这个框架里不断加工具和验证器。3.4 看一份真实的规划产物长什么样代理执行中,规划器产出的 JSON 大概是这样的:[ { "step": 1, "goal": "读取目标模块源码,了解 Calculator 类接口", "files": ["calc.py"], "tools": ["read_file"], "acceptance": "已取得全部方法和边界条件" }, { "step": 2, "goal": "生成覆盖常规与异常路径的单元测试", "files": ["test_calc.py"], "tools": ["write_file"], "acceptance": "测试包含整数除法、被零除、类型错误用例" }, { "step": 3, "goal": "运行测试并确认全部通过", "files": ["test_calc.py"], "tools": ["run_tests"], "acceptance": "pytest 结果无失败,无语法错误" } ]注意每一步都有acceptance字段,这是验证器判断"这步算不算完成"的基准。如果你的项目有特殊要求,比如"所有测试必须覆盖参数化用例"、"文档字符串必须包含示例",就把这些要求写进acceptance,验证器会按你的标准来,而不是让模型自己给自己打分。4. 决定成败的关键参数与调优手记框架搭起来只是开始,真正决定代理好不好用的,是几个关键参数的设定。这些参数我前前后后调了很多轮,把最影响结果的几条经验放在这里。4.1 最大步数与滚动规划深度max_steps这个参数控制代理最多执行多少步。设得太小,复杂任务根本跑不完;设得太大,低效循环会白白烧掉大量时间。我的一般经验是:简单任务 5 步以内,中等任务 8 到 10 步,大型重构任务建议设到 20 步以上,但同时在验证器里加上"连续 3 次同样的失败必须中止"的规则,防止原地转圈。还有一个隐藏参数叫规划深度,它决定规划器拆解任务时的粒度。深度 1 表示只拆成"读代码、写代码、跑测试"这种粗粒度步骤;深度 3 则会把"写代码"进一步拆成"编写函数骨架、填充业务逻辑、处理边界条件、添加注释"。深度越大,每一步越容易被验证器精确判断,但规划本身的 token 开销也越大。我实测下来的结论:日常任务用深度 2 性价比最高,复杂模块用深度 3。不要动不动就上深度 4,规划链太长之后,模型自己都容易在规划阶段迷失方向。4.2 上下文预算怎么分这是我觉得 PI 框架做得最聪明的地方。它把模型上下文比作一块总预算,明确分给四个部分:系统提示、滚动规划产物、工具执行记录、对话历史。其中,系统提示只占很小头,规划产物是常驻的,工具执行记录只保留最近几轮的完整内容,更早的执行记录会被压缩成一行摘要。我自己常用的分配比例大概是:规划占三成,工具执行记录占四成,对话历史占两成,系统提示一成。如果你发现代理总是"记性不好",忘掉早期规划步骤,通常是规划产物被截断了,这时候要把规划部分的预算调大,或者减少最大步数,让规划产物变短一些。上下文预算的调优没有银弹,我的土办法是打开 trace 日志,观察每轮实际发送给模型的 token 数量。如果发送量超过总预算的八成,基本可以断定代理要开始"胡言乱语"了。这时候优先压缩对话历史,把一轮执行中无关紧要的中间输出裁剪掉。4.3 重试、超时与并发阈值执行器的三个参数我提一下。timeout_seconds是单次工具调用的超时时间,跑测试、调接口这类工具要给足 60 秒以上,读写文件给 10 秒就够;retry是工具调用失败后的重试次数,我建议设 1 到 2,不要超过 3,因为能重试解决的问题一遍就够,不能解决的问题重试十遍也是白搭;max_concurrency是并行工具的数量,当你注册了多个标记为parallel的工具时这个参数才有意义,默认 1 就行,不宜开大,模型同时处理多个工具的上下文很容易串味。这里有个我踩过的坑:给测试工具设了 15 秒超时,但项目变大后测试本来就跑 30 秒,代理连续三次超时后直接判定任务失败删掉了自己写了一半的测试文件。后来我把超时调成 90 秒,并且在验证器里加了"超时不算失败,只算待重试"的规则,问题才解决。所以那些不可控的工具,超时要给足弹性。4.4 模型选型的"组合拳"PI 框架允许每个阶段用不同模型。我的推荐配置是:规划阶段用推理能力强、但速度和价格都偏贵的模型,因为规划错误是最贵的错误,一次规划失误可能导致后面几十步全废;执行和验证阶段用响应快、价格低的模型,因为这两个阶段动作很简单,用不着顶级推理能力。这么一套组合下来,任务成功率能维持在高位,但成本比全程用顶级模型省百分之五六十。如果你是个人开发者,更在乎成本,可以规划阶段也用普通模型,但要在验证器上加严,用更多轮的迭代来修正规划误差。5. 我踩过的坑:高频问题排查实录做编程代理这件事,最大的障碍从来不是模型能力,而是一堆边界情况。这一节我把自己实操中遇到的典型问题整理成速查表,每条都附上排查思路和解决办法。5.1 代理在同一个决策点上反复横跳现象是:代理在执行到某一步时,反复修改同一个文件,每次修改内容差不多,验证始终不通过,但代理不觉得是方向错了,只以为是细节没写对。这种问题我在自动生成测试用例时遇到最频繁,尤其是涉及浮点数断言、编码格式这类容易有隐式误差的场景。排查思路:先看验证器的失败输出是否包含足够信息。很多时候验证失败只有一句assert False,模型根本不知道错在哪。解决办法是在验证工具的描述里明确要求"失败时返回具体断言行、期望值和实际值",这样模型才能有针对性地修复。另外可以考虑在规划器里加规则:同一文件的修改次数超过 N 次,必须触发重新规划,不要继续原地修。5.2 工具明明失败,代理却当成成功继续往下走这个坑非常隐蔽。模型查看工具执行结果时,如果结果文本比较长,模型可能只看了开头几行就判定成功。最常见的是pytest输出前面一大段测试收集日志,真正失败的报错在最后面,模型看了前 500 个字符直接跳过了失败信息。解决办法有两层。第一层,在工具描述里加上"结果字符串以 SUCCESS 或 FAILED 开头"这样的显式约定;第二层,写一个结果摘要工具,对失败的输出做二次提炼,只把关键错误行返回给模型。我自己是把测试工具的输出做了后处理,把测试摘要压缩成"通过数、失败数、失败用例名列表"三行,模型误判的情况立刻少了很多。5.3 上下文越用越长,代理行为越退化这个问题在对话式 Agent 里几乎必然出现。前几轮代理还很清醒,后面逐渐开始答非所问、重复操作,日志里 token 消耗量也在暴涨。本质是上下文里塞了太多无关紧要的历史记录,模型被噪声淹没。排查思路是看每一轮发送量。PI 框架本身有摘要压缩机制,但如果你打开了 verbose 日志,发现某个轮次的输入 token 数是前几轮的几倍,就要检查是不是有工具返回了超大文本。比如read_file默认返回整个文件内容,一个上千行的文件会直接吃掉大量预算。我的做法是给read_file加max_chars参数,超过后只返回文件的前缀和行号索引,代理需要哪一段再单独按行号去读,省预算又省时间。5.4 给代理的权限一定要有边界最后是安全敬畏。我第一次做自动化时,给代理挂了一个可以执行任意 shell 命令的通用工具,结果它在一次数据库迁移测试里把沙箱环境的临时表数据清掉了,虽然没有真实影响,但也吓得我再不敢放开权限。PI 框架没有很复杂的权限系统,它的哲学是"少给工具,多写验证"。我的习惯是:每个任务只挂绝对必要的工具;通用 shell 执行工具永远不挂,需要跑脚本就写专门的工具,参数白名单验证;所有写文件操作强制限定在 workspace 目录内;涉及删除的操作必须额外确认一次。这些边界规则写进系统提示是不够的,必须在工具函数内部用代码强制,因为模型在压力下真的会忘。6. 后续还能怎么玩:从单代理到多代理到这一步,单代理的闭环你已经能完全掌握了。再往深走,PI 框架支持把多个代理编排成一个小组,这在实际项目里非常有价值。我最近在做的一个内部工具就是这样的结构:一个管理者代理负责接收需求、拆分任务、派发结果;两个执行代理分工,一个专门处理代码生成,一个专门处理代码审查;还有一个测试代理负责跑回归。管理者代理的规划器会把任务分配出去,执行结果汇总回来后再做合并决策。每个代理共享同一套验证器,但各自的工具集完全隔离。这种多代理结构的好处不只是并行,更重要的是各代理可以用不同的模型承担不同的角色。审查代理如果和生成代理用同一个模型,容易出现"自己写的代码自己看不出问题"的盲区;用不同的模型,反而能交叉验证,抓出不少低级失误。关于扩展,还有几个我打算尝试的方向。一是把验证器的结果沉淀到日志里,积累一段时间就能统计出代理最常失败的工具和场景,有针对性地优化工具描述;二是对接项目管理系统,把任务描述直接拉进来作为代理输入,结果再写回去,形成一套半自动的开发辅助链路;三是实验外置的知识库,让代理在规划阶段先检索团队历史代码规范,让生成的代码从第一个版本开始就更贴近团队的约定。总的来说,PI 框架这个工具的定位很清晰:它不替你思考,但帮你把思考过程规范化、流程化,让 AI 编程代理从"碰运气"变成"走流程"。在这个框架上花的每一分钟,都会沉淀成可复用的工具、规则和调优经验,而不是打水漂。