首页
/ Ponytail 的 Group By 示例深读:用 Object.groupBy 替代 lodash 依赖,以及背后的决策阶梯

Ponytail 的 Group By 示例深读:用 Object.groupBy 替代 lodash 依赖,以及背后的决策阶梯

2026-09-04 10:39:17作者:盛欣凯Ernestine

本文以 Ponytail 仓库中的 examples/group-by.md 为例,完整拆解"按 key 分组对象数组"这一日常任务在有无 Ponytail 技能下的两种典型输出,并结合 skills/ponytail/SKILL.md 的决策阶梯与 docs/platform-native.md 的平台内建对照表,讲清楚为什么 Agent 会跳过 lodash 选择 Object.groupBy,以及在不同运行时环境下如何正确回退。读完后你可以掌握:该示例对应的真实可复现来源、Object.groupBy 的运行时可用性判断方法,以及在浏览器/Node 不支持时选 reduce 而非依赖库的取舍逻辑。

任务定义:"Group this array of objects by a key"

examples/group-by.md 给出的任务只有一句话:

Task: "Group this array of objects by a key."

这是一个极高频的场景:拿到 orders 这类对象数组,需要按某个字段(如 status)把元素聚合成 { pending: [...], shipped: [...], delivered: [...] } 这样的分组结果。仓库用同一模型、同一任务分别生成了"无技能"(Without Ponytail)与"带 Ponytail"(With Ponytail)两版输出,以便直接对比。

没有 Ponytail 时的典型输出

第一种路径是"装个依赖":

npm install lodash
import { groupBy } from "lodash";

const byStatus = groupBy(orders, "status");
// → { pending: [...], shipped: [...], delivered: [...] }

第二种路径是手写 reduce

const byStatus = orders.reduce((acc, order) => {
  (acc[order.status] ??= []).push(order);
  return acc;
}, {});

两条路径各有代价:前者为了一个功能引入一整包工具库(即使现代打包器会 tree-shaking,依赖面、版本升级、许可证都要多背一份);后者虽然零依赖,但多行累加逻辑、??= 空值兜底,读起来不如语言内建直观。

带 Ponytail 时的输出

// ponytail: Object.groupBy does this
const byStatus = Object.groupBy(orders, order => order.status);
// → { pending: [...], shipped: [...], delivered: [...] }

一行内建 API,外加一条 ponytail: 注释说明"为什么选它"。原文档的总结是:1 dependency (or a reduce) → 1 built-in——一个依赖(或一段 reduce)变成了一个内建。这正是 Ponytail 的核心口号 "The best code is the code you never wrote" 在依赖管理上的体现:少装一个 lodash,就少一个需要维护、审计和升级的包。

// ponytail: 这种注释不是随手写的风格,而是 skills/ponytail/SKILL.md 中成文的规则:对"刻意简化、砍掉了真实边角"的方案,要用 ponytail: 注释标出上限与升级路径。配套地,README 中的 /ponytail-debt 命令会把这些被推迟的 ponytail: 捷径收进一张"债务台账",避免"稍后处理"变成"永远不处理"(见 README.md 的命令表)。

为什么跳过 lodash:七级决策阶梯

Ponytail 的"懒"不是随机偷懒,而是一套写死在技能里的决策顺序。skills/ponytail/SKILL.md 定义了 "The ladder",Agent 在写码前先爬梯子,停在第一个成立的台阶:

1. Does this need to exist?   → 不需要就不写(YAGNI)
2. Already in this codebase?  → 复用代码库里已有的实现
3. Stdlib does it?            → 用标准库
4. Native platform feature?   → 用平台内建
5. Installed dependency?      → 用已安装的依赖,绝不为此新增依赖
6. One line?                  → 一行解决
7. Only then: the minimum that works

把这个例子放进梯子走一遍,轨迹非常清晰:

  • 台阶 1–2 排除后,任务在台阶 3(标准库)/ 台阶 4(平台内建)就落地了——Object.groupBy 是语言运行时自带的,直接命中,根本走不到"要不要装一个新依赖"这一步;
  • 台阶 5 的规则是"用已安装依赖,绝不为几行代码能搞定的事新增依赖",这正是 npm install lodash 方案被否掉的原因;
  • 台阶 6 的"一行"恰好与台阶 3/4 的答案重合,Object.groupBy 本身就是单行方案。

仓库里还有一份"平台内建速查表"直接支撑了这个判断:docs/platform-native.md 的 "JavaScript / Browser APIs" 一节里,lodash.groupby 一行明确写着它的平台替代就是 Object.groupBy(arr, fn)。该文档的立场(The Pattern 一节)是:平台团队花多年解决的问题,包作者包一层,你装了这个 wrapper,wrapper 失维护,你再去调试 wrapper——跳过 wrapper,平台功能随应用免费附带。

另外注意技能中的一条配套规则:两个标准库方案体积相同时,"选在边角情况下正确的那个"(skills/ponytail/SKILL.md)。懒是少写代码,不是挑更弱的算法——这条规则解释了为什么 Ponytail 选内建 API 而不是"能用就行"的更 hacky 写法。

运行时可用性与回退决策

Object.groupBy 不是"永远可用",它的选型依赖目标运行时。原文档给出的发布节点是:

运行时 支持起点
Chrome 117
Firefox 119
Safari 17.4
Node.js 21

原文档还给出了两个进阶决策:

  1. 想要 Map 而不是普通对象:用 Map.groupBy(orders, o => o.status)。从 JS 标准语义看,两者差别在于键的类型处理——Object.groupBy 的结果键会被字符串化(非字符串键也会落成字符串属性名),而 Map.groupBy 保留回调返回值原样作键,对"分组键本身是对象/数字且需要保留类型"的场景更合适;
  2. 目标环境不支持时(如需要兼容 IE11 或旧版 Node):回退到前文的 reduce 一行写法——原文档特别强调,此时的正确选择是 reduce,而不是 lodash。依赖库没有因为"运行时太旧"而变得更值得装,零依赖的手写版才是与旧运行时匹配的方案。

一个容易混淆的点值得澄清:本仓库基准测试的 README(benchmarks/README.md)要求 Node.js ≥ 22.22.0,那是 promptfoo 引擎自身的版本约束;而 Object.groupBy 的"Node.js 21"指该 API 在 Node 主线可用的起点。两者是不同语境,不要混用。

示例来源:一次可复现的真实基准运行

这组对比不是人手写的。examples/README.md 说明:examples/ 下都是基准运行的真实模型输出(verbatim),同一个模型(Claude Haiku 4.5,temperature 1,来源 benchmarks/output.json)对同一任务分别以"无技能"与"带 ponytail"两种配置作答。示例目录包含 group-bydebouncecsv-sumdeep-clonemodal-dialog 等十余个例子,超出基准标准五任务的表内清单。

整个对照链路在仓库里可以逐环核对:

  • 对照实验配置benchmarks/promptfooconfig.yaml 定义了三条对照臂(baseline 无技能、caveman、ponytail)与标准任务集;
  • Ponytail 臂的注入方式benchmarks/arms/ponytail.js 直接把仓库的 skills/ponytail/SKILL.md 全文读入作为 system prompt——也就是说,示例中 Agent 表现出的"先查内建、再谈依赖"行为,正是这份技能文本约束的结果;
  • 质量门benchmarks/README.md 的 Metrics 表说明,loc.js 负责度量代码行数(总是通过,只记录),而 correctness.js门槛——"一个在 LOC 上得分很高但实际坏掉的一行代码,会在 correctness 上失败"。这保证了"一行内建"的省不是牺牲正确性换来的。

复现命令(见 benchmarks/README.md):

npx promptfoo@latest eval -c benchmarks/promptfooconfig.yaml

落到自己项目里的用法

如果你想在真实编码流程中让 Agent 表现出同样的选择习惯(遇到 groupBy 先查内建、拒绝装 lodash),仓库提供两类接入方式(详见 README.md 的 Install 一节):

  1. 插件安装(以 Claude Code 为例,分两条独立 prompt 发送):
/plugin marketplace add DietrichGebert/ponytail
/plugin install ponytail@ponytail

Codex、GitHub Copilot CLI、OpenCode、Gemini CLI 等 20 个宿主都有对应的安装命令;卸载则用 /plugin remove ponytail 等宿主命令,并可用 node scripts/uninstall.js 清理插件目录外的状态文件。

  1. 指令文件模式(Cursor、Cline、Copilot Chat 等无插件的宿主):把 AGENTS.md 或对应规则文件(.cursor/rules/.clinerules/ 等)复制进项目即可,零配置生效。

日常配合的斜杠命令中,与本示例直接相关的有两个:/ponytail-review 会对当前 diff 做过度工程审查并回传"可删清单"(比如 diff 里新装了 lodash 却只用了 groupBy,就会被它揪出来);/ponytail-debt 则汇总代码里的 ponytail: 注释形成推迟事项台账。强度用 /ponytail lite|full|ultra|off 调节,默认 full,也可用环境变量 PONYTAIL_DEFAULT_MODE~/.config/ponytail/config.json 预设。

小结

examples/group-by.md 用最小篇幅演示了 Ponytail 的完整决策闭环:任务 → 爬梯子 → 停在"平台内建"台阶 → 一行 Object.groupBy + 一条 ponytail: 注释 → 附运行时可用性清单与旧环境的 reduce 回退。它同时是一个可验证的样本——背后有完整的 promptfoo 对照配置、correctness 门槛与真实模型输出存档支撑。对读者而言,其价值不仅是"哦原来有 Object.groupBy",更在于一套可迁移的判断框架:下一次 Agent 要 npm install 某个工具库之前,先问自己那七个台阶的问题。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384