上周末在电梯里碰到隔壁组的同事他手机屏幕上全是终端里滚动的日志我随口问了一句又在刷什么他头也没抬地说pi跑起来了。我以为又是什么树莓派周边项目后来才发现他说的不是树莓派而是一个叫pi的开源编码智能体coding agent。那时候我在社区里已经看到好几轮关于pi agent和pi coding agent的讨论了但一直没动手。周末花了两天把它装好、跑通又用了一周时间把它塞进手头一个正在改造的内部工具项目里才敢写这篇东西。这不是评测是一份实测笔记重点说清楚pi解决什么问题、怎么配、能干活到什么程度以及那些文档里不会写的坑。1. PI到底是什么三个本质特征让我决定试它1.1 编程助手已经卷到了第三代如果从工作方式看编程助手大概分了三代。第一代是补全型你写一半它给你补后半截核心价值是省敲键盘的时间第二代是对话型你问它一段代码怎么写它给你一段答案核心价值是省搜索时间第三代就是agent型你给它一个任务它自己读代码、自己动手改、自己跑测试、再根据失败信息自己修核心价值是省盯过程的时间。pi明显属于第三代。但第三代这条赛道里已经挤了很多人为什么pi还被反复提起我试下来感觉有三个特征比较突出轻量、终端优先、可组合。轻量是指它本身不是一个巨大的IDE套件安装完就是一个命令行工具核心是一个调度引擎。你给它模型访问密钥它负责编排读写文件的动作调用外部的git、测试、构建命令。说白了它是一个把大模型接进本地仓库的中介而不是又一个全家桶软件。终端优先这件事很关键。现在很多agent都嵌在IDE里好处是可视化但坏处是你要一直开着IDE。而pi的思路是开发者的家就在终端你已经有git、有测试、有构建工具pi只是多一个能自己操作这些工具的角色。这个定位让它在脚本化场景、CI环境、远程开发机上都很容易接进去。我后来试过在远程开发机里跑它体验跟在本地几乎没有差别。可组合则体现在它不绑定某一个模型。它做的是路由复杂任务让强模型来规划简单执行步骤可以换便宜快速的模型。这意味着它不像某些工具那样把你锁死在一个模型生态里你可以根据任务类型灵活替换。我后面会专门讲模型路由的配置那是我觉得它最值得研究的部分。1.2 为什么说能在终端里闭环比能改代码更重要很多人第一次用这类工具时有个误解觉得agent不就是自动改代码吗其实改代码只是其中一环。真正拉开差距的是闭环能力——你把任务交给它之后它能不能自己发现改坏了然后自己修。pi的做法是每做一步操作都会把命令的输出比如测试结果、lint报错、git diff重新作为上下文喂给模型让它判断下一步。也就是说它不是在蒙头写而是在一个试错-反馈-再试的循环里工作。我举个实测中的例子。我让它给一个Python脚本的argparse部分加一个--dry-run参数。它拆分出的步骤大概是先读入口文件定位argparse初始化代码添加一个新参数找到真正执行逻辑的分支加上判空条件最后跑一遍现有测试确认没破坏别的功能。这个拆解质量其实比我想象的高。而且最关键的是如果测试挂了它不是停下来等我而是会自己去看测试输出判断是参数没接上还是逻辑分支写错了再继续改。这种从执行结果中学习的能力是把一个聊天工具变成干活助手的分水岭。聊天工具给你的是一段代码而它给你的是一个已经跑过验证的结果。1.3 和几类常见coding agent形态的直观对比为了说清它的定位我把自己用过的几种形态做了个对比。这里的形态是指工作方式不是具体某个商业产品大家按类型去理解更准确。形态交互场所能否自主执行命令多文件改动适合场景补全型IDE编辑器不能弱写单行、补函数对话型网页/聊天窗不能弱问思路、要片段IDE内嵌agentIDE侧边栏/对话框部分可以中单仓库内半自动改终端类agentpi终端/命令行可以强多文件、跨步骤、需要验证闭环这个表其实就是我决策的原因我大量时间在终端里维护一个历史包袱很重的老项目需要的是能自主执行、能验证、能处理多文件改动的工具而不是另一个聊天窗口。后来跑完一整个流程这个判断基本被验证了。当然它也带来了新问题——自主性越强翻车画面越壮观这个放到第四节。2. 上手准备装好环境、配好密钥、先让它只读跑一遍2.1 安装环境Node版本和nvm切换是第一个坑pi作为一个命令行工具安装本身不复杂我用的是npm全局安装的方式装完输入pi --version就能看到版本。但我在安装时确实踩了一个环境坑它要求Node 20以上而我系统默认Node是18。这个问题大家可能觉得不算事切换一下就行但实际nvm的默认版本和shell配置经常不一致。我当时执行安装命令时shell里nvm默认指向的是18所以第一遍安装就报了引擎不兼容的警告。我当时没细看继续用结果pi启动时直接起不来报错信息指向一个很底层的语法错误我后来才反应到是平台版本太低。解决办法很笨但很有效在项目目录下放一个.nvmrc文件固定Node版本然后用nvm use切过去再重新安装。这个教训也提醒我任何要求运行时版本的CLI工具第一件事先用--version确认环境别被shell的假象骗了。如果你用的是其他包管理器比如homebrew或者直接下载二进制思路是一样的——先确认运行时的真实版本再谈后面的事。2.2 密钥策略给PI单独配一把钥匙别和日常开发混用pi本身没有模型它需要调用大模型API来干活。所以配置的核心就是给它一个模型服务的访问密钥。这一步我强烈建议单独申请一个新的密钥不要用你平时开发插件里的那个。原因很实际agent跑起来以后token消耗速度远超你手动聊天。我见过一个同事用共享密钥跑了一个大改任务十几分钟就把当天限额烧掉大半当天下午他正常开发时老是报限流。所以给pi配独立密钥本质上是把高频实验消耗和日常开发消耗隔离开省得互相拖累。配置方式我建议走环境变量而不是写进项目配置文件。项目配置文件容易在多人协作时误传到仓库里环境变量只影响当前终端会话安全性高得多。我在.zshrc里加了一行export然后每次用pi之前先确认变量能打印出来。还有一个细节如果你的模型服务商提供了不同的访问地址记得也通过环境变量指定别让默认地址去请求一个不存在的入口。这一步看起来基础但很多人第一次跑不通都是栽在密钥看起来配置了但请求发到了另一个端点这种问题上。2.3 第一次运行选一个陌生但结构规范的仓库让它做只读体检第一次用pi我最建议的任务不是直接让它改代码而是让它做一个只读的仓库体检——读项目结构、理解构建方式、归纳模块分工。这样你能在最低风险下观察它的行为模式。我给它的指令是不要修改任何文件只阅读然后告诉我这个项目怎么构建、入口在哪、主要模块有哪些。它会先扫描目录重点看README、package.json这类清单文件再决定读哪些源码而不是把所有文件从头到尾读一遍。这个挑文件读的判断力比多数人想象的重要。它决定了在仓库较大的时候agent能不能在有限的上下文窗口里抓住重点。我拿一个中型React项目试输出里对构建命令、入口文件、路由分层的归纳都比较准确没有胡编。这一步让我对它的基础能力建立了初步信任。如果你发现它在阅读阶段就开始胡编结构那后面改代码的阶段大概率更不可靠趁早换模型或者检查配置。反过来如果只读体检的输出质量不错那这个信号通常是靠谱的。2.4 一个初始化坑配置文件差点被提交进版本库pi首次在项目里运行会在根目录生成一个配置文件里面记录了模型路由、审批级别、忽略规则等。问题在于它生成的默认.gitignore只忽略了一些常见的日志目录并没有把这个配置文件加进去。我第一遍跑demo的时候没注意后来准备提交代码时git status里出现了这个文件。万一里面写了密钥或者内网地址这就算一个不大不小的安全事故了。所以我现在的习惯是pi init之后第一步就去翻一遍生成的配置文件检查有没有敏感信息然后手动把它加进项目的.gitignore。这类工具越智能它产生的元数据越多这些给工具用的文件如果混进版本库后面维护的人会很头疼。别觉得这是小事我同事就因为在PR里带进过一个agent的会话日志被review的人追着问了好几轮最后还得清理历史记录才能消停。3. 核心工作流拆解计划-执行-验证循环是怎么转起来的3.1 它收到任务后第一件事不是写代码而是写计划这是pi和那种你发一段代码它就给你改一段的聊天助手的最大区别。拿到任务后它不会直接动手改文件而是先输出一份计划通常包含涉及的文件、要做的改动点、用什么手段验证。你可以在此时打断它、修正方向再让它继续。这个设计我觉得非常聪明——它把agent的思考过程透明化了让你有机会在它犯大错之前截住它。我实测过一个任务给旧项目加一个简单的重试逻辑。它的计划大概是三步找出所有外部HTTP调用点、在公共请求封装层加retry、写一个mock测试验证。如果它直接把代码改完再告诉我好了我大概率还得自己去diff里找它到底动了哪里现在反而省事。你甚至可以把这份计划当成code review的第一个产物来看待。我后面实战时养成的习惯是先只看计划确认方向对再让它执行省下大量返工时间。如果计划本身就不对直接让它停下来比事后review一堆代码省力得多。3.2 执行循环里的自纠错报错信息反过来成为新的上下文计划的下一步是执行。pi的执行不是一次性把补丁全打上而是一步一步来。每执行一条命令它都会把输出结果拼接回自己的上下文然后判断这个结果符合预期吗如果报错错误信息本身就是下一轮决策的输入。这个自纠错能力是它真正干活的底气。它不指望一次改对而是通过改-跑-看结果-再改这个循环自己逼近正确结果。我观察到它有几种典型的纠错路径。一种是测试失败后它会读失败堆栈定位到是测试断言写错了还是实现确实有问题二是lint报错后它会直接修掉格式问题再重新跑三是命令本身失败了比如包安装失败它会换一种方式重试。但是——这里有个重要的但是——它重试的前提是报错信息足够典型。凡是那种需要隐性知识才能解决的错误它容易陷入无效重试比如明明下载超时导致的失败它会反复修改代码而不是检查网络源。这个特点在后面实战部分让我血压升高过一次。遇到这种情况别等它自己绕出来直接打断效率最高。3.3 写操作为什么是生成patch而不是直接覆盖文件我注意到的另一个设计细节是pi对文件的修改不是整文件覆盖而是生成一个patch补丁类似你在代码评审工具里看到的那种diff。这个设计的好处有三个第一个是上下文开销小它只需要把改动片段和上下文附近的内容传给模型不需要重发整个大文件第二个是diff可读性好你可以清楚地看到它每一步改了什么review门槛低第三个是可回滚如果一段时间后发现某个改动有问题git层面就能处理。对使用者来说这个设计的意义是你能在它执行过程中实时围观而不是等它全部做完之后面对一个黑盒结果。我通常会开一个终端窗口专门跑pi另开一个窗口随时git diff看到不对劲就打断。这种人类审批机器执行的组合才是这类工具使用体验的核心。它干活的过程是可见的、可干预的这一点比全自动一次到位重要得多因为现阶段没有任何agent能做到一次到位还不需要复查。3.4 审批等级调到什么程度取决于你对它的信任值pi提供了不同的审批级别我记忆里大概是全自动执行、写操作前询问、危险命令前询问、完全只读。默认一般是写操作前询问。刚上手的时候我建议先别开全自动哪怕你觉得计划已经看过了——因为计划归计划执行过程中它可能突然决定去安装一个依赖包或者对一个文件做超出预期的改动。我给自己的规则是第一天全程开最高审批第二天改成写操作前询问等摸清它在什么场景下可靠再考虑放开到危险命令前询问。所谓危险命令大概包括强制删除、直接push远端、批量替换文件等。这些动作一旦出错破坏面比较大。我的看法是审批级别本质上是信任管理不必追求极致自动化。这类agent的价值是把你从敲代码中解放出来而不是把你从对自己的仓库负责中解放出来。该你盯的风险换成再强的agent也要盯。你越早建立这个心态用它的时候就越不容易翻大车。4. 真实任务实录给内部工具加批量导出功能并记录三次翻车4.1 任务简报我给PI的原始prompt长什么样理论说了这么多得上一个完整实例。我拿手头一个内部工具做实验技术栈是前端React、后端Go后端直接暴露JSON接口前端目前只有单条数据的查看我需要加一个批量导出Excel的功能。我给pi的prompt大概是这个意思项目入口在cmd/server/main.go前端在web/目录。需要新增一个POST /api/export接口接收产品ID列表用已引入的excel库生成xlsx返回下载链接。同时在前端页面加一个导出按钮导出当前筛选条件下的所有ID。完成后跑后端测试和前端build确保通过。这里我特意把约束写得很清楚接口路径、字段来源、允许使用的库、验证方式。因为你给agent的上下文越明确它计划的质量就越高。你当然可以让它自己去读代码发现一切但那样它会做很多无效探索token消耗也大。我一般的原则是从需求到接口设计的决策我出从接口到代码实现的翻译工作它做。这个分工不一定适合所有人但对我这种老项目来说效果最好。4.2 第一段顺利后端API和字段映射一次通过后端部分它完成得很快。它先读了cmd/server/main.go里现有的路由注册方式仿照已有的handler风格新增了一个handler然后找到现有的数据模型定义把导出字段一一对应好最后跑通了测试。整个过程大概几分钟没有出现编译错误。我diff了一下它的改动结构和项目里既有的handler几乎一致包括错误处理和日志格式都对齐了现有风格。这一点很让我意外因为它说明它真的在模仿这个项目的既有约定而不是输出一套通用模板。对老项目来说跟着现有风格走比功能正确还要重要因为风格不一致的代码进来review成本极高而且慢慢地会把项目变成四不像。4.3 第一次翻车前端依赖装错三次它不读报错开头真正的问题出在前端部分。导出功能需要用到前端Excel生成库但项目之前并没有引入相关依赖。pi决定自己安装然后它连着失败了三次。它每次失败后都能看到命令输出但它似乎没仔细看报错的第一行——报错信息其实是npm的registry地址请求失败因为公司网络环境需要走内部源。它却理解为可能是版本不存在于是换了一个版本号继续装结果当然还是失败。我介入的时候它在终端里已经来回滚了十几屏日志。我做的事情很简单手动把registry换到内部源然后让它重新执行。它立刻安装成功。这个事给我的教训很直接agent擅长处理代码逻辑错误但不太擅长处理环境配置错误。环境类问题往往需要的是我知道这里有个看不见的约定而这种约定它不知道。所以后来我凡是知道项目里有特殊网络环境的都会提前在配置里带上内部源信息而不是让它现场摸索。这也算是我第一次意识到用agent其实是在教一个聪明但没经验的实习生干活——你得把环境常识提前告诉它。4.4 第二次翻车它不认识部门内部的命名规范第二处翻车不那么明显但更值得警惕。它生成的导出文件名是export.xlsx字段名直接用了数据库里的英文名。这个按功能标准是完全能用的但我们内部约定导出文件的命名要包含日期和类型字段头要换成中文显示名。这些约定不在代码里不在文档里而是这个团队的老规矩。agent当然不可能凭空知道。它交出来的东西不是错的而是不符合项目语境的。我没让它改第二遍因为我觉得这个例子特别适合说明人工review的价值。它给你的不是错的代码而是符合通用规范但不符合项目语境的代码。如果你闭着眼睛接受它的输出代码能跑但团队协作成本会上升。正确的用法是把这些约定写进约束里或者像老带新的方式发现它违反约定后把规则补充进说明让它下次注意。我后来会在config的constraints字段里把命名规则、目录结构、接口风格这些隐性约定一条条列出来效果立竿见影。4.5 第三次翻车跨文件改动时它丢了前一个文件的上下文第三个问题发生在一次比较大的改动里。我希望它在导出功能的同时给列表页加一个筛选条件并且把筛选结果同步到导出接口。这个任务涉及两个前端文件、一个后端handler和一个路由定义。它做到第三个文件的时候明显开始忘事——它把筛选参数写在了前端请求里但后端接口处理时却用了默认值等于参数传了但没用。我看了它的会话上下文判断是它中间做了太多轮文件读取早期计划里的参数约定被后来的上下文挤掉了。这个问题的本质是上下文窗口的物理限制。解决方式有两种一是拆任务把一个大任务拆成几个小任务每个小任务之间用明确的结果衔接二是在prompt里把关键约定写成一个约束清单并在任务中点名让它遵守。我后来负责大改动时基本都会把约束单独放一段不跟任务描述混在一起。这个小改动显著降低了它遗忘的概率。现在我对任何跨三个文件以上的任务第一反应都是先想怎么拆而不是让它一口气做完。4.6 人机分工的复盘哪些环节省时间哪些必须人盯一个完整任务跑下来我的体感是写样板代码、字段映射、补齐单元测试这些环节它确实省时间需要环境约定、团队规范、跨模块隐性依赖的地方它需要人提前喂规则或者事后兜底。我总结了一张分工表基本就是我后来每次用它时的默认心态环节PI表现人工介入程度读项目结构、找入口高效低写CRUD、接口、字段处理高效低模仿现有代码风格较好中需review diff安装依赖/处理环境问题容易无效重试高提前配置或盯识别团队隐性约定差高写进约束或人工改跨多文件大任务上下文会丢中拆任务自测与补测试较好中看覆盖是否够这张表不是想说它不行而是想说每个环节的投入产出比不一样。你把约束提前写清楚、把环境问题提前配置好它就能把大量时间花在真正写代码上你如果什么都不管直接扔任务那你的兜底成本也低不了。工具是放大器你准备的上下文质量决定了放大的是价值还是风险。5. 社区热词背后的配置学问模型路由、审批级别与安全红线5.1 先说热词本身pi、pi agent、pi coding agent大家在讨论什么最近社区里围绕它的讨论热词有好几个pi、pi agent、pi coding agent偶尔还有看起来像梗的写法。其实核心都在说同一件事把编码智能体跑起来。pi是这个工具的短名pi agent指的是以pi为内核的智能体工作方式pi coding agent则更完整地强调它是面向编码场景的agent。你在技术讨论里看到这几个词完全可以当一个意思理解不必被拼写差异弄晕。我更想聊的是这个词背后的关注点为什么一个终端里的编码agent能成为热词我猜是因为它代表了一种轻量、可编排、模型无关的路线。相比把agent绑死在某个IDE或特定模型生态里像pi这种命令行工具更适合被脚本化、被CI调用、被个人定制。社区的热度本质上是大家在寻找一种更自由、更透明的agent使用方式。你可以把它塞进自己的构建脚本里也可以跟别的工具组合成一套更复杂的自动化流水线这种自由度是IDE内嵌agent很难给的。5.2 模型路由规划用贵模型执行用便宜模型模型路由是pi这类agent最有意思的优化点。因为它不是绑定单一模型而是可以在不同阶段把任务分给不同模型。我自己的配置逻辑是规划阶段读计划、拆任务用更强更贵的大模型因为它要做推理和判断执行阶段生成patch、修格式、写单元测试用便宜快速的模型因为这个环节模式固定、上下文明确。这样既保证决策质量又控制成本。我实测下来的效果一次中型重构任务模型路由策略能比全程用高端模型省差不多三分之一的token费用而最终代码质量没有明显差异。当然这个比例和你的任务类型强相关。如果你的任务全是高难度推理就别省规划模型的成本如果你大量时间花在机械性的CRUD上路由策略的收益会非常明显。配置的时候留意一下路由规则字段不同模型名要写对我第一次就写错了一个模型别名结果请求直接报错折腾了十几分钟才反应过来是名字大小写的问题。5.3 工具白名单限制它能执行的外部命令另一个容易被忽略的配置是工具白名单。pi可以调用外部命令去完成工作但可以调用不等于应该都能调用。我建议把白名单收窄到你项目实际会用到的工具上比如git、npm、pytest、go test这些。这样做的最大好处是压缩安全面即使某个任务的prompt被注入恶意内容agent能执行的命令范围也是受限的破坏力有限。这不是什么玄学而是很实在的防御策略。我见过有人把白名单配得极宽结果一次处理第三方库代码时agent自作主张执行了一个未知脚本。虽然最后没出事但这个场景吓出我一身冷汗。任何允许agent自主执行命令的工具都应该遵循最小权限原则这和给开发机开账号是一个道理。配置不复杂就是在一个配置文件里列出允许的命令前缀。重要的是你有这个意识而不是默认全开。我现在的规则是只有明确需要跑测试、装依赖、看diff的时候才会临时把对应工具加进白名单平时保持一个很窄的集合。5.4 安全红线三样东西永远别放进agent可见的目录最后说安全。我给自己定了几条底线可能对你有参考价值。第一生产环境的密钥、数据库连接串、云平台凭证永远不要放在agent会扫描的项目目录里因为agent读取文件后会把内容作为上下文发送给模型服务这等于把敏感信息发到了外部。第二不要在公网仓库或多人仓库里直接跑高权限agent任务尽量在本地分支或隔离环境跑。第三涉及大规模删除、覆盖、push到共享分支的操作不建议放进自动化流程即使agent能力再强这类操作的人工确认也不能省。这不是什么高深的安全理论就是基本的风险隔离习惯。用agent和用其他工具没有任何区别知道它可能做什么就知道自己该防什么。配置层面把这些限制写清楚运行层面保持人工监督基本能把风险控制在可接受范围内。我见过有人因为嫌审批弹窗烦直接开了全自动模式结果agent在未确认的情况下删了一个数据库视图的迁移脚本虽然能恢复但当时的惊吓程度足够让我记住这个教训。6. 一周实测后的使用建议什么项目适合PI什么项目别硬塞6.1 适合PI的三种任务画像用了一周我对什么任务适合它的判断比第一天清晰多了。第一种是结构清晰的中小型特性开发加接口、加页面、加字段、改逻辑分支这类任务边界明确验证方式简单它完成度和速度都很高。第二种是跨文件的机械性改动比如统一改日志格式、替换过时的API调用、批量增加参数校验这类任务靠真人的话又烦又容易漏它反而能做到系统性替换。第三种是测试补全型让它在已有函数上补单元测试和边界测试它能快速模仿现有测试风格把覆盖率撑起来。这三种画像的共同点是需求可以写清楚验证可以由测试给出结论。也就是说让agent干活的前提是你自己有能力判定干对了。如果你连判断标准都没有那agent给出的结果你也没法信任。我一开始最大的误区就是总期待它给我惊喜后来才发现它最擅长的恰恰是把那些你已经想得很明白、只是懒得敲的代码给写出来。6.2 不太适合PI的三种情况反过来也有几种情况我建议别硬塞。第一种是历史包袱极重、文档全无的老系统我试着让它读一个十年前的老模块它理解项目结构的时间太长了效果不如人先看。第二种是强依赖产品判断的改动——比如交互流程怎么变、文案怎么调、边界条件怎么定义这些不是代码问题是产品决策问题agent帮不上忙。第三种是环境极度特殊的场景私有网络、特定硬件、特殊编译链agent的知识覆盖不到我在实战里已经领教过一次它会在环境错误上反复空转。我的经验是如果一个任务连熟练的人类开发都需要先问三个问题才能动手那大概率也不适合直接扔给agent。agent适合的是人类已经清楚知道怎么做、只是不想亲手做的任务而不是人类也不知道怎么做、想让它探索的任务。把探索性任务托付给它短期内看是省事长期看会让你的项目积累一堆未经思考的代码。这个边界越早摸清你对它的期望就越合理。6.3 我的配置参考一个可直接抄的config文件示例最后放一份我当前用的配置骨架给你一个参考起点。注意不同版本字段可能有差异但思路是通用的。{ model_route: { plan: strong-model, execute: fast-model }, approval: write, allowed_tools: [git, npm, go, go test, pytest, node], ignore_paths: [node_modules, dist, .git], session_max_turns: 30, constraints: [ 不修改公共配置文件, 不在未确认的情况下执行npm install -g, 所有新增代码必须遵循项目现有命名风格 ] }几个关键字段的解释approval设置成write表示写文件前要确认这是我对多数项目的默认信任级别allowed_tools是白名单控制它能跑的命令session_max_turns是单次任务的最大轮数防止它在某个死胡同里无限转圈。constraints这段是我的习惯把团队的隐性约定直接变成它的硬约束。配置好后我一般会跑一个只读任务验证路由和工具配置都正常再开始干正事。这个习惯帮我避免了两次因为模型名写错而白跑任务的情况。最后再说点个人体会。我用pi这几天最大的感受不是它替我写了多少代码而是它让我重新思考了什么叫把话说清楚。以前给同事交接任务时我总默认对方懂得项目里的各种约定但给agent布置任务时那些默认全都不存在我必须把路径、约束、验证方式都说清楚。这个过程本身就在帮我梳理自己脑子里到底哪些是明确需求、哪些是模糊感觉。至于它是不是完美我觉得现阶段不用期待一个万能编码机器人把它当成一个执行力强、但需要你把需求背景喂饱的结对同事反而最接近它的真实样子。想把一个工具用好最重要的可能就是知道它的边界在哪里。