免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Friend 项目 Parity Pack v0 解析:基于本地脱敏 Cassette 重放的 STT/LLM 契约测试体系

Friend 项目 Parity Pack v0 解析:基于本地脱敏 Cassette 重放的 STT/LLM 契约测试体系 Friend 项目 Parity Pack v0 解析基于本地脱敏 Cassette 重放的 STT/LLM 契约测试体系【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend本篇文章以仓库中 backend/testing/parity_pack_v0/README.md 为主干结合 backend/testing/parity_pack_v0/ 下的实现源码、backend/routers/listen/parity_capture.py 实时接线以及 backend/tests/unit/test_parity_pack_v0.py 等测试用例系统讲解 Parity Pack v0 的完整机制它如何在仅限开发dev且默认拒绝的强门禁下采集匿名的线缆观测cassette、如何通过 SHA-256 脱敏指纹保证可重放性以及如何用严格调用拓扑的播放器在无外网、无真实供应商的条件下完成 STT/LLM 回环重放。读完本文你将掌握这套采集→脱敏→重放→金标漂移流水线的每个配置项、目录契约与操作流程并能在本地复现npm run test:parity-pack-v0的整套检查。1. 定位本地重放包契约而非生产采集路径Parity Pack v0 在 README 开头就给出了明确的边界定义This is a local replay-pack contract, not a production capture path。它是一套本地重放包契约用来验证同一批请求在录制时的行为与当前代码行为保持一致parity即对等一致性而不是一条可以随时开关的生产录制通道。由此派生出的两条硬性约束贯穿全文载荷受限Pack 载荷pack payloads是受限的本地/开发产物必须禁止被提交到 Gitmust never be committed默认拒绝所有采集入口默认 deny只有显式满足三项环境变量条件才会放行。实现上整个模块由 backend/testing/parity_pack_v0/ 下的若干小文件组成各司其职文件职责whitelist.pyCaptureWhitelist从环境变量解析并执行默认拒绝门禁schema.pyCassetteIdentity与RequestFingerprint匿名身份与脱敏指纹redaction.py保守脱敏凭据键、邮箱、电话、URL 参数capture.pyCaptureTap/CaptureInvocation采集与落盘players.pySTTCassettePlayer/LLMCassettePlayer回环播放器manifest.pymanifest.json的构建与写盘gold.py金标双跑、漂移报告runner.py无外网运行原语与 Fake 命中记账matrix.py六个合成 overlay 的名称矩阵rewrite.py未来重写二进制的描述符槽位2. 开发采集门禁三项环境变量与默认拒绝采集的唯一入口是CaptureWhitelist。README 明确CaptureTap使用CaptureWhitelist.from_environ()在序列化任何 cassette 字节之前调用allows(principal_id)默认 deny。允许采集必须同时满足三个设置OMI_ENV_STAGEdev OMI_PARITY_PACK_CAPTURE1 OMI_PARITY_PACK_ALLOWED_PRINCIPALSsynthetic-user-1,synthetic-device-2对照 whitelist.py 的源码门禁判定逻辑非常直接def allows(self, principal_id: str | None) - bool: Default deny: only explicit dev enablement plus an exact allow-list hit. return bool(self.enabled and self.environment dev and principal_id and principal_id in self.principal_ids)即允许条件为OMI_PARITY_PACK_CAPTURE取值为1/true/yes大小写不敏感且OMI_ENV_STAGE dev且principal 非空且严格命中OMI_PARITY_PACK_ALLOWED_PRINCIPALS逗号分隔列表。任何一个条件不满足start()都会拒绝并只保留有界的 lane/reason 元数据。backend/tests/unit/test_parity_pack_v0.py 用测试锁死了这一行为prod阶段即使 capture 置 1 也拒绝空环境默认拒绝特别地OMI_ENV不是规范运行时阶段变量——只有OMI_ENV_STAGE能开启 dev 采集test_capture_whitelist_ignores_non_canonical_env_var。采集命名遵守匿名规则在CassetteIdentity中使用匿名会话/事件标识符请求指纹是规范化脱敏请求结构的 SHA-256 摘要auth、cookies、keys、签名 URL 参数以及尽力而为的邮箱/电话字符串都会在生成摘要前被移除或打码。严禁把原始请求数据写进 manifest、报告或 Git。3. 匿名身份与脱敏指纹契约的地基3.1 CassetteIdentity六元组身份schema.py 中的CassetteIdentity是单次供应商调用的身份元组六个字段字段说明anon_session匿名会话标识必须已是匿名、稳定的标识符provider_lane供应商通道如stt、llm、memoryroute_or_model路由或模型名call_ordinal调用序号≥ 0retry_attempt重试次数≥ 0parent_event_anon父事件的匿名标识构造器在__post_init__中强制四个文本字段必须为非空字符串call_ordinal/retry_attempt必须非负。key()方法对身份的规范 JSON 做 SHA-256生成 64 位、文件系统安全的稳定键名——不包含任何请求内容。测试 test_identity_tuple_is_stable_and_complete 验证了as_dict()的字段完整性与key()的长度。3.2 RequestFingerprintcanonical redacted 的一次性摘要RequestFingerprint.from_request()的流程是先用redact_value(value, drop_sensitiveTrue)递归移除敏感字段而不是打码再对规范化 JSONsort_keysTrue, separators(,,:)做 SHA-256算法标识为sha256-canonical-redacted-v1。摘要本身只用于比对绝不写入 manifest 或报告。redaction.py 的脱敏是保守型的包含三层规则敏感键正则SENSITIVE_KEY匹配authorization、auth、cookie、token、secret、api_key、password、signature、signed、private_key、credential、bearer、jwt等带词边界。_is_sensitive_key()先把 camelCase 归一化为 snake_case所以accessToken、clientSecret与access_token、access-token一样能被捕获URL剥离 query/fragment仅保留 scheme/netloc/path并追加[REDACTED_URL_PARAMS]标记邮箱/电话[REDACTED_EMAIL]、[REDACTED_PHONE]打码。redact_value的drop_sensitive参数决定敏感值的处理方式为True指纹场景时直接省略为False报告场景时替换为稳定的[REDACTED]标记。测试 test_fingerprint_is_canonical_and_never_contains_sensitive_values 证明键序不同的两个请求会得到相同指纹而指纹/脱敏结果中不会残留topsecret或电话号码。3.3 特例audio_b64 不做过度脱敏capture.py 的CaptureInvocation.observe()有一个容易被忽略的关键细节base64 音频是不透明二进制而非自由文本对其跑文本模式脱敏可能把一段 base64 数字误判成电话号码、从而破坏可重放音频事件。因此实现中对payload.get(audio_b64)原样保留仅对其余字段走redact_value。4. CaptureTap三类方向的时序观测CaptureTap.start()在通过白名单后创建一次调用invocation。CaptureInvocation.observe()的观测边界记录三类 wire 事件client客户端方向的观测如解码后的客户端音频outbound出站到供应商的观测如发送给 STT 的 socket 数据inbound供应商回调方向的观测如 STT 返回的 transcript。每个CassetteEvent由direction、dt_ms、payload组成dt_ms是相对毫秒相对 invocation 起点的时间差由可注入的time.monotonic时钟计算取整到毫秒且必须非负构造器强制校验。persist()把schema_version: 1、identity、fingerprint 与事件数组写成紧凑 JSONsort_keysTrue, separators(,,:)到cassettes/identity-key.json并可选追加surface/source顶层判别字段见第 6 节。白名单未命中时CaptureTap.start()不写任何 cassette 字节只把{provider_lane: ..., reason: whitelist_miss}追加进有界元数据列表denied_metadata并裁剪到最近 100 条del self.denied_metadata[:-100]。5. 实时路径接线/v4/listen 与 ListenParityCaptureREADME 指出/v4/listen运行时只在 Firebase WebSocket 认证完成、STT 供应商选定之后才会创建routers.listen.parity_capture.ListenParityCapture。它用 Firebase UID仅做CaptureWhitelist的精确比对在 cassette 创建前派生匿名会话/事件标识符_anonymous_id对 UIDsession 做 SHA-256 并截取前 32 位十六进制见 parity_capture.py记录解码后的客户端音频、成功的 STT socket 发送、以及供应商 transcript 回调在正常 listen 会话 teardown 期间持久化。parity_capture.py 的from_environ()把门禁决策细化为可观测的分类_allowlist_decision_reasonallowed、capture_disabled、stage_not_dev、principal_missing、allowlist_miss并同步写入遥测。整条链路设计为采集绝不拖垮 listen 会话初始化、观察、持久化任何一步异常都只记 warning 日志并降级为无操作fail-closed但服务本身继续。5.1 第四个变量OMI_PARITY_PACK_ROOTREADME 强调第四个、必需且仅操作者可设的变量其值必须是仓库之外的绝对路径OMI_PARITY_PACK_ROOT/absolute/restricted/local/parity-pack没有默认 root。缺失、相对路径或位于仓库内的 root 一律禁用。源码_capture_root()落实了这一规则先要求非空、expanduser后必须is_absolute()再用root.resolve().relative_to(repository_root)探测——若解析后的 root 落在仓库目录树内则返回None禁用。因此以下情况全部判定为禁用未设置OMI_PARITY_PACK_ROOTroot 是相对路径root 位于仓库内OMI_ENV_STAGE不是dev缺少OMI_PARITY_PACK_CAPTURE1白名单未命中。任何 Helm 或生产默认配置都不会开启这条路径。由于本地 cassette 可能包含受限的音频/transcript 事件载荷必须把 root 放在 Git 之外且永远不要把它附到 PR 上。5.2 有界性约束采集不是无限录音parity_capture.py 与 live_capture.py 共享一组采集上限常量常量值含义MAX_CAPTURE_EVENTS1 000单次 invocation 最多事件数MAX_CAPTURE_AUDIO_BYTES8 MiB音频总字节上限MAX_CAPTURE_TEXT_CHARS16 384单段文本截断长度MAX_CAPTURE_SEQUENCE_ITEMS1 000序列元素截断数量_can_observe()在分配 base64 表示之前就检查原始输入len(audio)超限即置_limit_reached并丢弃后续事件防止把 listen 会话的内存打爆。6. 更多采集面SurfaceParityCapture 与 surface 判别表SurfaceParityCapture复用同一套门禁/导出器覆盖额外的记忆形成memory-forming表面。它在 cassette 文档上追加可选的顶层判别字段surface/source但保持 v1 的 identity、fingerprint、event 契约不变因此存量播放器无需改动。README 的映射表如下surfacesource采集接缝pttdesktop_ptt_http、desktop_ptt_streamDesktop PCM PTT 与实时 PTT STT有界音频 transcript 事件screendesktop_screen_activity_sync纯文本屏幕活动/上下文同步无视频或 embedding 向量conversation_finalizationconversation_sourcetranscript 输入、记忆抽取结果与已接受记忆memory_writev3_memory_create、v3_memory_batch_create、integration_app、twitter_persona手动/API、集成与社交记忆写入者memory_importv3_memory_import_batch有界导入产物与摄取结果非原始媒体surface_parity_capture.py 提供了这些表面的通用实现from_environ()内部同样走_capture_root→CaptureTap.start()任何初始化异常都降级为禁用实例。对记忆载荷还定义了窄化函数_memory_payload()只保留id、content截断到 16K、category、visibility、source_type五个字段——绝不把完整证据链塞进 cassette。capture_memory_write()则是一次写入一个memory-write cassettes 的便捷入口request仅含memory_count与source不暴露 UID。对应的单元测试在 test_surface_parity_capture.py。7. 开发部署emptyDir 挂载与私有 GCS 导出README 描述了开发 listen 部署的落地方式挂载/var/omi-parity-pack作为emptyDir仅供显式白名单的 dogfood 主体验证者使用。Pod 的fsGroup: 10001与非 root 后端镜像组一致使 listener 能创建并持久化cassettes/目录随后尽力而为地把 cassette JSON 导出到私有开发桶gs://based-hardware-dev-omi-parity-pack-v0/parity-pack/v0/cassettes/identity-key.json导出是fail-open的即使 GCS 宕机listen 会话也照常继续persist()内对导出异常只记 warning。README 给出的离线重放下载命令gcloud storage cp -r \ gs://based-hardware-dev-omi-parity-pack-v0/parity-pack/v0 \ ./omi-parity-pack-dogfood/ # Point OMI_PARITY_PACK_ROOT at the local tree (or compose a pack with # manifest.json as required by this README), then: npm run test:parity-pack-v0两条红线永远不要把 cassettes 提升到生产存储也不要提交进仓库emptyDir 是临时存储pod 重启后、成功导出之前的数据会丢失。7.1 可观测性零初始化计数与日志标记开发 listen 采集暴露零初始化的 Prometheus 计数器omi_parity_pack_capture_events_total{stage,outcome,reason_class}及配套的parity_pack_capture_event日志标记。其封闭标签closed labels用于区分接受的 listen、白名单决策、采集初始化、cassette 持久化、GCS 导出尝试/成功/失败。这些事件从不包含主体验证者或会话标识、载荷、凭据或 cassette 对象路径非 dev 运行时既不递增该计数器也不写该日志。相关实现在 parity_telemetry.py。8. 重放播放器严格调用拓扑的回环适配器采集之后是重放。STTCassettePlayer与LLMCassettePlayer是 wire-oracle fakes 的回调式回环适配器loopback adapters。两者共享有序的InvocationTopologyplay()先验证完整身份与规范化脱敏请求指纹再依次产出录制的PlayedEventdirection / dt_ms / payload并交给emit回调assert_complete()对未使用的 cassette判定失败unused cassettes: N错位、乱序或多余调用立即失败CassetteTopologyError。players.py 的具体行为情形错误信息含义已消费完但还有新调用extra invocation: key多出的调用身份与当前位置 cassette 不一致out-of-order cassette: expected... actual...乱序请求指纹不一致cassette request fingerprint mismatch请求不匹配player 通道不匹配lane player cannot play lane通道错配消费后仍有剩余unused cassettes: N未用完的 cassetteSTTCassettePlayer.lane sttLLMCassettePlayer.lane llm互不串位。_read()还会校验schema_version 1不支持的版本直接抛ValueError。cassettes 始终是受限的本地/dev 输入禁止提交。9. 包目录布局与 manifest.json 契约README 规定的受限本地包目录结构restricted-local-pack/ manifest.json # hashes case descriptors only inputs/case.json # referenced by inputs_ref cassettes/identity-key.json # referenced by cassette_refsmanifest.json记录schema 版本、pack_id、产物哈希以及每个 case的输入/cassette 引用、预期结果expected outcomes、不变量 ID、匿名 cassette 身份、脱敏请求指纹仅摘要。由 manifest.py 的build_manifest()生成schema_version: 1、pack_id、cases、artifact_hashes四段sha256_file()按 1 MiB 分块计算产物哈希。测试 test_manifest_has_references_hashes_outcomes_and_invariants 验证了 case 中invariant_ids、artifact_hashes的完整性。基础检查命令是npm run test:parity-pack-v0该脚本在仓库根 package.json 中映射到 backend/testing/parity_pack_v0/run.sh后者在仓库根执行PYTHONPATHbackend的 pytest覆盖 test_parity_pack_v0.py 与 test_parity_pack_v0_stage3.py 两组测试。10. 金标、漂移与重写槽10.1 double_run_gold先双跑再冻结gold.py 的double_run_gold()在更新金标gold前把每个 case跑两遍两次结果摘要不同 → 直接抛AssertionErrornon-deterministic replay for case_id拒绝非确定性结果只有write_goldTrue才允许改写expected_outcomes把第一次结果写回 manifest普通重放只产出一份仅摘要、仅 warn的漂移报告drift_report()status: warn/ok、drift_count、逐条Drift(case_id, expected_digest, actual_digest)、enforcement: warn-only。设计意图很明确漂移永不掩盖结果、永不阻塞开发者排查它只是一个提示信号。金标变更必须显式走write_goldTrue防止普通重放以副作用方式悄悄改掉本地包的预期结果。10.2 rewrite_launch_descriptor重写二进制的显式集成槽rewrite_launch_descriptor()是为未来重写二进制预留的显式集成槽位。描述符RewriteLaunchDescriptor包含command、input_manifest、output_manifest、available其中available恒为False。README 特别强调该描述符在本仓库中刻意不可用——操作者必须自行安装并调用经批准的二进制默认名omi-replay-rewrite重放流程绝不会下载或执行任意二进制。这是对供应链安全的刻意约束只有经人工批准、显式提供的二进制才能改写 cassette 数据。11. 合成 v0 矩阵六个 overlayREADME 给出合成 v0 矩阵的六个 overlay 名称baseline、duplicate_delivery、provider_timeout、provider_error、out_of_order_events、redacted_capture。它们不包含任何真实采集载荷。对应 matrix.py 的SYNTHETIC_MATRIX每个 overlay 用delivery/provider/expected三元组描述行为预期overlaydeliveryproviderexpectedbaselinesinglerecordedfinalized正常终结duplicate_deliveryduplicaterecordedidempotent幂等provider_timeoutsingletimeoutrecoverable可恢复provider_errorsingleerrorfailed-safe安全失败out_of_order_eventsreorderedrecordedrejected拒绝redacted_capturesinglerecordedno-sensitive-payload无敏感载荷这组矩阵实际上把重放引擎要验证的六类不变量固化成了命名契约正常路径、重复投递的幂等性、供应商超时的可恢复性、供应商错误的失败安全、乱序事件的拒绝以及脱敏捕获确保无敏感载荷。12. 操作者工作流仅限 devREADME 在最后给出三步操作者工作流也是整套体系的收束本地隔离把本地包放在仓库之外绝不提交 cassettes、inputs、载荷或白名单。显式门禁设置OMI_ENV_STAGEdev、OMI_PARITY_PACK_CAPTURE1以及显式的OMI_PARITY_PACK_ALLOWED_PRINCIPALS白名单。任何其他阶段或缺失白名单都是默认拒绝且不持久化任何 cassette 字节。Hermetic 重放用已选择加入的合成/dev principal 运行应用路径然后执行npm run test:parity-pack-v0进行隔离重放。测试拒绝外网 egress 并要求 Fake 命中记账不使用任何真实供应商或生产服务。12.1 hermetic 保障如何落地runner.py 是第 3 步的技术保障deny_network()委托给仓库验证过的block_outbound_network见 backend/testing/hermetic_network.py连底层socket.connect、connect_ex和 DNS 解析都一并封死任何被拦截的 egress 都会以UnexpectedEgress断言失败。hermetic_run()在此之上叠加FakeHitRegistry每个 Fake 被调用一次记一次hit()最后由require(**expected)做精确命中计数比对多了少了都失败。清理钩子按逆序执行且彼此隔离——某个清理钩子失败不会阻断其他钩子body 异常永远优先于清理异常上报。测试 test_parity_pack_v0.py 直接导入了hermetic_run与UnexpectedEgress验证重放确实在完全离线的环境里完成。小结Parity Pack v0 是一套把隐私安全与契约测试焊死在一起的本地重放体系CaptureWhitelist的默认拒绝门禁、CassetteIdentity/RequestFingerprint的匿名与脱敏、CaptureTap三类方向的事件观测、SurfaceParityCapture的多种记忆表面、STTCassettePlayer/LLMCassettePlayer的严格拓扑重放、double_run_gold的双跑金标与 warn-only 漂移以及rewrite_launch_descriptor的安全重写槽位共同构成一条从 dev 采集到 hermetic 重放的完整闭环。所有机制都以载荷不落 Git、身份不落日志、egress 一律封禁为底线任何一环失守都会以测试失败或显式禁用收场。对需要为供应商密集型后端STT/LLM/记忆写入建立可回归、可离线验证的契约测试体系的工程团队而言这套 pack 契约与目录布局是一个可直接借鉴的范本。【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表