ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DeepSeek Harness 匿名用户标识设计解析:`$DSH_HOME/.anonymous-user-id` 与 OTel Resource `user.id`

DeepSeek Harness 匿名用户标识设计解析:`$DSH_HOME/.anonymous-user-id` 与 OTel Resource `user.id` DeepSeek Harness 匿名用户标识设计解析$DSH_HOME/.anonymous-user-id与 OTel Resourceuser.id【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness本文以 DeepSeek Harness 仓库中的架构决策记录Agent Note为主体结合其落盘实现与三大消费方源码完整讲解会话遥测Session Telemetry中匿名用户标识的设计权衡、存储契约与并发语义。读完你可以掌握匿名 UUID 如何被生成与持久化、为何随机而非派生是匿名性的根基、user.id为何挂在 OTel Resource 而非每条记录上以及删除文件、只读 home、多进程首启等边界情况下的行为。该标识的最终归属包是deepseek-ai/dsh-anonymous-user-id被会话遥测 OTel 后端、/feedback命令与 DeepSeek 直连请求三方共享。背景遥测已就位但缺少用户这一维度会话遥测默认被挂载进 dsh web 组合见 default-mount Note然而当时的 OTel Resource 只携带service.name/service.version完全没有用户级身份。这意味着收集端collector既无法按用户聚合记录也无法统计活跃用户数。此前唯一相关的裁决是用 hostname/本地 IP 哈希派生用户 ID的未实现提案。OTel 数据流迫切需要一种语义干净的匿名用户身份。决策总览一个 UUID、一个文件、三方共享核心决策一句话概括getOrCreateAnonymousUserId()返回$DSH_HOME/.anonymous-user-id文件中的裸 UUID 行home 由resolveDshHome解析优先级$DSH_HOME~/.dsh首次调用时铸造并持久化一个随机 UUID v4后端构造函数将其作为 Resource 的user.idOTel semconv 标准用户属性携带每个导出批次batch携带一次。该实现最初内嵌在session-telemetry-otel中因为当时不存在第二个真实消费方。/feedback后来成为第二个消费方于是 共享 ID 决策 将所有权迁移至独立包deepseek-ai/dsh-anonymous-user-id存储、匿名性、并发与丢失语义均保持不变DeepSeek 直连请求身份 则是同一 ID 的第三个消费方。设计裁决表裁决项取值理由ID 来源随机 UUID v4绝不从 hostname、网络地址或 git remote 派生派生 ID 可逆会让匿名沦为虚构存储形式.anonymous-user-id裸 UUID 行 换行无 JSON 包装身份是独立事实不应归档在某个遥测数据流的文件名/格式之下IO 形式同步 IO 以解析后文件路径为键的进程生命周期 memoOpenTelemetrySessionBackend构造函数是同步的异步会重塑插件加载流程每个进程只碰一次磁盘运行中删除文件不影响当前进程并发首启用排他创建wx写入决出胜负失败者重读胜利者的 ID覆盖常见并发场景若重读恰好落在胜者创建→写入的微秒窗口内本次运行仍可能每进程各持一个 ID下次启动收敛到持久化值——遥测级后果可接受丢失语义文件被删 → 下次启动铸造全新 ID丢失被接受匿名身份没有恢复价值可恢复性需要派生材料这与匿名性冲突写入失败尽力而为返回内存中的 IDSessionTelemetryBackend绝不被只读 home 阻塞上报位置Resource 属性而非每条记录属性每个批次一次足以支撑 Resource 维度聚合逐条注入会触碰 seam 契约并增大线上体积semconv 依赖不引入opentelemetry/semantic-conventions一个字符串常量不值得引入一个依赖归属包deepseek-ai/dsh-anonymous-user-id由 OTel 后端、/feedback、DeepSeek 直连请求共享消费者共享同一存储契约而不依赖某个导出器后端独立开关无任何消费方都可创建该身份DSH_TELEMETRY_DISABLED只停止遥测上报不关闭 feedback 确认或 DeepSeek 请求头源码级拆解getOrCreateAnonymousUserId()的实现机制完整实现位于 packages/identity/anonymous-user-id/src/index.ts包内只暴露一个主函数与一个文件名字符串常量export type AnonymousUserId BrandedAnonymousUserId /** File inside the harness home storing the id: a bare UUID line, no wrapper format. */ export const ANONYMOUS_USER_ID_FILE_NAME .anonymous-user-id const UUID_PATTERN /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i export function getOrCreateAnonymousUserId(options: AnonymousUserIdOptions {}): AnonymousUserId { const file join(resolveDshHome(undefined, options.env ?? process.env), ANONYMOUS_USER_ID_FILE_NAME) const cached memo.get(file) if (cached ! undefined) return cached let id readPersistedId(file) if (id undefined) { const generate options.randomUUID ?? randomUUID const created generate() as AnonymousUserId try { mkdirSync(dirname(file), { recursive: true }) writeFileSync(file, ${created}\n, { encoding: utf8, flag: wx }) id created } catch { // wx 拒绝EEXIST同时覆盖并发胜者与既有损坏文件两种情况…… id readPersistedId(file) if (id undefined) { try { writeFileSync(file, ${created}\n, utf8) } catch { // 尽力而为持久化home 不可写时也在本次运行中返回一致 ID } id created } } } memo.set(file, id) return id }完整代码见 src/index.ts。四个关键机制随机铸造ID 来自node:crypto的randomUUID()src/index.ts即标准 UUID v4。源码注释明确它绝不从 hostname、网络地址、git remote 或任何其他可识别来源派生——匿名是铸造行为本身的属性而非事后修饰。逐路径 memomemo是一个以解析后文件路径为键的Mapsrc/index.ts所以不同的测试 home / 不同的$DSH_HOME永远不会共享 ID且同一进程只触碰一次磁盘。读取校验readPersistedId读取文件后trim()并与 UUID 正则匹配src/index.ts不合法或不可读返回undefined由调用方走铸造并持久化路径。wx排他创建 失败回退首写用writeFileSync(file, ..., { flag: wx })。若抛错EEXIST等先重读文件以采纳并发胜者写入的合法 ID若重读仍无效损坏文件则回退到普通覆盖写覆盖写再失败只读 home则直接返回内存中新 ID——telemetry 与 feedback 永远不会被不可写的 home 阻塞。可测试性设计AnonymousUserIdOptions提供两个默认值钩子src/index.tsenv默认为process.env用于注入DSH_HOME与randomUUID默认为crypto.randomUUID用于模拟并发。测试套件 tests/anonymous-user-id.spec.ts 用这两个钩子覆盖了全部边界首次使用创建、持久化并返回裸 UUID 行含末尾换行home 目录缺失时自动递归创建后续调用返回持久化 ID容忍首尾空白损坏文件被新 ID 覆盖并发胜者采纳在初始读与wx写之间植入胜者文件返回的是胜者的 IDhome 无法容纳文件时返回可用 ID 且不持久化最佳努力进程生命周期 memo删除文件后仍返回同一 ID不同 home 持不同 ID默认读取process.env。存储契约一个裸 UUID 行.anonymous-user-id的存储契约被刻意保持极简裸 UUID 行 换行无 JSON 包装、无版本标记。裁决理由写得很清楚——身份是独立事实不是归档在某条遥测数据流的文件名/格式之下。读取时对整行trim()后做 UUID 校验首个写入者使用排他创建wx并发失败者重读并采纳胜者值损坏或不可读文件落入铸造并覆盖路径。memo 以解析后路径为键保证不同 home 不共享 ID。一个值得注意的开放问题记录在包 README 的 Dev Note由于契约是无版本标记的裸 UUID 行未来若要在 ID 旁增加第二个值或将行包装进容器对存量文件没有迁移方案——带版本的行格式是让这类变更变安全的一种方式。三大消费方同一 ID 贯穿遥测、反馈与请求1. OTel 会话遥测后端Resourceuser.id在 packages/session/session-telemetry-otel/src/index.ts 中后端构造LoggerProvider时通过resourceFromAttributes组装 Resourcethis.provider new LoggerProvider({ resource: resourceFromAttributes({ service.name: APP_IDENTITY.product, service.version: APP_IDENTITY.version, // OTel semconv 标准用户属性每导出批次在 Resource 上携带一次而非逐条记录 user.id: getOrCreateAnonymousUserId(), }), ... })正如裁决表上报位置一栏所述collector 按 Resource 聚合ID 在进程内本来就是稳定的每批次一次足矣逐条注入不仅多余还会触碰 session-telemetry seam 契约并增大线上传输体积。同时该包没有引入opentelemetry/semantic-conventions依赖——一个user.id字符串常量不值得增加一个依赖。调用发生在后端构造函数内这与同步 IO memo的裁决直接呼应构造函数同步插件加载流程不被重塑。2./feedback命令确认消息中的匿名用户行在 packages/feedback/command-feedback/src/index.ts 中feedback 成功确认消息形如Feedback recorded for session {sessionId} Anonymous user: {userId}. {sharingDisclosure(telemetry)}这样运维方可以把确认消息与导出的遥测记录关联起来。设计上feedback 包只依赖身份能力本身而非遥测 seam 或 OTel SDK——避免了直接命令依赖可选导出器后端以及遥测导出 feedback 时形成反向依赖环的问题详见 共享 ID 决策 的备选方案分析。3. DeepSeek 直连请求x-deepseek-harness-user-id请求头在 packages/llm/llm-deepseek/src/adapter.ts 中请求头集合包含const headers { authorization: Bearer ${apiKey}, content-type: application/json, accept: text/event-stream, ...attributionHeaders(), x-deepseek-harness-user-id: String(userId), ...options.sessionId ! undefined ? { x-deepseek-harness-session-id: String(options.sessionId) } : {}, ...options.purpose compaction ? { x-deepseek-harness-compact: 1 } : {}, }这样用量可以按安装实例harness home归属。注意共享 ID 决策中的两个时序细节无效 feedback 在解析 ID 之前就被拒绝DeepSeek 适配器在凭据验证成功后才解析 ID——因此一次空命令或一次凭据失败都不会创建.anonymous-user-id文件详见 共享 ID 决策。消费方聚合图┌──────────────────────────────────────┐ │ $DSH_HOME/.anonymous-user-id (UUID) │ └──────────────────┬───────────────────┘ getOrCreateAnonymousUserId() ┌───────────────────┬───────┴──────────┬──────────────────┐ ▼ ▼ ▼ ▼ OTel Resource /feedback 确认消息 x-deepseek-harness- 未来新消费方 user.id Anonymous user: … user-id 请求头 直接导入复用备选方案回顾为何这些路被否决被否决方案一句话理由Hostname/IP 哈希派生 ID此前的旧裁决可逆就意味着不匿名随机 UUID 语义干净——用户裁决予以取代在每条记录属性上带user.idClaude Code 的形状触碰 session-telemetry seam 契约或逐条注入、增大线上体积每批次放 Resource 上已能完成聚合在/feedback需要 ID 之前就抽出共享包初版做法当时唯一真实消费方是 OTel 后端只有直连 feedback 需要同一关联 ID 时才证明抽包是合理的AppCLIEntry 读取 ID 并通过配置 patch 注入每个表层入口都要接线把运行时事实塞进部署配置混淆了两类关注点将能力放进deepseek-ai/dsh-home-pathspaths 是纯路径计算、零 IO把持久化身份能力塞进去会污染包边界其中先在 OTel 后端内实现、待出现第二个消费方再抽包的演进路径尤其值得注意它体现了共享库只有在确有两个以上真实消费者时才值得抽离的工程原则。/feedback成为第二个消费方后共享 ID 决策将所有权迁移到独立包并完整保留了既有的随机 UUID、home 解析、进程 memo、排他创建并发、损坏替换与最佳努力写语义——不改变本 Note 记录的任何存储、匿名性、并发或丢失语义。后果与运维视角一个$DSH_HOME即 OTel 数据流中的一个稳定用户不同 home 天然是不同用户且不存在跨 home 关联机制。OTel 数据流、/feedback与 DeepSeek 直连请求共享同一个.anonymous-user-id。删除.anonymous-user-id即重置身份下次启动生效home 不可写时每个进程在 home 变可写之前持有自己的内存 ID。就匿名用户 ID 部分而言default-mount Note 中身份跟进事项已由此决策闭环hostname/表面维度、脱敏规则与用量指标跟踪仍保持开放。实际操作速查观察 ID查看$DSH_HOME/.anonymous-user-id默认~/.dsh/.anonymous-user-id文件内容即裸 UUID 行。重置 ID删除该文件下次启动铸造新 ID运行中的进程因 memo 机制保持当前 ID 直到退出。多个 home为不同部署设置不同DSH_HOME即可获得相互独立、不可关联的匿名身份。在自己的包中使用来自包 README 的官方示例README.mdimport { getOrCreateAnonymousUserId } from deepseek-ai/dsh-anonymous-user-id const userId getOrCreateAnonymousUserId() // 进程生命周期内稳定遥测总开关DSH_TELEMETRY_DISABLED非空即禁用遥测上报行含0/false但它不会禁用 feedback 确认或 DeepSeek 请求头中的匿名 ID。已知限制删除后不可恢复文件丢失即铸造新匿名身份这是有意设计——恢复需要稳定的派生材料而派生材料会削弱匿名性。并发为最佳努力落在并发进程排他创建与写入完成之间狭窄窗口的读取者本次运行可能使用不同的内存 UUID后续启动会收敛到持久化值。无跨 home 身份不同$DSH_HOME之间无法关联。配置的 DeepSeek 网关会收到该 IDdsh-llm-deepseek向其解析出的baseURL含部署覆盖发送稳定请求头与遥测共享模式无关。删除文件不会重置当前进程memo 让本次运行的 ID 保持到下次启动。延伸阅读决策原始记录2026-07-31-telemetry-anonymous-user-id.md及中文版遥测默认挂载决策2026-07-31-web-telemetry-default-mount.md共享 ID 抽包决策2026-08-07-shared-feedback-telemetry-user-id.md包级说明identity/anonymous-user-id/README.md身份组地图identity/README.mdhome 路径解析util/home-paths/README.md拥有$DSH_HOME与~/.dsh的解析语义消费方源码session-telemetry-otel/src/index.ts、command-feedback/src/index.ts、llm-deepseek/src/adapter.ts【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表