Doramagic 项目包 · 项目说明书
id-agent 项目
生成时间:2026-05-19 13:55:34 UTC
项目介绍
id-agent 是一个专为 AI Agent 系统设计的人类可读、令牌高效的 ID 生成库。该项目由 vostride 开发,采用 MIT 开源许可证,当前版本为 1.0.3。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
id-agent 是一个专为 AI Agent 系统设计的人类可读、令牌高效的 ID 生成库。该项目由 vostride 开发,采用 MIT 开源许可证,当前版本为 1.0.3。
资料来源:package.json:2-10
核心目标
id-agent 解决了传统 UUID 在 AI Agent 系统中的几个关键痛点:
| 问题 | UUID 方案 | id-agent 方案 |
|---|---|---|
| 令牌开销 | ~23 个令牌 | 3词约 5 个令牌(节省 78%) |
| 可读性 | 无意义十六进制字符串 | 自然语言单词组合 |
| 碰撞安全 | 120 bits 熵 | 默认 96 bits(安全 300 万亿条目) |
| 人类协作 | 难以口头交流 | 可直接朗读和拼写 |
资料来源:README.md
技术架构
架构概览
graph TD
A[用户请求] --> B{ID 类型选择}
B -->|随机 ID| C[crypto.getRandomValues CSPRNG]
B -->|确定性 ID| D[HMAC-SHA256 Web Crypto API]
C --> E[映射到 WORDLIST 索引]
D --> E
E --> F[formatId 格式化输出]
F --> G[返回人类可读 ID]
H[验证流程] --> I[parse 解析 ID]
I --> J[wordlist 词表校验]
J --> K{词存在?}
K -->|是| L[返回有效结果]
K -->|否| M[返回无效原因]核心模块
| 模块 | 文件路径 | 职责 |
|---|---|---|
| 主入口 | src/index.ts | 导出公共 API |
| 随机 ID | src/random.ts | 基于 CSPRNG 的随机 ID 生成 |
| 确定性 ID | src/deterministic.ts | 基于 HMAC-SHA256 的确定型 ID 生成 |
| 解析器 | src/parse.ts | 解析和验证 ID 格式 |
| 检测器 | src/detect.ts | 检测文本中的重复 ID |
| 词表 | src/wordlist.ts | 4096 个精选单词列表 |
| 别名映射 | src/alias.ts | 令牌压缩的双向映射 |
工作原理
熵与安全性
id-agent 使用精心挑选的 4096 词表(2^12),每个单词贡献 12 bits 熵。单词选择通过以下流程确保安全性:
graph LR
A[CSPRNG 随机数] --> B[取前 16 位]
B --> C[模 4096 取余]
C --> D[映射到 WORDLIST]
D --> E[生成单词]
E --> F[组合为 ID]碰撞概率计算公式:
P(collision) ≈ n² / (2 × 2^b)
其中 b = 总熵位数
| 单词数 | 熵位 | ID 空间 | 50% 碰撞阈值 |
|---|---|---|---|
| 3 | 36 bits | 6.9 × 10¹⁰ | ~309K 项 |
| 5 | 60 bits | 1.2 × 10¹⁸ | ~13 亿项 |
| 8 | 96 bits | 7.9 × 10²⁸ | ~300 万亿项 |
| 10 | 120 bits | 2.7 × 10¹⁸ | UUID 级别安全 |
资料来源:README.md
令牌效率
BPE 分词器(如 GPT-4o 使用的 o200k_base)在自然语言上训练,短英文单词恰好是单个令牌:
| 格式 | 令牌数 | 字符数 | 效率 |
|---|---|---|---|
dc193952-186a-4645 | ~11 tokens | 18 | 低 |
storm-delta-stone | ~4 tokens | 18 | 高 |
词表中的每个单词都经过验证,确保在 o200k_base 分词器上是恰好 1 个 BPE 令牌。
资料来源:README.md
词表构建
词表构建经过多层过滤:
graph TD
A[o200k_base 词汇表] --> B{长度 3-6 字符}
B -->|是| C[系统词典验证]
B -->|否| X[排除]
C --> D{小写字母?}
D -->|是| E{无同音词?}
D -->|否| X
E -->|是| F{无贬义?}
E -->|否| X
F -->|是| G[加入词表]过滤规则:
- 长度筛选:仅保留 3-6 个字符的单词
- 词典验证:确保是有效的英语单词
- 同音词过滤:排除会产生歧义的同音词(如
bare/bear) - 内容过滤:移除冒犯性词汇
资料来源:scripts/build-wordlist.ts
API 参考
安装
npm install id-agent
# 或
pnpm add id-agent
环境要求:Node.js ≥ 18
资料来源:package.json:32-33
`idAgent.generate(opts?)`
生成随机人类可读 ID。
import { idAgent } from 'id-agent'
const id = idAgent.generate()
// => "storm-delta-stone"
const prefixed = idAgent.generate({ prefix: 'task', words: 5 })
// => "task_snow-ocean-frost-finch-grove"
参数选项:
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
prefix | string | undefined | 类型前缀(小写字母数字) |
words | number | 8 | 单词数量(1-16),控制熵位 |
`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 密钥,用于域隔离 |
资料来源:src/deterministic.ts
`parse(id)`
解析任意 id-agent ID 为其组成部分。
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'
// }
`validate(id)`
验证字符串是否为合法的 id-agent ID。
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' }
资料来源:src/parse.ts
`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 }]
参数选项:
| 参数 | 类型 | 描述 | |
|---|---|---|---|
pattern | RegExp | 匹配 ID 的正则表达式 | |
text | `string \ | string[]` | 要扫描的文本 |
资料来源:src/detect.ts
`createAliasMap(opts)`
创建用于 LLM 上下文令牌压缩的双向别名映射。
import { createAliasMap } from 'id-agent'
const aliases = createAliasMap({ words: 3 })
// 设置长 UUID 并获取短别名
aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
// => "storm-delta-stone"
// 替换文本中的所有 UUID
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
})
// 恢复原始 UUID
const restored = aliases.restore(shortened)
`WORDLIST`
直接访问 4096 词表(只读数组)。
import { WORDLIST } from 'id-agent'
WORDLIST.length // => 4096
Object.isFrozen(WORDLIST) // => true
资料来源:README.md
应用场景
| 场景 | 推荐配置 | 熵位 | 说明 |
|---|---|---|---|
| 开发/测试 | words: 3 | 36 bits | 快速原型,便于一目了然 |
| 生产 SaaS | words: 5 | 60 bits | 节省 65% 令牌,安全到 10 亿项 |
| 高并发/分布式 | words: 8(默认) | 96 bits | 默认安全配置,可应对 300 万亿项 |
| UUID 等效 | words: 10 | 120 bits | 与 UUID v4 碰撞概率相同 |
开发命令
| 命令 | 说明 |
|---|---|
pnpm build | 构建生产版本 |
pnpm test | 运行测试 |
pnpm lint | 代码检查 |
pnpm validate | 验证词表质量 |
pnpm build:wordlist | 重新构建词表 |
资料来源:package.json:37-44
项目依赖
生产依赖
| 依赖 | 版本 | 用途 |
|---|---|---|
zod | 4.3.6 | Schema 验证 |
开发依赖
| 依赖 | 用途 |
|---|---|
vitest | 单元测试 |
tsup | 构建工具 |
js-tiktoken | 分词器验证 |
obscenity | 词表内容过滤 |
dprint | 代码格式化 |
oxlint | 代码检查 |
资料来源:package.json:17-28
总结
id-agent 是一款专为 AI Agent 工作流优化的 ID 生成工具,通过以下核心优势解决 LLM 上下文中的效率问题:
- 令牌高效:默认配置节省约 78% 的令牌开销
- 人类可读:基于自然语言单词,便于口头交流和日志追踪
- 灵活安全:可配置的熵位满足从开发到生产各级别需求
- 确定性与随机性兼顾:支持随机生成和 HMAC 确定性生成两种模式
资料来源:package.json:2-10
安装与快速开始
id-agent 是一个用于 AI 代理系统的人类可读、令牌高效的可识别 ID 生成库。该项目通过将 UUID 或其他长标识符转换为基于单词的短格式,实现高达 78% 的令牌节省效果,同时保持足够的熵值以防止碰撞。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
项目概述
id-agent 是一个用于 AI 代理系统的人类可读、令牌高效的可识别 ID 生成库。该项目通过将 UUID 或其他长标识符转换为基于单词的短格式,实现高达 78% 的令牌节省效果,同时保持足够的熵值以防止碰撞。
资料来源:package.json:2-3
安装前准备
系统要求
| 要求 | 最低版本 | 说明 |
|---|---|---|
| Node.js | >= 18 | 必须支持 Web Crypto API |
| 包管理器 | pnpm 10.6.1 | 推荐使用 pnpm |
资料来源:package.json:21-22
核心依赖
| 依赖 | 版本 | 用途 |
|---|---|---|
| zod | 4.3.6 | 参数验证与类型安全 |
资料来源:package.json:28-30
安装步骤
方式一:使用 pnpm(推荐)
pnpm add id-agent
方式二:使用 npm
npm install id-agent
方式三:使用 yarn
yarn add id-agent
快速开始
基础导入与使用
import { idAgent } from 'id-agent'
// 生成随机人类可读 ID
const id = await idAgent.generate()
// => "storm-delta-stone-cloud-voice-fruit-north-field"
// 带前缀的 ID
const taskId = await idAgent.generate({ prefix: 'task', words: 5 })
// => "task_storm-delta-stone-cloud-voice-fruit"
资料来源:README.md
确定性 ID 生成
基于输入字符串生成稳定的 ID,适用于用户 ID、文档 ID 等场景:
const userId = await idAgent.from('[email protected]')
// 相同输入始终返回相同的 ID
const namespaced = await idAgent.from('[email protected]', {
namespace: 'my-app',
prefix: 'user',
words: 5,
})
资料来源:src/deterministic.ts:1-47
ID 解析与验证
import { parse, validate } from 'id-agent'
// 解析 ID 组成
const parsed = parse('task_storm-delta-stone')
// => { prefix: 'task', words: ['storm', 'delta', 'stone'], wordCount: 3, bits: 36 }
// 验证 ID 有效性
const result = validate('storm-delta-stone')
// => { valid: true, prefix: undefined, wordCount: 3 }
const invalid = validate('task_jump-notaword')
// => { valid: false, reason: 'unknown words: notaword' }
资料来源:src/parse.ts
生成选项配置
| 选项 | 类型 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|
prefix | string | undefined | 小写字母数字 | 类型前缀,用于域名分离 |
words | number | 8 | 1-16 | 单词数量,控制熵值:words * 12 bits |
资料来源:README.md
不同场景推荐配置
| 场景 | words | bits | 容量 | 说明 |
|---|---|---|---|---|
| 快速原型 | 3 | 36 | ~309K | 最小令牌占用 |
| 生产 SaaS | 5 | 60 | ~1.3B | UUID 65% 令牌节省 |
| 高流量/分布式 | 8 | 96 | ~300T | 安全默认值 |
资料来源:README.md
别名映射与文本替换
在 LLM 上下文中,可以使用别名映射将长 ID 替换为短单词格式:
import { createAliasMap } from 'id-agent'
const aliases = createAliasMap({ words: 3 })
aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
// => "storm-delta-stone"
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"
const restored = aliases.restore(shortened)
// => 恢复原始 UUID
资料来源:README.md
重复 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 }]
开发环境搭建
克隆项目
git clone https://github.com/vostride/id-agent.git
cd id-agent
安装依赖
pnpm install
可用脚本命令
| 命令 | 说明 |
|---|---|
pnpm build | 使用 tsup 构建项目 |
pnpm test | 运行 vitest 测试 |
pnpm test:watch | 监听模式运行测试 |
pnpm lint | 使用 oxlint 检查代码 |
pnpm format | 使用 dprint 格式化代码 |
pnpm format:check | 检查代码格式 |
pnpm size | 检查 bundle 大小 |
pnpm publint | 检查包发布配置 |
pnpm validate | 验证单词列表 |
资料来源:package.json:24-36
验证安装
安装完成后,可以通过以下方式验证:
import { idAgent, WORDLIST } from 'id-agent'
// 验证 WORDLIST 加载
console.log(WORDLIST.length) // => 4096
console.log(Object.isFrozen(WORDLIST)) // => true
// 生成测试 ID
const testId = await idAgent.generate({ words: 3 })
console.log(testId) // => 3 词格式 ID
下一步
资料来源:package.json:2-3
随机 ID 生成
随机 ID 生成是 id-agent 项目的核心功能之一。该模块利用加密安全的随机数生成器(CSPRNG)创建人类可读、Token 高效的标识符。与传统的 UUID 相比,随机生成的 ID 由从精心策划的 4096 词表中选取的单词组成,每个单词恰好对应一个 BPE Token,在 LLM 场景下显著降低 Token 消耗。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
随机 ID 生成是 id-agent 项目的核心功能之一。该模块利用加密安全的随机数生成器(CSPRNG)创建人类可读、Token 高效的标识符。与传统的 UUID 相比,随机生成的 ID 由从精心策划的 4096 词表中选取的单词组成,每个单词恰好对应一个 BPE Token,在 LLM 场景下显著降低 Token 消耗。
核心设计
词表机制
id-agent 维护一个包含 4096 个英语单词的词表,每个单词满足以下条件:
- 长度为 3-6 个字符
- 在 o200k_base 分词器上恰好为 1 个 Token
- 不包含冒犯性词汇
- 排除同音异义词(如 "wait" 和 "weight")
每个单词提供 log2(4096) = 12 位的熵值,ID 的总熵由单词数量决定:
总熵 = 单词数量 × 12 bits
ID空间 = 4096^单词数量 = 2^(单词数量 × 12)
资料来源:README.md
随机数生成
随机 ID 使用 crypto.getRandomValues() 作为熵源,这是 Web Crypto API 提供的加密安全随机数生成器。相比 Math.random(),CSPRNG 提供的随机数具有密码学安全性,适用于需要防止预测的攻击场景。
API 参考
`idAgent.random(opts?)`
生成一个随机 ID,默认配置为 8 个单词(96 位熵)。
import { idAgent } from 'id-agent'
const id = idAgent.random()
// => "storm-delta-stone-cloud-frost-raven-ocean-ember"
const shortId = idAgent.random({ words: 3 })
// => "storm-delta-stone"
const prefixedId = idAgent.random({ prefix: 'user', words: 5 })
// => "user-storm-delta-stone-cloud-frost"
#### 参数选项
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
prefix | string | undefined | 类型前缀(小写字母数字组合) |
words | number | 8 | 单词数量(范围 1-16),控制熵值 |
参数验证由 Zod Schema 执行,无效输入会抛出描述性错误。
资料来源:src/schemas.ts
类型定义
interface IdAgentRandomOptions {
prefix?: string
words?: number
}
interface IdAgent {
random(opts?: IdAgentRandomOptions): string
from(input: string, opts?: IdAgentFromOptions): Promise<string>
parse(id: string): ParsedId | null
validate(id: string): ValidationResult
}
资料来源:src/types.ts
工作流程
graph TD
A[调用 idAgent.random] --> B{参数验证}
B -->|通过| C[计算所需单词数]
B -->|失败| Z[抛出 ZodError]
C --> D[调用 crypto.getRandomValues]
D --> E[从 DataView 读取随机字节]
E --> F[每 2 字节映射到 WORDLIST 索引]
F --> G{检查重复}
G -->|无重复| H[收集单词到数组]
G -->|重复| E
H --> I{单词数已满足}
I -->|否| E
I -->|是| J[拼接单词为字符串]
J --> K{提供 prefix}
K -->|是| L[添加前缀: prefix-words]
K -->|否| M[仅返回 words]
L --> N[返回完整 ID]
M --> N碰撞概率分析
由于使用固定词表,存在理论碰撞概率。根据生日悖论公式:
P(碰撞) ≈ n² / (2 × 2^b)
其中 b = 总位数 = 单词数 × 12
不同配置的碰撞概率
| 单词数 | 熵值 | ID 空间 | 100 万条时概率 | 10 亿条时概率 | 50% 碰撞点 |
|---|---|---|---|---|---|
| 3 | 36 位 | 6.9 × 10¹⁰ | 0.00004% | ~43% | ~309K 条 |
| 4 | 48 位 | 2.8 × 10¹⁴ | 1.8 × 10⁻⁶ % | ~100% | ~20M 条 |
| 5 | 60 位 | 1.2 × 10¹⁸ | 4.3 × 10⁻⁸ % | 0.43% | ~1.3B 条 |
| 8 | 96 位 | 7.9 × 10²⁸ | 6.3 × 10⁻¹⁸ % | ~0% | ~300T 条 |
| 10 | 120 位 | 1.3 × 10³⁶ | ~0% | 9.4 × 10⁻²⁰ % | ~2.7 × 10¹⁸ 条 |
默认配置(8 单词,96 位) 可安全处理超过 300 万亿条 ID 而不产生 50% 碰撞概率。
资料来源:README.md
Token 效率对比
id-agent 的单词型 ID 在 LLM 分词器上远比 UUID 高效。以 o200k_base 分词器为例:
| 格式 | 平均 Token 数 | 熵值 | 相比 UUID 节省 |
|---|---|---|---|
| UUID v4 | ~23 | 122 位 | -- |
| id-agent (3 单词) | ~5 | 36 位 | 78% |
| id-agent (5 单词) | ~9 | 60 位 | 61% |
| id-agent (8 单词) | ~14 | 96 位 | 39% |
这是因为短英语单词在 BPE 分词器上天然就是单个 Token,而 UUID 的十六进制字符会不可预测地分裂成多个 Token。
资料来源:README.md
与确定性 ID 的关系
id-agent 提供两种 ID 生成模式:
| 模式 | 熵源 | 可重现性 | 适用场景 |
|---|---|---|---|
随机 random() | crypto.getRandomValues() | 否 | 会话 ID、临时标识符 |
确定性 from() | HMAC-SHA256 | 是 | 用户标识、内容寻址 |
确定性模式使用相同的词表和单词选择逻辑,但通过 HMAC-SHA256 将输入字符串映射到固定的单词序列,确保相同输入始终产生相同输出。
// 随机 ID:每次调用生成不同 ID
idAgent.random() // => "storm-delta-stone..."
idAgent.random() // => "frost-ocean-ember..."
// 确定性 ID:相同输入产生相同 ID
await idAgent.from('[email protected]') // => "cloud-train-scope..."
await idAgent.from('[email protected]') // => "cloud-train-scope..."
资料来源:src/deterministic.ts
使用场景
场景一:会话标识符
const sessionId = idAgent.random({ words: 4, prefix: 'session' })
// => "session-storm-delta-stone-cloud"
场景二:任务队列标识符
const taskId = idAgent.random({ words: 5, prefix: 'task' })
// => "task-frost-ocean-ember-light-rain"
场景三:轻量级随机标识符
const shortId = idAgent.random({ words: 3 })
// => "stone-cloud-train"
配置建议
| 应用场景 | 推荐配置 | 熵值 | 说明 |
|---|---|---|---|
| 开发/测试 | words: 3 | 36 位 | 简短易读,用于非关键场景 |
| 生产 SaaS | words: 5 | 60 位 | 安全到约 10 亿条,节省 65% Token |
| 高容量/分布式 | words: 8 (默认) | 96 位 | 安全到约 300 万亿条,推荐默认值 |
| UUID 等效 | words: 10 | 120 位 | 与 UUID v4 碰撞概率相当 |
安全性考量
CSPRNG 的必要性
使用 crypto.getRandomValues() 而非 Math.random() 是关键安全设计:
Math.random():伪随机数,可被预测crypto.getRandomValues():密码学安全随机数,满足熵要求
前缀验证
前缀仅接受小写字母数字组合(/^[a-z0-9]+$/),防止格式注入和解析歧义。
idAgent.random({ prefix: 'User-ID' })
// => 抛出错误: "prefix must be lowercase alphanumeric only"
资料来源:src/schemas.ts
验证与解析
`validate(id)`
验证 ID 是否合法:
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' }
`parse(id)`
解析 ID 的组成部分:
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'
// }
资料来源:src/parse.ts
内部实现
单词选择算法
核心逻辑位于 selectRandomWords 函数,通过以下步骤选择单词:
- 使用
DataView.getUint16()从随机字节序列中读取 16 位无符号整数 - 取模 4096 得到词表索引
- 重复直到收集到所需单词数量
- 去重确保无重复单词
重复检测
如果生成过程中出现重复单词,会重新生成而不是替换。这确保 ID 的实际熵值与理论值一致。
// 简化伪代码
do {
word = WORDLIST[randomUint16() % 4096]
} while (used.has(word) && used.size < 4096)
总结
随机 ID 生成是 id-agent 项目的基石功能,通过以下设计实现了安全性、可读性和 Token 效率的统一:
- 4096 词表:每个单词 12 位熵,BPE 友好
- CSPRNG:密码学安全的熵源
- 可配置单词数:根据场景平衡安全性和简洁性
- 可选前缀:支持类型化 ID 命名空间
- 与确定性模式共存:覆盖随机和可重现两种场景
资料来源:README.md
确定性 ID 生成
确定性 ID 生成是 id-agent 库的核心功能之一,允许从任意字符串输入生成唯一、可复现的 ID。与随机 ID 生成不同,相同的输入无论何时何地调用,都会产生完全相同的输出 ID。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
确定性 ID 生成是 id-agent 库的核心功能之一,允许从任意字符串输入生成唯一、可复现的 ID。与随机 ID 生成不同,相同的输入无论何时何地调用,都会产生完全相同的输出 ID。
该功能主要适用于:
- 用户标识:基于邮箱、用户名等生成唯一用户 ID
- 内容寻址:基于文件内容哈希生成确定性文件 ID
- 幂等操作:为请求生成稳定的标识符用于去重
核心原理是通过 HMAC-SHA256 哈希函数将输入字符串转换为加密安全的伪随机数,然后将这些数字映射到精选的 4096 词表中的单词。
工作原理
架构流程
graph TD
A[输入字符串] --> B[TextEncoder 编码]
B --> C[导入 HMAC 密钥]
C --> D[crypto.subtle.sign 使用 HMAC-SHA256]
D --> E[DataView 读取签名结果]
E --> F[每 2 字节映射到 WORDLIST 索引]
F --> G[拼接单词为最终 ID]
H[namespace 配置] --> C
I[words 数量] --> F
J[prefix 前缀] --> G关键步骤解析
步骤 1:输入验证与编码
函数首先验证输入有效性,确保传入的是非空字符串:
if (!input || typeof input !== 'string') {
throw new Error('input must be a non-empty string')
}
步骤 2:选项解析与默认值
使用 Zod Schema 验证并应用默认配置,words 默认值为 8:
const validated = IdAgentFromOptionsSchema.parse(opts ?? {})
const words = validated.words ?? 8
资料来源:src/deterministic.ts:12-13
步骤 3:Web Crypto API 密钥导入
使用 Web Crypto API 的 crypto.subtle 模块导入 HMAC 密钥:
const enc = new TextEncoder()
const keyData = enc.encode(validated.namespace ?? 'id-agent')
const key = await globalThis.crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
)
资料来源:src/deterministic.ts:18-24
步骤 4:HMAC-SHA256 签名
对输入字符串进行加密签名:
const sig = await globalThis.crypto.subtle.sign('HMAC', key, enc.encode(input))
步骤 5:哈希值到单词的映射
将签名结果转换为 DataView,每 2 字节(16 位)作为一个索引,取模 4096 获取词表中的单词:
const view = new DataView(sig)
const selected: string[] = new Array(words)
for (let i = 0; i < words; i++) {
selected[i] = WORDLIST[view.getUint16(i * 2) % 4096]
}
资料来源:src/deterministic.ts:28-32
步骤 6:格式化输出
使用 formatId 函数将前缀和单词数组拼接为最终 ID:
return formatId(validated.prefix, selected.join('-'))
API 参考
idAgent.from(input, opts?)
生成确定性 ID 的主要方法,挂载在 idAgent 函数对象上。
import { idAgent } from 'id-agent'
const id = await idAgent.from('[email protected]')
// => 例如:vivid-shade-glimmer (每次相同)
#### 参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
input | string | 是 | - | 用于生成 ID 的输入字符串 |
opts.prefix | string | 否 | undefined | 类型前缀(小写字母数字) |
opts.words | number | 否 | 8 | 单词数量(1-16),控制熵值 |
opts.namespace | string | 否 | 'id-agent' | HMAC 密钥,用于域隔离 |
资料来源:src/generate.ts:8,README.md
#### 返回值
返回格式化后的确定性 ID 字符串,格式为:{prefix_}word1-word2-word3-...
#### 使用示例
基础用法
const id = await idAgent.from('[email protected]')
// 每次调用都返回相同结果
带前缀和命名空间
const userId = await idAgent.from('[email protected]', {
namespace: 'my-app',
prefix: 'user',
words: 5,
})
// => "user_calm-bold-wake-tide-neat"
不同命名空间产生不同 ID
const id1 = await idAgent.from('same-input', { namespace: 'app-a' })
const id2 = await idAgent.from('same-input', { namespace: 'app-b' })
// id1 !== id2 (因为命名空间不同)
配置选项详解
words 参数
控制生成 ID 的单词数量,直接决定熵值大小。
| words 值 | 熵值 | ID 空间 | 适用场景 |
|---|---|---|---|
| 3 | 36 bits | 6.9 × 10¹⁰ | 仅用于日志/调试 |
| 5 | 60 bits | 1.2 × 10¹⁸ | 生产级 SaaS 应用 |
| 8 | 96 bits | 7.9 × 10²⁸ | 默认,高容量/分布式系统 |
| 10 | 120 bits | 1.3 × 10³⁶ | UUID v4 等效安全级别 |
资料来源:README.md
namespace 参数
HMAC 密钥用于域隔离,确保相同输入在不同应用/租户间产生不同 ID。
// 多租户场景
const id1 = await idAgent.from('shared-resource-id', { namespace: 'tenant-1' })
const id2 = await idAgent.from('shared-resource-id', { namespace: 'tenant-2' })
prefix 参数
类型前缀用于区分不同实体的 ID,便于人工识别和日志分析。
- 必须是 小写字母数字
- 格式:
{prefix}_word-word-word - 例如:
user_、task_、file_
await idAgent.from('doc-123', { prefix: 'doc' })
// => "doc_river-wave-soft-wake-calm-dream-bloom"
验证与解析
validate(id)
验证确定性 ID 的有效性,检查词表中的单词是否都存在:
import { validate } from 'id-agent'
validate('task_storm-delta-stone')
// => { valid: true, prefix: 'task', wordCount: 3 }
validate('task_jump-notaword')
// => { valid: false, reason: 'unknown words: notaword' }
资料来源:src/parse.ts
parse(id)
解析 ID 的结构组件:
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'
// }
资料来源:src/parse.ts
安全性分析
HMAC-SHA256 保障
确定性 ID 生成使用 Web Crypto API 的 crypto.subtle.sign 方法,底层采用 HMAC-SHA256 算法:
graph LR
A[输入] --> B[HMAC-SHA256]
B --> C[128位签名]
C --> D[选取前 2×words 字节]
D --> E[每字节对 4096 取模]
E --> F[映射到单词]域隔离机制
通过 namespace 参数实现密钥隔离,即使输入相同,不同命名空间产生的 HMAC 签名完全不同:
// 即使输入完全相同
idAgent.from('same-input', { namespace: 'ns-a' }) // => 完全不同的输出
idAgent.from('same-input', { namespace: 'ns-b' }) // => 完全不同的输出
熵值计算
每个单词贡献 12 bits 熵值(因为词表大小为 4096 = 2¹²):
总熵值 = words × 12 bits
默认 8 个单词提供 96 bits 熵值,在 300 万亿个项目内碰撞概率低于 50%。
浏览器兼容性
该功能依赖 Web Crypto API,要求:
| 环境 | 支持情况 | 说明 |
|---|---|---|
| 现代浏览器 | ✅ | HTTPS 环境下完整支持 |
| Node.js 18+ | ✅ | 内置 Web Crypto API |
| HTTP 页面 | ❌ | 浏览器安全限制 |
| Node.js <18 | ❌ | 需升级版本 |
if (!globalThis.crypto?.subtle) {
throw new Error('idAgent.from() requires Web Crypto API (crypto.subtle). Use HTTPS in browsers.')
}
资料来源:src/deterministic.ts:15-17
最佳实践
生产环境推荐配置
// 高安全性场景
const secureId = await idAgent.from(userId, {
words: 8, // 默认值,96 bits 熵值
prefix: 'user',
namespace: process.env.APP_NAMESPACE
})
// 平衡可读性与安全性
const balancedId = await idAgent.from(resourceId, {
words: 5, // 60 bits,适合大多数应用
prefix: 'resource'
})
常见错误处理
try {
const id = await idAgent.from('')
} catch (e) {
// Error: input must be a non-empty string
}
try {
const id = await idAgent.from('input', { words: 20 })
} catch (e) {
// ZodError: Number must be less than or equal to 16
}
与随机 ID 对比
| 特性 | 确定性 ID (from) | 随机 ID (idAgent()) |
|---|---|---|
| 输入要求 | 任意字符串 | 无 |
| 可复现性 | ✅ 相同输入 → 相同输出 | ❌ 每次不同 |
| Web Crypto | ✅ 必须 | ❌ 可选 |
| 适用场景 | 用户/内容标识 | 会话/临时 ID |
| 熵值来源 | HMAC-SHA256 | CSPRNG |
资料来源:src/generate.ts
解析与验证
解析与验证模块是 id-agent 项目中处理 ID 字符串的核心功能组件。该模块负责将人类可读的 ID 字符串解析为其组成成分,并检验其是否符合 id-agent 规范的格式要求。解析功能支持连字符分隔和下划线分隔两种格式,能够提取出前缀、单词数组、单词数量和熵位数等信息。验证功能则确保生成的 ID 仅包含有效的单词,并且格式完全符合规范要求。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
解析与验证模块是 id-agent 项目中处理 ID 字符串的核心功能组件。该模块负责将人类可读的 ID 字符串解析为其组成成分,并检验其是否符合 id-agent 规范的格式要求。解析功能支持连字符分隔和下划线分隔两种格式,能够提取出前缀、单词数组、单词数量和熵位数等信息。验证功能则确保生成的 ID 仅包含有效的单词,并且格式完全符合规范要求。
在 id-agent 的整体架构中,解析与验证模块扮演着基础设施的角色,被别名映射(AliasMap)、确定性 ID 生成(deterministicId)以及重复检测(detectDuplicates)等多个功能模块所依赖。这种设计确保了整个系统在处理 ID 字符串时拥有一致性的验证标准和数据格式。
核心 API
parse 函数
parse 函数是解析模块的入口函数,用于将 id-agent 格式的 ID 字符串分解为其组成成分。该函数支持连字符分隔(如 task_storm-delta-stone)和下划线分隔(如 task_storm_delta_stone)两种格式,并能够智能识别前缀与单词部分的分隔方式。
函数返回的对象包含以下属性:prefix 表示 ID 的前缀类型(如 task),若不存在前缀则返回 undefined;words 是一个字符串数组,包含 ID 中的所有单词;wordCount 表示单词的数量;bits 表示总熵位数,计算公式为 words.length * 12;raw 保存原始输入字符串;format 字段标识输入格式为 readable。当输入字符串无法被识别为有效的 id-agent 格式时,函数返回 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' }
解析算法首先尝试按下划线分割字符串,取第一部分作为候选前缀。若第一部分仅包含小写字母和数字,则将其识别为有效前缀;否则整个字符串将被视为纯单词序列。单词提取阶段通过正则表达式匹配连续的纯小写字母单词序列,支持连字符或下划线作为单词间的分隔符。这种双重格式支持使得 id-agent 能够兼容不同来源和风格的 ID 命名约定。
资料来源:src/parse.ts:1-30
validate 函数
validate 函数用于检验给定的字符串是否为合法的 id-agent ID。该函数执行多层次的验证检查,包括大写字符检测、无效字符检测、格式识别以及单词有效性验证。只有通过全部检查的 ID 才会被标记为有效。
验证流程首先检查字符串是否包含大写字符。id-agent 规范要求所有字符必须为小写,因此任何大写字母的出现都会导致验证失败并返回 contains uppercase characters 错误信息。资料来源:src/parse.ts:41-43
接下来验证字符串是否包含非法字符。有效字符集限于小写字母、数字、连字符和下划线。若发现其他字符,函数返回 contains invalid characters 错误。资料来源:src/parse.ts:44-46
然后调用 parse 函数尝试解析 ID。若解析失败(返回 null),说明字符串不符合 id-agent 的基本格式要求,验证将返回 unrecognized format 错误。资料来源:src/parse.ts:47-49
最后一项验证是单词词汇表检查。解析成功后,函数会逐一检查每个单词是否存在于 id-agent 的 4096 词单词列表中。若发现任何未知单词,返回 unknown words: ${invalid.join(', ')} 错误信息,列出所有无效单词。资料来源:src/parse.ts:50-58
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' }
成功验证时返回的对象包含 valid: true、可选的 prefix 字段以及 wordCount 表示单词数量。这种结构化的返回方式便于调用方根据验证结果进行后续处理,同时保留了必要的元数据信息。
重复检测功能
detectDuplicates 函数
detectDuplicates 函数提供在文本中扫描重复 ID 的能力。该函数是纯函数实现,不涉及文件系统访问,仅依赖正则表达式进行模式匹配,因此可以在任何环境中安全使用。资料来源:src/detect.ts:1-27
函数接受一个包含 pattern 和 text 两个属性的选项对象。pattern 是一个正则表达式,用于匹配待检测的 ID 格式;text 可以是单个字符串或字符串数组,函数会统一处理两种输入形式。资料来源:src/detect.ts:3-7
重复检测的工作流程首先将 text 参数统一转换为数组形式,然后遍历每一段文本使用正则表达式进行全局匹配。正则表达式的 g 标志确保能够找到所有匹配项,同时代码会确保匹配器始终以全局模式运行。资料来源:src/detect.ts:8-19
检测结果以数组形式返回,每个元素是一个包含 id 和 count 属性的对象。id 字段保存重复出现的 ID 字符串,count 字段表示该 ID 在文本中出现的次数。仅当 count > 1 时,结果才会被包含在返回值中,这意味着函数默认过滤掉仅出现一次的 ID。资料来源:src/detect.ts:20-26
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"'],
})
该函数在实际应用场景中具有广泛的用途,包括但不限于:检测代码库中是否存在重复的 ID 定义、验证测试数据中的 ID 唯一性、以及在数据迁移过程中确保 ID 不发生冲突。纯函数的设计使其特别适合在持续集成环境中作为自动化检查工具使用。
别名映射系统
createAliasMap 函数
createAliasMap 函数创建一个双向别名映射系统,用于在 LLM 上下文中实现令牌节省。该系统将长格式的原始标识符(如 UUID)映射为短格式的单词别名,并在需要时能够精确恢复原始值。资料来源:src/alias.ts:1-67
别名映射对象提供三个核心方法:set(original) 用于添加新的映射关系并返回生成的别名;get(alias) 根据别名查找原始值;replace(text, options) 批量替换文本中所有已注册的标识符;restore(text) 恢复被替换的标识符。
import { createAliasMap } from 'id-agent'
const aliases = createAliasMap({ words: 3 })
aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
// => "storm-delta-stone" (3 random words from WORDLIST)
aliases.get('storm-delta-stone')
// => "8cdda07b-85d2-459c-8a2a-83c8f9245dbe"
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"
const restored = aliases.restore(shortened)
// => original text
set 方法内部维护两个映射表:forward 将原始值映射到别名,reverse 将别名映射回原始值。当添加新的映射时,方法首检查是否存在已有映射以避免重复生成。若启用冲突允许选项,在生成别名时会进行碰撞检测。资料来源:src/alias.ts:19-31
replace 方法支持可选的正则表达式参数,若提供则仅替换匹配该模式的字符串。这种设计允许调用方精确控制替换范围,避免意外替换不应被处理的文本内容。资料来源:src/alias.ts:32-50
单词列表系统
WORDLIST 常量
WORDLIST 是 id-agent 项目精心策划的 4096 词单词列表,每个单词都经过验证恰好是 o200k_base 分词器(GPT-4o、GPT-4.1 等模型使用)的单个 BPE 令牌。这个特性是 id-agent 实现令牌效率的基础。资料来源:src/wordlist.ts
单词列表构建过程包含多个严格的筛选阶段。首先从 o200k_base 词汇表中提取所有 3-6 个字符的候选单词,然后与系统词典进行交叉验证确保词汇的有效性。资料来源:scripts/build-wordlist.ts:30-50
接下来进行两项 BPE 令牌验证:单令牌编码测试确保单词独立编码为单个令牌;连字符前缀测试确保单词在与其他内容连接时不会产生额外的令牌分割。资料来源:scripts/build-wordlist.ts:51-65
最后进行语义过滤,使用 obscenity 库检测并移除不适当的词汇,同时人工审查移除同音异义词(如 "wait" 和 "weight"),避免在口语环境中造成混淆。资料来源:scripts/build-wordlist.ts:66-90
import { WORDLIST } from 'id-agent'
WORDLIST.length // => 4096
Object.isFrozen(WORDLIST) // => true
单词列表以冻结数组形式导出,确保在运行期间不可被修改。这种不可变性保证了解析和验证结果的确定性和可靠性。
validate-wordlist.ts 验证脚本
scripts/validate-wordlist.ts 是项目提供的单词列表验证工具,用于确保 WORDLIST 满足所有令牌效率和安全要求。该脚本通过编程方式验证列表中每个单词的特性。
验证包含两个主要检查:单令牌验证确保每个单词编码为恰好一个 BPE 令牌;连字符前缀验证确保单词在与其他内容连接时保持令牌边界。资料来源:scripts/validate-wordlist.ts:1-25
脚本输出详细的验证报告,包括通过检查的项目数量和失败的单词列表(若存在)。所有检查通过时输出 ALL CHECKS PASSED,否则列出失败数量并返回非零退出码。
pnpm run validate
开发者可以通过运行此脚本快速验证单词列表的完整性,在修改单词列表或升级分词器版本后尤其重要。
数据结构
解析结果类型
| 字段 | 类型 | 描述 | |
|---|---|---|---|
prefix | `string \ | undefined` | ID 前缀,若无前缀则为 undefined |
words | string[] | 单词数组 | |
wordCount | number | 单词数量 | |
bits | number | 总熵位数(wordCount × 12) | |
raw | string | 原始输入字符串 | |
format | 'readable' | 格式标识 |
验证结果类型
| 字段 | 类型 | 描述 | |
|---|---|---|---|
valid | boolean | 验证是否通过 | |
prefix | `string \ | undefined` | 解析得到的前缀(仅在 valid 为 true 时) |
wordCount | number | 单词数量(仅在 valid 为 true 时) | |
reason | string | 验证失败原因(仅在 valid 为 false 时) |
重复检测结果类型
| 字段 | 类型 | 描述 |
|---|---|---|
id | string | 重复出现的 ID 值 |
count | number | 出现次数 |
验证流程图
graph TD
A[输入字符串] --> B{包含大写字符?}
B -->|是| E[返回 invalid: contains uppercase characters]
B -->|否| C{包含无效字符?}
C -->|是| F[返回 invalid: contains invalid characters]
C -->|否| D{格式可解析?}
D -->|否| G[返回 invalid: unrecognized format]
D -->|是| H{所有单词在词表中?}
H -->|否| I[返回 invalid: unknown words]
H -->|是| J[返回 valid: true]
style J fill:#90EE90
style E fill:#FFB6C1
style F fill:#FFB6C1
style G fill:#FFB6C1
style I fill:#FFB6C1架构关系图
graph LR
A[用户输入] --> B[parse 函数]
A --> C[validate 函数]
B --> D{解析成功?}
D -->|否| E[返回 null]
D -->|是| F[返回解析结果]
C --> G{多层验证}
G --> H[大写检测]
G --> I[字符检测]
G --> J[格式解析]
G --> K[词表验证]
F --> L[别名映射系统]
F --> M[确定性生成]
C --> N[重复检测]
H --> O{通过?}
I --> P{通过?}
J --> Q{通过?}
K --> R{通过?}
O -->|否| S[验证失败]
P -->|否| S
Q -->|否| S
R -->|否| S
O -->|是| T{下一检测}
P -->|是| T
Q -->|是| T
R -->|是| U[验证成功]依赖关系
解析与验证模块的正常运作依赖于以下核心组件:单词列表(WORDLIST)提供词汇验证所需的参考数据,src/wordlist.ts 导出包含 4096 个有效单词的冻结数组;密码学模块(crypto)提供 selectRandomWords 函数用于别名映射中的随机单词选择。资料来源:src/alias.ts:2
验证选项使用 Zod 进行模式验证,确保用户输入的参数符合预期的类型和约束。资料来源:package.json:29-30
使用建议
在实际项目中集成解析与验证功能时,建议遵循以下最佳实践。首先,对于需要频繁验证的场景(如 API 输入验证),可以在应用启动时创建单词集合(Set)以加速查找操作,尽管 id-agent 内部已经优化了验证流程。
其次,在使用 detectDuplicates 检测重复时,正则表达式的设计应当精确匹配目标 ID 格式,避免误匹配其他类似格式的字符串。建议为不同类型的 ID 使用专用的模式定义。
第三,别名映射系统适合在 LLM 对话场景中使用,通过 createAliasMap 创建的映射可以在多轮对话中复用,减少令牌消耗。确保在对话开始时注册所有需要处理的标识符,然后在对话结束时使用 restore 方法恢复原始值。
最后,对于安全敏感的应用场景,验证模块应作为防御性编程的一部分,在接收外部输入的 ID 时始终调用 validate 函数进行检查,切勿仅依赖格式解析结果假设 ID 有效。
资料来源:src/parse.ts:1-30
词表设计
id-agent 的词表(Wordlist)是生成人类可读 ID 的核心组件,由 4096 个精心筛选的英文单词组成。本文档详细介绍词表的设计目标、构建流程、质量标准和使用方式。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
id-agent 的词表是一个经过多轮筛选的 4096 词集合,每个单词恰好对应 o200k_base 分词器中的一个 BPE token。这意味着使用该词表生成的 ID 在 token 效率上远优于传统的 UUID 或十六进制字符串。
词表的核心设计原则:
- 熵值保证:每个单词贡献 12 bits 熵值(log₂(4096) = 12)
- 分词友好:所有单词在主流 LLM 分词器中都是单 token
- 可读性:单词长度控制在 3-6 个字符
- 安全性:过滤了冒犯性词汇和同音异义词
- 不可变性:词表数组被冻结(frozen),不可修改
资料来源:src/wordlist.ts:1
构建流程
词表的构建是一个六阶段的筛选过程,每一阶段都有明确的质量目标:
graph TD
A[o200k_base 词汇表] --> B[长度过滤 3-6字符]
B --> C[系统词典验证]
C --> D[BPE 单token验证]
D --> E[连字符前缀验证]
E --> F[冒犯词过滤]
F --> G[同音异义词去重]
G --> H[4096词最终词表]第一阶段:候选词提取
从 o200k_base BPE 词汇表中提取所有 3-6 个小写字母组成的单词作为候选词。这一阶段通常产生数万个候选词。
const parts = o200k_base.bpe_ranks.split(' ')
const candidateSet = new Set<string>()
for (const b64 of parts) {
if (!b64.trim()) continue
const bytes = Buffer.from(b64, 'base64')
const text = bytes.toString('utf-8')
const word = text.startsWith(' ') ? text.slice(1) : text
if (/^[a-z]{3,6}$/.test(word)) candidateSet.add(word)
}
资料来源:scripts/build-wordlist.ts:45-58
第二阶段:词典过滤
使用系统词典 /usr/share/dict/words 验证候选词的有效性,确保每个单词都是真实存在的英文单词。
const dictFiltered = candidates.filter((w) => dictWords.has(w))
资料来源:scripts/build-wordlist.ts:69-78
第三阶段:单 Token 验证
使用 js-tiktoken 库验证每个单词在 o200k_base 分词器中是否恰好编码为单个 token。这是确保 token 效率的关键步骤。
const enc = new Tiktoken(o200k_base)
const singleToken = dictFiltered.filter((w) => enc.encode(w).length === 1)
资料来源:scripts/build-wordlist.ts:79-82
第四阶段:连字符前缀验证
验证单词在添加连字符前缀后仍保持高效编码(不超过 2 个 token)。这是为了确保生成的 ID(如 storm-delta-stone)在 LLM 上下文中保持 token 效率。
const hyphenValid = singleToken.filter((w) => enc.encode('-' + w).length <= 2)
资料来源:scripts/build-wordlist.ts:83-85
第五阶段:冒犯词过滤
使用 obscenity 库检测并过滤冒犯性词汇,同时维护一个手动屏蔽列表:
const MANUAL_BLOCKLIST = [
'ass', 'crap', 'damn', 'hell', 'slut', 'rape', 'kill', 'die',
'fag', 'gay', 'drug', 'dumb', 'lame', 'nazi', 'pimp', 'porn',
'poop', 'puke', 'sexy', 'sick', 'anus', 'cunt', 'dick', 'tit',
'tits', 'boob', 'piss', 'shit', 'cock', 'whore', 'bitch',
'bastard', 'bloody', 'bugger', 'tosser', 'wank', 'twat',
'arse', 'shag', 'meth', 'heroin', 'crack', 'coke', 'weed',
]
const matcher = new RegExpMatcher({
...englishDataset.build(),
...englishRecommendedTransformers,
})
const cleanWords = hyphenValid.filter((w) => {
if (manualBlockSet.has(w)) return false
if (matcher.hasMatch(w)) return false
return true
})
资料来源:scripts/build-wordlist.ts:87-106
第六阶段:同音异义词去重
过滤同音异义词(homophones),避免因拼写相似造成的混淆:
const HOMOPHONE_GROUPS = [
['air', 'heir'],
['ate', 'eight'],
['bare', 'bear'],
['be', 'bee'],
['blue', 'blew'],
['buy', 'by', 'bye'],
['cell', 'sell'],
['cent', 'sent', 'scent'],
['dear', 'deer'],
['deer', 'dear'],
['die', 'dye'],
['eye', 'i'],
['fair', 'fare'],
['faze', 'phase'],
['find', 'fined'],
['flea', 'flee'],
['flew', 'flu', 'flue'],
['for', 'fore', 'four'],
['gait', 'gate'],
['greek', 'greak'],
['hair', 'hare'],
['hare', 'hair'],
['hall', 'haul'],
['heal', 'heel', 'heil'],
['hear', 'here'],
['him', 'hym'],
['hym', 'him'],
['isle', 'aisle'],
['knead', 'kneed', 'need'],
['knew', 'gnu'],
['know', 'no'],
['leach', 'leech'],
['loan', 'lone'],
['mail', 'male'],
['main', 'mane', 'mean'],
['meat', 'meet', 'mete'],
['medal', 'meddle', 'metal'],
['mede', 'meed'],
['miner', 'minor'],
['missed', 'mist'],
['mist', 'missed'],
['naval', 'navel'],
['nude', 'newed'],
['overdo', 'overdue'],
['pail', 'pale'],
['pain', 'pane'],
['pair', 'pear', 'pare'],
['palate', 'palette', 'pallet'],
['pare', 'pair', 'pear'],
['passed', 'past'],
['past', 'passed'],
['paten', 'pattern'],
['pause', 'paws', 'pores', 'pours'],
['peace', 'piece'],
['pear', 'pair', 'pare'],
['peer', 'pier'],
['pores', 'pause', 'paws', 'pours'],
['pour', 'pore', 'paw'],
['pours', 'pause', 'paws', 'pores'],
['pray', 'prey'],
['principal', 'principle'],
['rain', 'reign', 'rein'],
['raise', 'rays', 'raze', 'reis'],
['rapt', 'wrapped'],
['reign', 'rain', 'rein'],
['rein', 'rain', 'reign'],
['rider', 'writer'],
['right', 'rite', 'write', 'wright'],
['road', 'rode', 'rowed'],
['roe', 'row'],
['role', 'roll'],
['roux', 'rue'],
['rows', 'rose'],
['rye', 'wry'],
['sail', 'sale'],
['scene', 'seen'],
['sea', 'see', 'si'],
['seas', 'sees', 'seize'],
['seize', 'seas', 'sees'],
['senses', 'censes'],
['sew', 'sough', 'so'],
['sewn', 'sown'],
['shear', 'sheer'],
['shoe', 'shoo'],
['side', 'sighed'],
['sighed', 'side'],
['sign', 'sine'],
['sine', 'sign'],
['sla', 'sley'],
['sleigh', 'slay'],
['slight', 'sleight'],
['sley', 'sla'],
['sole', 'soul'],
['some', 'sum'],
['son', 'sun'],
['sown', 'sewn'],
['stare', 'stair'],
['stair', 'stare'],
['stake', 'steak'],
['steak', 'stake'],
['steal', 'steel'],
['steel', 'steal'],
['step', 'steppe'],
['steppe', 'step'],
['stew', 'stoo', 'stu'],
['stu', 'stew', 'stoo'],
['stoo', 'stew', 'stu'],
['suite', 'sweet', 'swete', 'sweat'],
['sweet', 'suite', 'swete', 'sweat'],
['swete', 'suite', 'sweet', 'sweat'],
['tack', 'tact'],
['tale', 'tail', 'tael'],
['tail', 'tale', 'tael'],
['tare', 'tear', 'tier'],
['team', 'teem'],
['tear', 'tare', 'tier'],
['teem', 'team'],
['teems', 'themes'],
['their', 'there', 'theyre'],
['threw', 'through'],
['throe', 'throw'],
['throne', 'thrown'],
['thrown', 'throne'],
['tide', 'tied'],
['tied', 'tide'],
['tier', 'tare', 'tear'],
['tile', 'trial'],
['to', 'too', 'two'],
['told', 'tolled'],
['tolled', 'told'],
['tone', 'towan'],
['towan', 'tone'],
['tray', 'trey'],
['tread', 'tred'],
['tred', 'tread'],
['treek', 'trig'],
['trig', 'treek'],
['troth', 'truth'],
['vane', 'vain', 'vein'],
['vain', 'vane', 'vein'],
['vein', 'vain', 'vane'],
['wade', 'weighed'],
['wait', 'weight'],
['war', 'wore'],
['waste', 'waist'],
['way', 'weigh'],
['weak', 'week'],
['wear', 'where'],
['wood', 'would'],
]
资料来源:scripts/build-wordlist.ts:32-43
质量标准
词表必须满足以下质量标准:
| 标准 | 要求 | 验证方法 |
|---|---|---|
| 词汇数量 | 恰好 4096 个 | 构建脚本最终输出验证 |
| 单词长度 | 3-6 个字符 | 正则表达式 /^[a-z]{3,6}$/ |
| 分词效率 | 单 token(o200k_base) | js-tiktoken 编码测试 |
| 连字符效率 | ≤2 tokens(-prefix) | js-tiktoken 编码测试 |
| 词库有效性 | 存在于系统词典 | /usr/share/dict/words |
| 安全性 | 无冒犯词 | obscenity 库 + 手动列表 |
| 可读性 | 无同音异义词 | 手动同音词组列表 |
| 不可变性 | frozen 数组 | Object.isFrozen(WORDLIST) |
词表导出与使用
导出格式
词表以 TypeScript 常量形式导出,位于 src/wordlist.ts:
export const WORDLIST = [
'abb',
'abel',
'abets',
'abhor',
'abide',
'abler',
// ... 共 4096 个单词
] as const
// 冻结数组确保不可变
Object.freeze(WORDLIST)
资料来源:src/wordlist.ts:1
使用方式
词表在内部模块中被广泛使用:
随机 ID 生成(用于 createAliasMap 等功能):
export function createAliasMap(opts: AliasOptions): AliasMap {
const { words, allowCollision } = AliasOptionsSchema.parse(opts)
// ...
alias = selectRandomWords(words, WORDLIST).join('-')
// ...
}
资料来源:src/alias.ts:1-25
确定性 ID 生成(使用 HMAC-SHA256):
export async function deterministicId(input: string, opts?: IdAgentFromOptions): Promise<string> {
// ...
for (let i = 0; i < words; i++) {
selected[i] = WORDLIST[view.getUint16(i * 2) % 4096]
}
return formatId(validated.prefix, selected.join('-'))
}
资料来源:src/deterministic.ts:1-38
ID 解析与验证
词表也用于验证和解析生成的 ID:
export function validate(id: string): ValidateResult {
// ...
if (parsed.words.length > 0) {
const invalid = parsed.words.filter(w => !wordSet.has(w))
if (invalid.length > 0) {
return { valid: false, reason: `unknown words: ${invalid.join(', ')}` }
}
}
// ...
}
资料来源:src/parse.ts:1-25
验证脚本
validate-wordlist.ts
构建完成后,使用验证脚本确认词表质量:
// 验证单 token 编码
const singleTokenFails = allWords.filter(w => enc.encode(w).length !== 1)
// 验证连字符前缀编码
const hyphenFails = singleTokenFails.filter(w => enc.encode('-' + w).length > 2)
check(`All words are single BPE tokens (failures: ${singleTokenFails.length})`, singleTokenFails.length === 0)
check(`All words pass hyphen-prefix test (failures: ${hyphenFails.length})`, hyphenFails.length === 0)
资料来源:scripts/validate-wordlist.ts:1-20
运行验证
pnpm run validate
词表设计决策
为什么选择 4096 个单词?
- 数学基础:4096 = 2¹²,每个单词贡献恰好 12 bits 熵值
- 空间效率:4096 个条目可存储在 12 位索引中(2 字节)
- 映射便利:确定性 ID 生成中,HMAC-SHA256 输出的 16 位字可映射到 4096 个单词(取模运算)
为什么限制 3-6 个字符?
- BPE 兼容性:在这个长度范围内的单词更可能在 o200k_base 中成为单 token
- 可读性平衡:过短的单词(如 "a"、"I")增加同音词冲突;过长的单词增加 token 消耗
- ID 紧凑性:较短的单词使生成的 ID 更紧凑易读
为什么过滤同音异义词?
在口语交流或听觉场景下,同音异义词(如 "there"/"their"/"they're")会造成混淆。id-agent 主要用于 AI 上下文,但保持这种过滤可以:
- 减少口头沟通歧义
- 避免拼写相似性导致的视觉混淆
- 提高 ID 的整体可读性
总结
id-agent 的词表设计是一个精心策划的多阶段流程,确保最终产出的 4096 个单词满足分词效率、可读性、安全性和不可变性等多重质量标准。这个词表是实现高 token 效率和人类可读 ID 生成的核心基础设施。
词表在运行时被冻结,任何对 ID 格式的验证都依赖词表内容,这种设计保证了系统的一致性和可预测性。
资料来源:src/wordlist.ts:1
加密模块
id-agent 的加密模块是整个库的核心安全基础,负责生成密码学安全的随机数并将其映射到人类可读的单词列表。该模块采用双重加密策略:
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
id-agent 的加密模块是整个库的核心安全基础,负责生成密码学安全的随机数并将其映射到人类可读的单词列表。该模块采用双重加密策略:
- 随机 ID 生成:使用 Web Crypto API 的 CSPRNG(密码学安全伪随机数生成器)
- 确定性 ID 生成:使用 HMAC-SHA256 哈希函数实现输入到 ID 的确定性映射
加密模块的核心价值在于确保 ID 的唯一性和不可预测性,同时保持人类可读性和令牌效率。
架构设计
graph TD
A[输入] --> B{ID类型选择}
B -->|随机| C[随机ID生成流程]
B -->|确定性| D[确定性ID生成流程]
C --> E[crypto.getRandomValues]
E --> F[从WORDLIST映射]
F --> G[格式化输出]
D --> H[TextEncoder编码输入]
H --> I[HMAC-SHA256签名]
I --> J[DataView读取哈希]
J --> K[取模映射到WORDLIST]
K --> G
G --> L[最终ID格式<br/>如: storm-delta-stone]随机 ID 生成
核心实现
随机 ID 生成依赖浏览器的 crypto.getRandomValues() 方法,这是 Web Crypto API 提供的密码学安全随机数接口。 资料来源:src/generate.ts:6-12
function createIdAgent(): IdAgent {
const fn = ((opts?: IdAgentOptions): string => {
const validated = IdAgentOptionsSchema.parse(opts ?? {})
const words = validated.words ?? 8
const selected = selectRandomWords(words, WORDLIST)
return formatId(validated.prefix, selected.join('-'))
}) as IdAgent
// ...
}
随机词选择机制
selectRandomWords 函数从 4096 个单词的词表中选择随机单词。该函数内部调用 crypto.getRandomValues() 获取密码学安全的随机字节,然后通过取模运算将随机数映射到词表索引。 资料来源:src/generate.ts:4-5
安全性保证
随机 ID 生成的安全性来源于:
| 保障措施 | 说明 |
|---|---|
| CSPRNG | 使用浏览器原生的 crypto.getRandomValues(),确保随机数不可预测 |
| 均匀分布 | 取模运算确保每个词被选中的概率完全相等 |
| 独立选择 | 每个词的位置独立随机选择,互不影响 |
确定性 ID 生成
核心实现
确定性 ID 生成使用 HMAC-SHA256 算法,通过 Web Crypto API 实现。相同的输入总是产生相同的 ID,适用于需要可重现性的场景。 资料来源:src/deterministic.ts:1-10
export async function deterministicId(input: string, opts?: IdAgentFromOptions): Promise<string> {
if (!input || typeof input !== 'string') {
throw new Error('input must be a non-empty string')
}
const validated = IdAgentFromOptionsSchema.parse(opts ?? {})
const words = validated.words ?? 8
if (!globalThis.crypto?.subtle) {
throw new Error('idAgent.from() requires Web Crypto API (crypto.subtle). Use HTTPS in browsers.')
}
// ...
}
HMAC-SHA256 签名流程
graph LR
A[输入字符串] --> B[TextEncoder]
B --> C[UTF-8字节]
C --> D[crypto.subtle.importKey]
D --> E[HMAC密钥]
C --> F[crypto.subtle.sign]
E --> F
F --> G[256位签名]
G --> H[DataView读取]
H --> I[每16位取模4096]
I --> J[映射到WORDLIST]关键步骤解析
- 密钥导入:使用命名空间(默认 "id-agent")作为 HMAC 密钥,确保不同应用的输出不会冲突 资料来源:src/deterministic.ts:16-20
- 签名生成:对输入字符串进行 HMAC-SHA256 签名,输出 256 位(32 字节)的签名 资料来源:src/deterministic.ts:21-22
- 词表映射:从签名中每 16 位(2 字节)读取一个无符号整数,对 4096 取模得到词表索引 资料来源:src/deterministic.ts:23-27
const sig = await globalThis.crypto.subtle.sign('HMAC', key, enc.encode(input))
const view = new DataView(sig)
const selected: string[] = new Array(words)
for (let i = 0; i < words; i++) {
selected[i] = WORDLIST[view.getUint16(i * 2) % 4096]
}
命名空间隔离
确定性 ID 支持自定义命名空间实现域分离:
const id1 = await idAgent.from('[email protected]')
const id2 = await idAgent.from('[email protected]', { namespace: 'different-app' })
// id1 !== id2
这允许同一输入在不同命名空间下产生不同的确定性 ID。 资料来源:src/deterministic.ts:12
别名映射模块
别名映射模块利用加密随机数生成短别名,用于减少 LLM 上下文中的令牌消耗。 资料来源:src/alias.ts:1-8
别名生成算法
graph TD
A[原始ID] --> B{ID已存在?}
B -->|是| C[返回已有别名]
B -->|否| D[生成新别名]
D --> E[selectRandomWords]
E --> F[从WORDLIST选择词]
F --> G{别名冲突?}
G -->|是| D
G -->|否| H[存储forward/reverse映射]
H --> I[返回别名]加密安全保障
别名映射同样使用 selectRandomWords 从 CSPRNG 获取随机数,确保别名不可预测。即使原始 ID 已知,也无法推断别名(除非存在映射表)。 资料来源:src/alias.ts:2
export function createAliasMap(opts: AliasOptions): AliasMap {
const { words, allowCollision } = AliasOptionsSchema.parse(opts)
const forward = new Map<string, string>()
const reverse = new Map<string, string>()
// ...
do {
alias = selectRandomWords(words, WORDLIST).join('-')
} while (reverse.has(alias) && !allowCollision)
// ...
}
词表设计
词表规格
| 属性 | 值 | 说明 |
|---|---|---|
| 词数 | 4096 | 2^12,便于位运算映射 |
| 每词熵 | 12 bits | log2(4096) = 12 |
| 单词字符数 | 3-6 字符 | 经 o200k_base 分词器验证 |
| 令牌效率 | 每词 1 token | 针对 o200k_base 优化 |
词表约束
词表经过多重过滤确保安全性和可用性:
- 词典验证:所有单词必须存在于系统词典
/usr/share/dict/words - 同音词过滤:移除如
wait/weight、weak/week等易混淆的同音词 - 冒犯性词汇移除:使用
obscenity库过滤不适当词汇 - 单令牌验证:每个词在 o200k_base 分词器中恰好为 1 个 token
API 参考
随机 ID 生成
const id = idAgent() // 默认 8 词 ID
const id5 = idAgent({ words: 5 }) // 5 词 ID
const idPrefixed = idAgent({ prefix: 'user', words: 4 })
确定性 ID 生成
const id = await idAgent.from('[email protected]')
const idCustom = await idAgent.from('[email protected]', {
namespace: 'my-app',
prefix: 'user',
words: 5
})
别名映射
const aliases = createAliasMap({ words: 3 })
const alias = aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
const original = aliases.get('storm-delta-stone')
安全注意事项
| 场景 | 建议 |
|---|---|
| 浏览器环境 | 确保使用 HTTPS,Web Crypto API 在不安全上下文中不可用 |
| 服务端环境 | Node.js 18+ 已原生支持 Web Crypto API |
| 确定性 ID 命名空间 | 生产环境应使用唯一的命名空间避免冲突 |
| 别名碰撞 | 默认情况下别名生成会避免碰撞,可通过 allowCollision: true 禁用 |
性能特性
- 随机 ID 生成:同步操作,约 0.1-0.5ms
- 确定性 ID 生成:异步操作,约 1-5ms(首次调用需初始化 HMAC 密钥)
- 别名映射:O(1) 查找和插入
与 UUID 的对比
| 特性 | UUID v4 | id-agent (8词) |
|---|---|---|
| 熵 | 122 bits | 96 bits |
| 令牌数 | ~23 | ~11 |
| 令牌节省 | - | ~52% |
| 50%碰撞阈值 | ~2.7 * 10^18 | ~300 trillion |
| 人类可读性 | 低 | 高 |
来源:https://github.com/vostride/id-agent / 项目说明书
Zod 验证架构
id-agent 使用 Zod 作为核心验证库,版本为 4.3.6,构建了一套完整的输入验证体系。该验证架构覆盖了所有公开 API 的用户输入,确保数据类型安全、参数合法,并提供清晰的错误信息。
继续阅读本节完整说明和来源证据。
概述
id-agent 使用 Zod 作为核心验证库,版本为 4.3.6,构建了一套完整的输入验证体系。该验证架构覆盖了所有公开 API 的用户输入,确保数据类型安全、参数合法,并提供清晰的错误信息。
核心设计目标:
- 在开发阶段捕获无效输入,防止运行时错误
- 提供人类可读的错误消息,帮助开发者快速定位问题
- 保持类型安全,实现编译时与运行时的双重保障
- 通过模式复用减少验证代码重复
依赖配置:
"dependencies": {
"zod": "4.3.6"
}
资料来源:package.json:25-27
资料来源:package.json:25-27
别名系统
别名系统(Alias System)是 id-agent 提供的一种高效令牌压缩机制,旨在将长标识符(如 UUID)映射为短小的、基于单词的别名,以便在 LLM 上下文中的传输。系统支持完整的双向映射、文本替换和还原功能。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
别名系统(Alias System)是 id-agent 提供的一种高效令牌压缩机制,旨在将长标识符(如 UUID)映射为短小的、基于单词的别名,以便在 LLM 上下文中的传输。系统支持完整的双向映射、文本替换和还原功能。
别名系统通过 createAliasMap() 工厂函数创建,返回一个 AliasMap 实例,该实例提供 set()、get()、replace()、restore() 和 entries() 等核心方法。
资料来源:src/alias.ts:3-10
核心概念
设计目标
别名系统解决了 LLM 应用中的两个关键问题:
- 令牌效率:长 UUID(如
8cdda07b-85d2-459c-8a2a-83c8f9245dbe)在 BPE 分词器中需要约 23 个令牌,而 3 个单词的别名(如storm-delta-stone)仅需约 5 个令牌,节省约 78% 的令牌开销。
- 可读性:单词形式的 ID 更容易在调试日志、API 响应和 LLM 输出中理解和引用。
资料来源:README.md
工作原理
别名系统内部维护两个 Map 数据结构:
- 正向映射(forward):存储原始 ID → 别名
- 反向映射(reverse):存储别名 → 原始 ID
graph LR
A[原始ID<br/>8cdda07b-85d2-...] -->|set| B[正向Map]
B -->|生成| C[别名<br/>storm-delta-stone]
C -->|存储| D[反向Map]
D -->|get| A资料来源:src/alias.ts:8-10
API 参考
createAliasMap(options?)
创建别名映射实例。
import { createAliasMap } from 'id-agent'
const aliases = createAliasMap({ words: 3 })
#### 参数选项
| 参数 | 类型 | 默认值 | 必填 | 描述 |
|---|---|---|---|---|
words | number | 8 | 否 | 每个别名的单词数量(1-16) |
allowCollision | boolean | false | 否 | 是否允许别名冲突 |
资料来源:src/alias.ts:3-6
AliasMap 实例方法
#### set(original: string): string
为给定的原始 ID 生成并存储一个别名。相同原始 ID 多次调用返回相同的别名。
const alias1 = aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
// => "storm-delta-stone"
const alias2 = aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
// => "storm-delta-stone" (相同)
资料来源:src/alias.ts:12-18
#### get(alias: string): string | undefined
根据别名查询对应的原始 ID。
aliases.get('storm-delta-stone')
// => "8cdda07b-85d2-459c-8a2a-83c8f9245dbe"
资料来源:src/alias.ts:20-22
#### replace(text: string, options?): string
将文本中所有匹配指定模式的原始 ID 替换为对应的别名。
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"
| 参数 | 类型 | 描述 |
|---|---|---|
text | string | 要进行替换的文本 |
options.pattern | RegExp | 匹配原始 ID 的正则表达式 |
资料来源:src/alias.ts:24-37
#### restore(text: string): string
将文本中所有别名还原为原始 ID。
const restored = aliases.restore('Process storm-delta-stone then cloud-train-scope')
// => "Process 8cdda07b-85d2-459c-8a2a-83c8f9245dbe then 6ba7b810-9dad-11d1-80b4-00c04fd430c8"
资料来源:src/alias.ts:39-41
#### entries(): [original, alias][]
返回所有映射对的数组,格式为 [原始ID, 别名]。
for (const [original, alias] of aliases.entries()) {
console.log(`${original} => ${alias}`)
}
⚠️ 返回顺序是[original, alias],而非[alias, original]。如需通过别名查询原始 ID,请使用get(alias)方法。
资料来源:README.md
典型使用场景
LLM 上下文压缩
在向 LLM 发送请求前,将长 UUID 替换为短别名,并在处理完成后还原:
sequenceDiagram
participant App as 应用
participant AliasMap as AliasMap
participant LLM as LLM
participant Output as 输出
App->>AliasMap: set(uuid1), set(uuid2)
App->>AliasMap: replace(requestText)
App->>LLM: 发送压缩后的请求
LLM-->>App: 返回引用别名的响应
App->>AliasMap: restore(responseText)
App->>Output: 输出还原后的结果const aliases = createAliasMap({ words: 3 })
// 1. 准备阶段:注册所有需要的 ID
aliases.set('8cdda07b-85d2-459c-8a2a-83c8f9245dbe')
aliases.set('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
// 2. 发送给 LLM 前:替换
const llmRequest = await sendToLLM(aliases.replace(prompt, {
pattern: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi
}))
// 3. 处理 LLM 返回后:还原
const finalOutput = aliases.restore(llmOutput)
资料来源:src/alias.ts:24-41
与重复检测结合
别名系统可与 detectDuplicates 功能结合使用,用于在 LLM 输出中检测重复的 ID:
import { createAliasMap, detectDuplicates } from 'id-agent'
const aliases = createAliasMap({ words: 3 })
// 检测文本中的重复 ID
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 }]
资料来源:src/detect.ts:1-21
内部实现
别名生成算法
别名通过 selectRandomWords 函数从词表(WORDLIST)中随机选择指定数量的单词生成:
alias = selectRandomWords(words, WORDLIST).join('-')
每个单词贡献 12 比特熵度(因为词表包含 4096 = 2¹² 个单词)。使用 3 个单词的别名提供 36 比特熵度。
| 单词数 | 熵度 | ID 空间 | 典型令牌数 | vs UUID 节省 |
|---|---|---|---|---|
| 3 | 36 bits | 6.9 × 10¹⁰ | ~5 | 78% |
| 5 | 60 bits | 1.2 × 10¹⁸ | ~8 | 65% |
| 8 | 96 bits | 7.9 × 10²⁸ | ~11 | 52% |
资料来源:src/wordlist.ts 和 README.md
替换正则构建
replace() 方法内部按长度降序排序所有已注册的原始 ID,确保较长的 ID 优先匹配,避免部分匹配问题:
function buildReplacementRegex(keys: string[]): RegExp | null {
if (keys.length === 0) return null
const sorted = [...keys].sort((a, b) => b.length - a.length)
const escaped = sorted.map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
return new RegExp(escaped.join('|'), 'g')
}
例如,如果同时注册了 abc 和 abcd,会优先匹配 abcd。
资料来源:src/alias.ts:8-11
冲突处理
默认情况下,set() 方法会检查生成的别名是否已存在(通过 reverse.has(alias))。如果发生冲突,会重新生成:
do {
alias = selectRandomWords(words, WORDLIST).join('-')
} while (reverse.has(alias) && !allowCollision)
通过设置 allowCollision: true 可以禁用此检查,允许不同的原始 ID 共享相同别名(不推荐)。
资料来源:src/alias.ts:14-16
最佳实践
词数选择指南
| 使用场景 | 建议词数 | 熵度 | 适用规模 |
|---|---|---|---|
| 开发/测试 | 3 | 36 bits | < 309K items |
| 小型应用 | 5 | 60 bits | < 1B items |
| 生产 SaaS | 5 | 60 bits | < 1B items |
| 高流量/分布式 | 8 (默认) | 96 bits | < 300T items |
生命周期管理
graph TD
A[创建 AliasMap] --> B[注册所有 ID]
B --> C{处理}
C -->|发送 LLM | D[replace]
C -->|接收响应 | E[restore]
D --> F[LLM 处理]
F --> E
E --> G{更多请求?}
G -->|是| C
G -->|否| H[销毁 AliasMap]- 预先注册:在处理任何文本前,先通过
set()注册所有需要用到的原始 ID - 重用实例:在整个会话期间复用同一个 AliasMap 实例
- 统一模式:使用统一的正则表达式进行
replace()和restore()
资料来源:src/alias.ts:12-22
错误处理
别名系统使用 Zod 进行配置验证:
const { words, allowCollision } = AliasOptionsSchema.parse(opts)
无效选项会抛出 ZodError,并附带描述性错误信息。有效的 words 范围为 1-16。
资料来源:src/alias.ts:4
与其他模块的关系
graph TB
Alias[别名系统<br/>src/alias.ts] --> Crypto[加密模块<br/>src/crypto.ts]
Alias --> Wordlist[词表<br/>src/wordlist.ts]
Alias --> Parse[解析模块<br/>src/parse.ts]
Parse --> Validate[验证模块<br/>src/validate.ts]
Detect[重复检测<br/>src/detect.ts] -.->|组合使用| Alias
classDef primary fill:#e1f5fe
class Alias primary- 依赖
selectRandomWords:别名生成使用加密安全的随机数生成器 - 依赖 WORDLIST:从 4096 个预验证单词中选择
- 可与
detectDuplicates组合:检测 LLM 输出中的重复别名
资料来源:src/alias.ts:2-3 和 src/parse.ts:1-3
资料来源:src/alias.ts:3-10
重复检测
重复检测(Duplicate Detection)是 id-agent 提供的核心功能之一,用于扫描文本内容中的重复 ID。该功能设计为纯函数,不依赖文件系统访问,适用于在 LLM 上下文中检测重复标识符、验证数据一致性以及在分布式系统中识别潜在的 ID 冲突问题。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
重复检测(Duplicate Detection)是 id-agent 提供的核心功能之一,用于扫描文本内容中的重复 ID。该功能设计为纯函数,不依赖文件系统访问,适用于在 LLM 上下文中检测重复标识符、验证数据一致性以及在分布式系统中识别潜在的 ID 冲突问题。
重复检测模块位于 src/detect.ts,通过正则表达式模式匹配来识别文本中出现的重复标识符。资料来源:src/detect.ts:1-24
工作原理
核心算法流程
重复检测采用基于正则表达式的模式匹配策略,通过以下步骤完成重复 ID 的识别:
graph TD
A[输入: pattern + text] --> B{text 类型判断}
B -->|string| C[转换为数组: text]
B -->|string[]| D[直接使用数组]
C --> E[初始化 Map 和 RegExp]
D --> E
E --> F[遍历每个文本]
F --> G[重置 lastIndex]
G --> H[执行正则匹配]
H --> I{找到匹配?}
I -->|是| J[更新 Map 计数]
I -->|否| K{还有文本?}
J --> K
K -->|是| F
K -->|否| L[过滤 count > 1]
L --> M[返回 Duplicate 数组]
I -->|否| K关键设计决策
- 纯函数设计:不产生副作用,不访问外部状态,确保测试可重复性和并发安全性
- 自动补全全局标志:如果用户提供的正则表达式缺少
g标志,函数会自动添加,确保能够匹配所有出现位置 - lastIndex 重置:每次处理新文本前重置正则表达式的
lastIndex,避免跨文本匹配时的状态污染 - 灵活输入支持:支持单个字符串或字符串数组作为输入源
API 参考
detectDuplicates 函数签名
function detectDuplicates(opts: DetectOptions): Duplicate[]
类型定义
| 类型 | 描述 |
|---|---|
DetectOptions | 检测配置选项,包含匹配模式和待检测文本 |
Duplicate | 重复检测结果,包含 ID 字符串和出现次数 |
参数配置
| 参数 | 类型 | 必填 | 描述 | |
|---|---|---|---|---|
pattern | RegExp | 是 | 用于匹配 ID 的正则表达式模式 | |
text | `string \ | string[]` | 是 | 待扫描的文本内容,可以是单个字符串或字符串数组 |
返回值
返回 Duplicate[] 数组,每个元素包含:
| 字段 | 类型 | 描述 |
|---|---|---|
id | string | 重复的 ID 字符串 |
count | number | 该 ID 出现的次数(仅包含 count > 1 的项) |
使用示例
基本用法:检测字符串中的重复 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 }]
资料来源:README.md
多文本检测:分析代码仓库中的重复项
import { detectDuplicates } from 'id-agent'
const dupes = detectDuplicates({
pattern: /task_[a-z]+(?:-[a-z]+)+/,
text: [
'const x = "task_red-fox-run"',
'const y = "task_red-fox-run"',
],
})
结合 UUID 模式检测
import { detectDuplicates } from 'id-agent'
const dupes = detectDuplicates({
pattern: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,
text: [
'Task A: 8cdda07b-85d2-459c-8a2a-83c8f9245dbe',
'Task B: 8cdda07b-85d2-459c-8a2a-83c8f9245dbe', // 重复
'Task C: 6ba7b810-9dad-11d1-80b4-00c04fd430c8',
],
})
// 返回值: [{ id: '8cdda07b-85d2-459c-8a2a-83c8f9245dbe', count: 2 }]
实现细节
源代码解析
以下为核心实现代码(资料来源:src/detect.ts:5-24):
export function detectDuplicates(opts: DetectOptions): Duplicate[] {
const texts = typeof opts.text === 'string' ? [opts.text] : opts.text
const idMap = new Map<string, number>()
const re = new RegExp(
opts.pattern.source,
opts.pattern.flags.includes('g') ? opts.pattern.flags : opts.pattern.flags + 'g',
)
for (const text of texts) {
re.lastIndex = 0
let match
while ((match = re.exec(text)) !== null) {
idMap.set(match[0], (idMap.get(match[0]) ?? 0) + 1)
}
}
return [...idMap.entries()]
.filter(([, count]) => count > 1)
.map(([id, count]) => ({ id, count }))
}
全局标志处理逻辑
函数自动确保正则表达式携带 g(全局)标志:
graph LR
A[输入 pattern] --> B{flags 包含 'g'?}
B -->|是| C[保持原 flags]
B -->|否| D[追加 'g' 标志]
C --> E[创建 RegExp]
D --> E与其他模块的集成
与别名映射系统配合使用
重复检测可以与 createAliasMap 配合使用,用于验证映射前后的 ID 一致性:
| 功能 | 函数 | 用途 |
|---|---|---|
| 缩短长 ID | createAliasMap.replace() | 减少 LLM 上下文中的 token 消耗 |
| 恢复原始 ID | createAliasMap.restore() | 还原别名映射 |
| 验证唯一性 | detectDuplicates() | 检测替换后是否存在重复别名 |
典型工作流程
graph LR
A[原始 UUID 列表] --> B[createAliasMap]
B --> C[生成别名映射]
C --> D[替换 UUID 为别名]
D --> E[发送到 LLM]
E --> F[LLM 响应]
F --> G[恢复原始 UUID]
G --> H[detectDuplicates 验证]
H --> I{存在重复?}
I -->|是| J[警告/处理冲突]
I -->|否| K[处理完成]最佳实践
正则表达式编写建议
| 建议 | 说明 | 示例 |
|---|---|---|
| 使用捕获组 | 明确指定要匹配的 ID 格式 | /task_[a-z]+(?:-[a-z]+)+/ |
| 包含边界符 | 避免部分匹配问题 | /\\b[a-f0-9-]{36}\\b/ |
| 区分大小写 | 根据 ID 规范选择标志 | i 标志用于大小写不敏感 |
| 避免过度贪婪 | 确保匹配精确的 ID 格式 | 使用 {36} 而非 + |
性能优化建议
- 预编译正则表达式:如果多次使用相同模式,预先创建正则表达式对象
- 批量处理文本:将多个文本片段合并为数组一次性处理,减少循环开销
- 精确的 pattern:避免过于宽泛的正则表达式,减少不必要的匹配尝试
注意事项
边界情况处理
| 场景 | 行为 | 说明 |
|---|---|---|
| 空字符串输入 | 返回空数组 [] | 正则匹配无结果 |
| 无匹配的文本 | 返回空数组 [] | 未发现任何匹配项 |
| 无重复项 | 返回空数组 [] | 所有 ID 都只出现一次 |
| 数组中有空字符串 | 正常处理 | 空字符串不产生匹配 |
限制说明
- 函数不验证匹配到的 ID 是否为有效的 id-agent 格式
- 如果需要验证 ID 有效性,应额外调用
validate()函数 - 正则表达式的
lastIndex属性在内部被管理,不会影响外部状态
资料来源:README.md
失败模式与踩坑日记
保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。
假设不成立时,用户拿不到承诺的能力。
新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
下游已经要求复核,不能在页面中弱化。
风险会影响是否适合普通用户安装。
Pitfall Log / 踩坑日志
项目:vostride/id-agent
摘要:发现 8 个潜在踩坑项,其中 0 个为 high/blocking;最高优先级:能力坑 - 能力判断依赖假设。
1. 能力坑 · 能力判断依赖假设
- 严重度:medium
- 证据强度:source_linked
- 发现:README/documentation is current enough for a first validation pass.
- 对用户的影响:假设不成立时,用户拿不到承诺的能力。
- 建议检查:将假设转成下游验证清单。
- 防护动作:假设必须转成验证项;没有验证结果前不能写成事实。
- 证据:capability.assumptions | hn_item:48191852 | https://news.ycombinator.com/item?id=48191852 | README/documentation is current enough for a first validation pass.
2. 维护坑 · 维护活跃度未知
- 严重度:medium
- 证据强度:source_linked
- 发现:未记录 last_activity_observed。
- 对用户的影响:新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
- 建议检查:补 GitHub 最近 commit、release、issue/PR 响应信号。
- 防护动作:维护活跃度未知时,推荐强度不能标为高信任。
- 证据:evidence.maintainer_signals | hn_item:48191852 | https://news.ycombinator.com/item?id=48191852 | last_activity_observed missing
3. 安全/权限坑 · 下游验证发现风险项
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:下游已经要求复核,不能在页面中弱化。
- 建议检查:进入安全/权限治理复核队列。
- 防护动作:下游风险存在时必须保持 review/recommendation 降级。
- 证据:downstream_validation.risk_items | hn_item:48191852 | https://news.ycombinator.com/item?id=48191852 | no_demo; severity=medium
4. 安全/权限坑 · 存在评分风险
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:风险会影响是否适合普通用户安装。
- 建议检查:把风险写入边界卡,并确认是否需要人工复核。
- 防护动作:评分风险必须进入边界卡,不能只作为内部分数。
- 证据:risks.scoring_risks | hn_item:48191852 | https://news.ycombinator.com/item?id=48191852 | no_demo; severity=medium
5. 安全/权限坑 · 来源证据:Consider an alternative wordlist
- 严重度:medium
- 证据强度:source_linked
- 发现:GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题:Consider an alternative wordlist
- 对用户的影响:可能影响授权、密钥配置或安全边界。
- 建议检查:来源问题仍为 open,Pack Agent 需要复核是否仍影响当前版本。
- 防护动作:不得脱离来源链接放大为确定性结论;需要标注适用版本和复核状态。
- 证据:community_evidence:github | cevd_c1936412c9da41088e71fab995197a90 | https://github.com/vostride/id-agent/issues/2 | 来源类型 github_issue 暴露的待验证使用条件。
6. 安全/权限坑 · 来源证据:Python port
- 严重度:medium
- 证据强度:source_linked
- 发现:GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题:Python port
- 对用户的影响:可能影响授权、密钥配置或安全边界。
- 建议检查:来源问题仍为 open,Pack Agent 需要复核是否仍影响当前版本。
- 防护动作:不得脱离来源链接放大为确定性结论;需要标注适用版本和复核状态。
- 证据:community_evidence:github | cevd_d733a90c7db44b9789900ed24e77220e | https://github.com/vostride/id-agent/issues/1 | 来源讨论提到 python 相关条件,需在安装/试用前复核。
7. 维护坑 · issue/PR 响应质量未知
- 严重度:low
- 证据强度:source_linked
- 发现:issue_or_pr_quality=unknown。
- 对用户的影响:用户无法判断遇到问题后是否有人维护。
- 建议检查:抽样最近 issue/PR,判断是否长期无人处理。
- 防护动作:issue/PR 响应未知时,必须提示维护风险。
- 证据:evidence.maintainer_signals | hn_item:48191852 | https://news.ycombinator.com/item?id=48191852 | issue_or_pr_quality=unknown
8. 维护坑 · 发布节奏不明确
- 严重度:low
- 证据强度:source_linked
- 发现:release_recency=unknown。
- 对用户的影响:安装命令和文档可能落后于代码,用户踩坑概率升高。
- 建议检查:确认最近 release/tag 和 README 安装命令是否一致。
- 防护动作:发布节奏未知或过期时,安装说明必须标注可能漂移。
- 证据:evidence.maintainer_signals | hn_item:48191852 | https://news.ycombinator.com/item?id=48191852 | release_recency=unknown
来源:Doramagic 发现、验证与编译记录