Show HN: Id-agent – 为AI代理节省Token的UUID替代方案
摘要
id-agent是一个开源npm库,生成可读的、节省Token的基于单词的ID,作为AI代理的UUID替代方案,将Token成本降低约40%,同时保持抗碰撞性。
查看缓存全文
缓存时间: 2026/05/19 13:02
vostride/id-agent
Source: https://github.com/vostride/id-agent
id-agent
为 AI 智能体设计的令牌高效 ID
当 UUID 消耗约 23 个令牌且容易被 LLM 幻觉时,id-agent 生成可记忆的基于单词的 ID,约 14 个令牌,具有相同的抗碰撞能力。这是第一个为上下文窗口(而非数据库)构建的 ID 库。
- 人类可读 —— 基于单词的 ID,人类和 LLM 都能真正记住
- 令牌高效 —— 单词列表中的每个单词在 o200k_base 上恰好是 1 个 BPE 令牌
- 抗碰撞 —— 可配置熵,范围从 ~12 到 ~192 位
- 输入验证 —— 所有公共 API 均采用 zod 驱动的模式验证
令牌成本对比
| 格式 | 示例 | 令牌数(o200k_base) | 抗碰撞性 |
|---|---|---|---|
| UUID v4 | 89b842d9-6df9-4cf4-8db0-9dc3aed3cfd7 | ~23 | 122 位 |
| id-agent(默认,8 个单词) | urd-antes-sorry-pac-dire-total-expire-going | ~14 | ~96 位 |
| id-agent(5 个单词) | frame-beer-bell-tog-hoot | ~8 | ~60 位 |
id-agent — 8 个单词,43 字符,12 令牌
UUID v4 — 36 字符,26 令牌
安装
npm install id-agent
pnpm add id-agent
快速上手
import { idAgent } from 'id-agent'
// 生成一个随机 ID(8 个单词,~96 位熵)
const id = idAgent() // => "urd-antes-sorry-pac-dire-total-expire-going"
// 带类型前缀
const taskId = idAgent({ prefix: 'task' })
// => "task_slide-exact-cede-bury-linge-ease-bean-impact"
// 减少单词数量用于短生命周期 ID
const short = idAgent({ words: 3 })
// => "front-reject-tho"
API 参考
idAgent(opts?)
生成一个随机、人类可读的 ID。
import { idAgent } from 'id-agent'
idAgent() // 8 个单词,~96 位
idAgent({ words: 5 }) // 5 个单词,~60 位
idAgent({ prefix: 'user' }) // "user_cloud-train-scope-frame-match-level-paint-field"
选项:
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
prefix | string | undefined | 类型前缀(仅小写字母数字) |
words | number | 8 | 单词数量(1-16)。控制熵:单词数 * 12 位 |
无效选项会抛出带有描述信息的 ZodError。
idAgent.from(input, opts?)
使用 HMAC-SHA256 从字符串输入生成确定性 ID。相同输入始终产生相同 ID。
const id = await idAgent.from('[email protected]')
// 始终返回相同输入对应的相同 ID
const namespaced = await idAgent.from('[email protected]', {
namespace: 'my-app',
prefix: 'user',
words: 5,
})
选项:
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
prefix | string | undefined | 类型前缀(仅小写字母数字) |
words | number | 8 | 单词数量(1-16) |
namespace | string | 'id-agent' | 用于域分离的 HMAC 密钥 |
parse(id)
解析任何 id-agent ID 为其组成部分。支持连字符和下划线分隔的单词。对于无法识别的格式返回 null。
import { parse } from 'id-agent'
parse('task_storm-delta-stone')
// => { prefix: 'task', words: ['storm', 'delta', 'stone'], wordCount: 3, bits: 36, raw: 'task_storm-delta-stone', format: 'readable' }
parse('task_storm_delta_stone')
// => { prefix: 'task', words: ['storm', 'delta', 'stone'], wordCount: 3, bits: 36, raw: 'task_storm_delta_stone', format: 'readable' }
validate(id)
检查一个字符串是否为有效的 id-agent ID。验证所有单词是否存在于 WORDLIST 中。
import { validate } from 'id-agent'
validate('storm-delta-stone')
// => { valid: true, prefix: undefined, wordCount: 3 }
validate('task_jump-notaword')
// => { valid: false, reason: 'unknown words: notaword' }
validate('INVALID')
// => { valid: false, reason: 'contains uppercase characters' }
createAliasMap(opts)
创建双向别名映射,用于 LLM 上下文中的令牌缩减。将长 ID 映射为短单词别名,支持完整的替换/恢复功能。
import { createAliasMap } from 'id-agent'
const aliases = createAliasMap({ words: 3 })
aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
// => "storm-delta-stone" (来自 WORDLIST 的 3 个随机单词)
aliases.get('storm-delta-stone')
// => "8cdda07b-85d2-459c-8a2a-83c8f9245dbe"
// 在发送给 LLM 之前替换文本中的所有 UUID
const text = 'Process 8cdda07b-85d2-459c-8a2a-83c8f9245dbe then 6ba7b810-9dad-11d1-80b4-00c04fd430c8'
const shortened = aliases.replace(text, {
pattern: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi
})
// => "Process storm-delta-stone then cloud-train-scope"
// 在 LLM 输出中恢复原始值
const restored = aliases.restore(shortened)
// => 原始文本
选项:
| 选项 | 类型 | 必需 | 描述 |
|---|---|---|---|
words | number | 是 | 每个别名的单词数量(1-16) |
entries() 返回 [original, alias] 对(而非 [alias, original])。使用 get(alias) 从别名查找原始值。
detectDuplicates(opts)
使用正则表达式模式扫描文本中的重复 ID。纯函数 —— 不访问文件系统。
import { detectDuplicates } from 'id-agent'
const dupes = detectDuplicates({
pattern: /[a-z]+(?:-[a-z]+)+/,
text: 'Found storm-delta-stone in file A and storm-delta-stone in file B',
})
// => [{ id: 'storm-delta-stone', count: 2 }]
// 也接受字符串数组
const dupes2 = detectDuplicates({
pattern: /task_[a-z]+(?:-[a-z]+)+/,
text: ['const x = "task_red-fox-run"', 'const y = "task_red-fox-run"'],
})
选项:
| 选项 | 类型 | 描述 |
|---|---|---|
pattern | RegExp | 匹配文本中 ID 的正则表达式 |
text | string | string[] | 要扫描重复项的文本 |
WORDLIST
直接访问精心挑选的 4096 单词列表。每个单词在 o200k_base 上恰好是 1 个 BPE 令牌。该数组是冻结的(不可变)。
import { WORDLIST } from 'id-agent'
WORDLIST.length // => 4096
Object.isFrozen(WORDLIST) // => true
数学原理
熵
每个单词从精心挑选的 4096 单词列表(2^12)中均匀选取。ID 中的每个位置都是独立的随机选择:
每个单词的熵 = log2(4096) = 12 位
总熵 = 单词数 * 12 位
ID 空间 = 4096^words = 2^(words * 12)
无论单个单词的长度如何,该结论都成立 —— 一个 3 字符单词和一个 6 字符单词都恰好贡献 12 位,因为攻击者必须从同一个 4096 单词池中猜测。
碰撞概率(生日悖论)
在 n 个随机生成的 ID 中至少发生一次碰撞的概率:
P(collision) ≈ n^2 / (2 * 2^b) 其中 b = 总熵位数
这是生日悖论近似值,在 P 较小(P < 0.01)时有效。
| 单词数 | 位数 | ID 空间 | P @ 1K | P @ 10K | P @ 100K | P @ 1M | P @ 1B | 50% 碰撞时的条目数 |
|---|---|---|---|---|---|---|---|---|
| 3 | 36 | 6.9 * 10^10 | 7.3 * 10^-6 | 7.3 * 10^-4 | 7.3 * 10^-2 | ~1 | ~1 | ~309K |
| 4 | 48 | 2.8 * 10^14 | 1.8 * 10^-9 | 1.8 * 10^-7 | 1.8 * 10^-5 | 1.8 * 10^-3 | ~1 | ~20M |
| 5 | 60 | 1.2 * 10^18 | 4.3 * 10^-13 | 4.3 * 10^-11 | 4.3 * 10^-9 | 4.3 * 10^-7 | 0.43 | ~1.3B |
| 8 | 96 | 7.9 * 10^28 | 6.3 * 10^-24 | 6.3 * 10^-22 | 6.3 * 10^-20 | 6.3 * 10^-18 | 6.3 * 10^-12 | ~331T |
| 10 | 120 | 1.3 * 10^36 | 3.8 * 10^-31 | 3.8 * 10^-29 | 3.8 * 10^-27 | 3.8 * 10^-25 | 3.8 * 10^-19 | ~1.4 * 10^18 |
| UUID v4 | 122 | 5.3 * 10^36 | 9.4 * 10^-32 | 9.4 * 10^-30 | 9.4 * 10^-28 | 9.4 * 10^-26 | 9.4 * 10^-20 | ~2.7 * 10^18 |
默认值(8 个单词,96 位) 在达到 50% 碰撞概率之前可安全用于超过 300 万亿个条目。实际上,大多数应用永远不会生成超过几百万个 ID。
计算示例
对于默认的 8 单词 ID,在 100 万个条目时:
b = 8 * 12 = 96 位
n = 1,000,000
P ≈ (10^6)^2 / (2 * 2^96) = 10^12 / (2 * 7.92 * 10^28) = 10^12 / (1.58 * 10^29) = 6.3 * 10^-18
这大约是 1 在 158 万万亿 —— 实际上为零。
令牌成本(实测)
所有测量基于 o200k_base(GPT-4o、GPT-4.1、o1、o3),使用 tiktoken。由于 BPE 与连字符的合并行为,每个 ID 的令牌数略有变化 —— 以下值为 100 个样本的平均值:
| 格式 | 平均令牌数 | 熵 | 与 UUID 相比节省的令牌数 | 节省比例 |
|---|---|---|---|---|
| UUID v4 | ~23 | 122 位 | – | – |
| id-agent(3 个单词) | ~5 | 36 位 | ~18 | 78% |
| id-agent(5 个单词) | ~8 | 60 位 | ~15 | 65% |
| id-agent(8 个单词,默认) | ~14 | 96 位 | ~9 | 39% |
| id-agent(10 个单词) | ~17 | 120 位 | ~6 | 26% |
为什么不直接用更少的单词?
合适的单词数量取决于你的规模。默认的 8 是故意保守的(全局安全)。但如果你在构建较小的东西:
| 规模 | 推荐 | 熵 | 原因 |
|---|---|---|---|
| 开发/测试 | words: 3 | 36 位 | 快速、可记忆、约 5 令牌。在约 300K 条目时发生碰撞。 |
| 团队工具 | words: 4 | 48 位 | 安全至约 20M 条目。适合内部 API。 |
| 生产级 SaaS | words: 5 | 60 位 | 安全至约 1B 条目。与 UUID 相比节省 65% 令牌。 |
| 高吞吐/分布式 | words: 8(默认) | 96 位 | 安全至约 300T 条目。安全的默认值。 |
| UUID 等效 | words: 10 | 120 位 | 匹配 UUID v4 碰撞数学。 |
令牌效率:为什么单词胜过十六进制
BPE 分词器(所有主要 LLM 使用)基于自然语言训练。短英文单词默认就是单个令牌。UUID 是十六进制字符串,会不可预测地拆分:
"storm-delta-stone" => 4 个令牌(3 个单词 + 分隔符)
"dc193952-186a-4645" => 11 个令牌(同样 18 个字符!)
id-agent 的单词列表经过精心挑选,使得每个单词在 o200k_base 上恰好是 1 个 BPE 令牌。由于 BPE 合并行为,连字符每 6 个单词增加约 1 个令牌。这就是为什么基于单词的 ID 从根本上比随机十六进制/字母数字字符串更节省令牌。
工作原理
id-agent 使用一个精心挑选的 4096 个英文单词列表,每个单词已验证为 o200k_base 分词器(GPT-4o、GPT-4.1 使用)上的单个 BPE 令牌。单词长度为 3-6 个字符,已过滤掉攻击性词汇和同音词。
随机 ID 使用 crypto.getRandomValues()(CSPRNG)。确定性 ID 使用 HMAC-SHA256(通过 Web Crypto API),将哈希映射到单词列表索引。所有公共 API 输入均使用 zod (https://zod.dev) 模式进行验证。无效选项会抛出带描述的错误信息。
许可证
MIT
相似文章
AI Agent Registry:关于问责制的思想实验
作者介绍了一个开源AI Agent Registry,它为智能体分配唯一的合规UUID,支持违规报告和查询,以促进自主AI系统的问责制和信任。
Show HN: 轻量级多AI代理对话方案,无需API付费
一种轻量级模式,用于编排多个AI代理(Claude、Codex、Gemini)之间的对话,通过CLI实现,无需API付费,利用会话恢复来维持跨代理交互的上下文。
AI代理让代币化平台比我预想的更易用
一位开发者分享了AI代理如何通过人类与系统的智能编排(而非完全自主)来改进代币化平台。
@ClementDelangue:令牌成本决定了不会有SaaS末日——优秀的开发工具是代理的缓存智能!流行的观…
Hugging Face的hf CLI被证明远比手写原始API调用更高效、更成功,AI代理使用后基准测试显示令牌消耗减少多达6倍,任务成功率从84%提升至94%,这表明良好的抽象是代理的缓存智能。
@pallavishekhar_: 如何减少AI代理中的Token使用?我们来理解一下。AI代理使用LLM进行思考、规划和推荐工具。每一步…
本帖子分享了减少AI代理中Token使用的策略,包括提示缓存、上下文摘要、使用较小模型、修剪工具输出、子代理、RAG以及紧凑的系统提示。