0%

把 AI 编码工具改造成研发工作台——基于 Pi 的 Extension 实践

最近几个月,AI 编码工具已经成了日常开发的一部分。Claude Code、Cursor、Copilot——它们在代码生成和重构上的表现越来越好。

但用得越久,越觉得有一个断层:AI 在终端里帮我写代码,但我自己却要频繁切出终端去做别的事。查数据库切到 DataGrip,看日志 SSH 上服务器,构造测试数据打开另一个脚本。AI 离我的实际工作环境,其实还很远。

这个问题让我开始想一件事:能不能让 AI 编码工具直接触达数据库、服务器、日志——把它从一个「代码助手」变成一个真正的「研发工作台」?


一、一个典型的排查场景

假设 QA 提了一个 bug:订单状态在某些情况下没有正确更新。

这时候我们需要放下手上的代码,开始排查:

1
2
3
4
1. 切到 DataGrip → SELECT * FROM orders WHERE id = 12345
2. 看到 user_id = 67890 → SELECT * FROM users WHERE id = 67890
3. 想查这个用户相关的操作记录 → SSH 上服务器 → tail -n 500 /opt/logs/app.log | grep 67890
4. 日志里有一条可疑的 MQ 消息 → 切回代码,定位到消费者逻辑

整个过程,我切换了 3 次工具。每次切换都是一次心流中断。

AI 编码工具能帮我写代码、重构代码,但它碰不到第 1、2、3 步——它不知道数据库里有什么,不知道日志里写了什么。它只能基于我告诉它的信息来推理。而「告诉它」这个动作本身,又回到了手动查数据的老路。

所以核心问题不是 AI 不够聪明,而是 AI 的工具集太窄。它只有读文件、写文件、执行 bash。它需要一套能连接到实际开发环境的工具箱。


二、我想要的研发工作台长什么样

在动手之前,我先想了一下理想状态。不是一个「更强的 AI」,而是一个 AI 能触达所有日常工具的环境。

大概是这样:数据库查询就在终端里,写 SQL → 出结果,不用切 DataGrip;日志搜索直接 tail 远程日志文件,不用手动 SSH;跳板机自动穿透,AI 的 bash 能落到目标机器上;测试数据读表结构自动生成,不用手写 INSERT;接口 Mock 本地起一个 mock server,描述接口即用。

这些东西本身都不是什么新奇工具——DataGrip、Kibana、Postman、Mockoon 都能做。问题在于它们都在不同的窗口里。 我想要的不是一个更好的数据库客户端或更好的日志工具,而是把它们都装进同一个终端——那个 AI 已经在帮我写代码的终端。

当这些能力汇聚到同一个终端后,AI 编码 agent 手里就有了研发流程需要的全部工具箱。排查问题时,查数据库定位数据、查日志追踪链路、改代码修 bug,整个过程在同一次对话中完成。不是「AI 帮我写代码,我出去查数据」,而是「AI 和我一起,在完整的工具环境里工作」。

所以我需要的不是一个「功能更多的 AI」,而是一个能让我自己给它装工具的 AI 编码助手。这导向了技术选型:用 Pi

这篇文章先讲数据库查询——它是研发工作台的核心能力,也是最能验证这套思路是否可行的试金石。日志、SSH、Mock 等能力后续再展开。


三、为什么是 Pi

Pi 是一个极简的 AI 编码 harness(代理框架——连接 AI 模型和文件系统、Shell 等操作系统工具的中间层),MIT 开源。如果你用过 Claude Code,你可能对这些东西有印象——子代理自动拆分任务、计划模式先规划再执行、权限弹窗每次确认。Pi 把这些都去掉了。这些功能在通用编码场景下很有用,但对于一个「我只想快速查数据、看日志」的工作台来说,它们反而增加了摩擦。Pi 的核心只提供 readwriteeditbash 四个工具,其他一切通过 extension 自己造。

Extension API 很克制:

1
2
3
4
5
export default function (pi: ExtensionAPI) {
pi.registerTool({...}); // 注册 AI 可调用的工具
pi.registerCommand({...}); // 注册 / 命令
pi.on("tool_call", ...); // 拦截工具调用
}

我需要的是一个干净的画布,不是一艘已经装了 80% 我用不上的功能的宇宙飞船。Pi 刚好是前者。

这次我选择先实现数据库查询。它是研发工作台的核心能力,也是最能验证 Pi extension 机制是否足够灵活的试金石。


四、数据库工作区:一个 /db 命令就够了

实现的 extension 叫 devops-toolsGitHub),入口是一个 /db 命令:

1
2
3
4
5
6
7
8
9
/db                     显示工作区面板
/db switch 选择环境 → 连接 → 数据库
/db tables 列出所有表
/db schema <表名> 查看表结构
/db query [表名|SQL] 执行查询
/db history [关键词] 搜索查询历史
/db favorite 管理收藏的 SQL 模板
/db relations 管理表关联关系
/db refresh-schema 刷新表结构缓存

所有状态跨会话保持——切换数据库后下次启动 Pi 还是同一个库。查询历史、收藏的 SQL、注册的表关系都存在本地 SQLite 里。

下面挑几个关键的设计决策展开。


五、命令,而不是 Tool——为什么不让 AI 生成 SQL

Pi 的 extension 可以注册两种东西:registerTool() 让 AI 主动调用,registerCommand() 让用户手动输入 / 命令。

我的数据库查询只注册了命令,没有注册 tool。

为什么?因为 SQL 的表达效率远高于自然语言。

维度 自然语言 SQL
精度 “查一下最近注册的活跃用户” — 什么叫"最近"?“活跃”? WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) AND status = 'active'
往返 描述 → AI 生成 SQL → 不对 → 修正 → 再生成 一次写完,一次执行
上下文成本 加载 schema + 描述意图 + AI 推理 脑子里就有表结构

对于一个熟悉自己数据库的工程师来说,写 SELECT * FROM orders WHERE user_id = 123 LIMIT 20 比跟 AI 描述「帮我查一下这个用户的订单」更快、更准、更省 token。

这背后是一条更通用的原则:确定性操作走命令,构造性操作走 tool。

  • 数据库查询、日志搜索——人比 AI 更清楚要查什么,自然语言只是在人和目标之间插入了一个可能出错的翻译层
  • 测试数据构造、接口 Mock——「造 50 条真实用户数据」、「mock 一个这样的接口」反而比手写脚本更快,因为人不需要精确描述每一列的值

这个区分对后续扩展其他工具也有指导意义:不是所有能力都适合交给 AI 自主调用,有些能力保持「人直接操作」反而更高效。

不过,「人直接操作」不意味着 AI 就碰不到查询结果。

/db query 执行后,查询结果会自动追加到当前会话的上下文消息中,AI 是可以看到这些数据的。 这意味着你可以先自己写 SQL 把数据查出来,然后直接基于结果和 AI 对话——「这批订单里 status = pending 的帮我分析一下原因」、「根据这个查询结果帮我构造一条测试数据」——数据你查,推理 AI 做,各司其职。


六、安全策略:强制执行,而不是依赖 AI 自觉

数据库查询有一个底线:只能读,不能写。 这条规则不能靠 AI 自觉。

我的做法是把安全拆成三层,每一层都是强制执行的,没有绕过路径:

第一层:SQL 白名单。 所有查询必须匹配 /^(SELECT|SHOW|DESCRIBE|EXPLAIN)\b/i,其他语句直接拒绝。这个检查不在命令层做——命令层只负责交互,不负责安全。真正的检查在查询执行引擎的唯一入口处强制执行。

第二层:LIMIT 自动注入。 无 LIMIT 的 SELECT 自动追加 LIMIT 100(可按连接配置)。SELECT * FROM users 永远不会全表扫描。追加后的最终 SQL 会原样展示给用户——自动加上的 LIMIT 是可感知的,不是隐藏行为。

第三层:密码保护。 数据库密码用 ${ENV_VAR} 占位符,启动时从环境变量替换。真实密码不落盘。

安全判断放在执行层而非命令层,是一个刻意的设计选择——不管你从哪个入口发起的查询,它都会经过同一道门。不存在"命令层忘了检查"的可能性。


七、自动关联查询:让表关系在查询时生效

多数业务系统已经不设物理外键了——性能考虑、分库分表、历史债务,原因很多。但表之间的逻辑关系仍然存在:orders.user_id 指向 users.idorder_items.order_id 指向 orders.id

这引出了一个常见的开发场景:我查一张表,往往需要同时看几张关联表的数据。

传统做法是手动写 JOIN 或者查完一张再查另一张。但在一个你每天都用的数据库上,每次都要手工拼这些关联关系,很繁琐。而且 JOIN 把所有数据拉平成一张宽表,读起来不直观。

/db relations 子命令解决了这个问题。你只需要注册一次表之间的关系:

1
2
3
4
/db relations add
→ 选择源表:orders,选择源列:user_id
→ 选择关联表:users,选择关联列:id
→ 关系类型:MANY_TO_ONE

之后查询时选择"查询关联表",它就会沿着你注册的关系自动联查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/db query orders (选择关联表)

结果:
═══ 查询 — order_db ═══
SQL:SELECT * FROM orders LIMIT 100
行数:12(0.023s)

| id | user_id | amount | status |
|----|---------|--------|----------|
| 1 | 101 | 99.00 | paid |
| 2 | 102 | 150.00 | pending |
...

────── 关联表 ──────

### order_db.users
关联路径:orders.user_id → users.id
行数:2(0.012s)

| id | name | email |
|-----|-------|-----------------|
| 101 | 张三 | zs@example.com |
| 102 | 李四 | ls@example.com |

这比手写 JOIN 直观得多——主表和关联表分开展示,关联路径一目了然。

关系的来源有三种:你可以手动注册(/db relations add),也可以从 MySQL 的 information_schema 自动发现残留的物理外键(/db relations discover),还可以让 AI 分析数据库的 Mermaid ER 图,补充那些靠列名约定隐含的关系(比如所有叫 user_id 的列大概率指向 users.id)。


八、几个让体验更顺滑的细节

Schema 缓存。 远程数据库查 information_schema 可能要几秒到十几秒。加了一层本地 JSON 缓存后,切换数据库秒开。缓存不自动过期——Pi 会话通常针对同一 schema 连续工作,新鲜度足够。用户执行 /db refresh-schema 时显式刷新。

查询结果自适应布局。 终端展示查询结果有一个实际问题——表太宽或行太多时,Markdown 表格很难读。格式化引擎根据数据形状自动选择布局:8 列以内用水平表格,超过 8 列但只有几行就行列转置,又宽又多就切换为垂直键值对展示。全 NULL 的列和所有行值相同的列自动折叠,在底部给一句摘要。

收藏和历史。 常用的排查 SQL 收藏起来,下次一键执行。所有查询自动记录,支持关键词搜索。这些功能都很简单,但它们省掉了"我上次那条 SQL 写的是什么来着"的回忆成本。


九、当前状态和后续扩展

目前 devops-tools 实现了数据库查询部分。但更重要的是,它验证了一件事:Pi 的 extension 机制足够灵活,可以用来搭建一个完整的研发工作台。

接下来计划扩展的方向,按场景区分操作模式:

工具 状态 模式 说明
数据库查询 ✅ 已实现 命令 人写 SQL
日志查询 计划中 命令 SSH tail/grep,人指定过滤条件
跳板机登录 计划中 命令 /ssh <host>,AI 的 bash 自动走 ProxyJump
测试数据构造 计划中 Tool AI 读表结构 → 生成数据 → 批量插入
接口 Mock 计划中 Tool AI 根据接口描述启动本地 mock server

每个新增的工具共享同一套配置体系和门面接口,可以独立实现、渐进扩展。

这个方案适合日常开发中的快速排查和辅助决策——你不必在所有场景下都用它。大规模数据分析、需要可视化的报表、多人协作的数据探索,DataGrip 之类的专用工具仍然更合适。工作台的意义不是替代专业工具,而是减少切换次数。

如果你也在用 AI 编码工具,而且总觉得有些地方「不连贯」——可能不是你的问题,是工具的设计假设和你的工作流不匹配。Pi 给了你一个机会,让你自己来调这个匹配度。