ARTICLE DETAIL

资讯详情

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

caveman SDK Parity 契约:一份 fixtures.json 如何成为 TS 与 Python 双 SDK 的发布门禁

caveman SDK Parity 契约:一份 fixtures.json 如何成为 TS 与 Python 双 SDK 的发布门禁 caveman SDK Parity 契约一份 fixtures.json 如何成为 TS 与 Python 双 SDK 的发布门禁【免费下载链接】caveman why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/cavemanpackages/sdk/parity目录是 caveman 仓库中跨语言 SDK 一致性parity测试的核心一个语言中立的fixtures.json被 TypeScriptcaveman-ai/sdk和 Pythoncaveman_cloud两个 SDK各自完整执行一遍。它的定位不是文档而是发布门禁release gate——只要某个字段在一个 SDK 中存在而在另一个 SDK 中缺失CI 上必然有一侧变红。读完后你将理解parity 契约文件的完整结构config、命名 header 集、operations 列表、两侧测试如何 mock 传输层并断言精确的 wire 请求与派生结果、以及维护双语言 SDK 时必须遵守的五条编辑规则。1. 定位release gate不是文档目录说明CLAUDE.md其内容与 AGENTS.md 一致开篇即给出这条契约的承重诚实属性load-bearing honesty property线协议契约只有一个东西用两种语言表达caveman-ai/sdkTS caveman_cloudPython。一个字段在一个 SDK 中存在、在另一个中缺失就会使某一侧的断言失败——因此这个文件夹是发布门禁而不是文档。目录布局非常简单fixtures.json —— 契约本体当前含 29 个 operationversion: 1runtime-policy.fixtures.json —— 运行策略客户端的配套共享 fixture由两侧 runtime-policy 测试驱动不在本文展开。两侧的驱动器语言驱动文件mock 对象TypeScriptparity.runtime.mjs全局fetchPythontest_parity.pyurllib.request.urlopen每一侧都实现了per-operation handler执行真实的SDK 调用、捕获 wire 请求、把结果规范化为 canonical snake-key 值。两侧都遍历每一个operation——缺少 handler 是失败failure绝不跳过never a skip。2. fixtures.json 的结构config 命名 header 集 operations契约文件顶部是一个固定的config即一个 Cave两侧 SDK 客户端都从它构造{ api_key: cave_live_parity_key, base_url: http://gateway.test, control_url: http://control.test, agent: parity-agent, default_workflow: parity-workflow, retention: metadata, user: parity-user-hash }接着是命名 header 集供各 operation 的expect.wire.headers以字符串引用TS 侧在fixtures[op.expect.wire.headers]解引用Python 侧同理。契约文档明确列出std_headers/std_headers_traced/std_headers_async_traced/otlp_headers四组实际 fixture 中还存在第五组std_headers_artifact_tracedartifact 上报操作使用其差异点是额外携带x-cave-artifact-envelope: value-v1与x-cave-artifact-source: tool:fetch。各组语义std_headers—— 基础六件套content-type、authorization: Bearer …、x-cave-agent、x-cave-workflow、x-cave-retention、x-cave-user-hashstd_headers_traced—— 在基础集上追加x-cave-trace-id: aaaabbbbccccddddeeeeffff00001111与x-cave-parent-span-id: 1122334455667788std_headers_async_traced—— traced 集上再追加x-cave-async: truestd_headers_artifact_traced—— traced 集上追加 artifact envelope/source 两键otlp_headers—— OTLP 导出专用content-typex-cave-api-key而非 Bearer agent/workflow/retention/user-hash。注意 trace id / span id 是硬编码常量而非 SDK 生成的值——这是无任何随机值进入断言规则的体现见第 5 节。3. 单个 operation 的解剖每个 operation 是一个对象包含四部分namehandler 映射键、input喂给真实 SDK 调用的参数、responsemock 传输层返回的罐头响应或transport: error强制字节安全直通以及expect块。expect又分wire与结果两部分expect.wiremethod、path、headers命名集或字面量对象body 二选一——精确的body或仅校验键集合的body_keys另可选base: control表示请求发往control_url而非base_urlexpect.result期望的派生结果canonical snake-key 值或result_from: response表示结果必须逐字节等于罐头响应。以tool_search为例取自 fixtures.json 第 168–217 行{ name: tool_search, input: { catalog: [ { name: search, description: Search things, input_schema: { type: object, properties: {} }, read_only: true, idempotent: true, always_load: false }, { name: fetch, description: Fetch a thing, input_schema: { type: object, properties: {} }, read_only: false, idempotent: false, always_load: true } ], query: find a thing, context: parity test, max_tools: 5, session_id: tool-session-1 }, response: { session_id: tool-session-1, tools: [{ name: search, description: Search things }], sent_schema_tokens: 120, full_schema_tokens: 840, deferred_count: 1, token_basis: estimated_bytes_div_4, method: lexical-hit-rate }, expect: { wire: { method: POST, path: /sdk/v1/tool-search, headers: std_headers, body: { tools: [ ... ], query: find a thing, context: parity test, max_tools: 5, session_id: tool-session-1 } }, result: { sent_schema_tokens: 120, full_schema_tokens: 840, deferred_count: 1, method: lexical-hit-rate, token_basis: estimated_bytes_div_4, basis: inferred, session_id: tool-session-1, saved_tokens: 720, reduction_pct: 85.7, tool_count: 1 } } }这里同时验证了三件事请求体与input语义一致地落到 wire 上派生值saved_tokens: 840-120720、reduction_pct: 85.7由 SDK 本地计算且必须精确相等结果被规范化为 snake-key。4. 29 个 operation 覆盖的 SDK 面当前 fixture 的operations列表按能力域覆盖如下顺序即文件内顺序能力域operation 名断言重点上下文装配context_assemble/context_assemble_self/context_assemble_none纯本地requestmodel/tools/system/messages、x-cave-assemblyheaderv1;slots4;prefix15506e067a67;vbb1、prefix_hash64 位 hex、breakpoints、stable_tokens: 56emit_cache_hints: self变体额外在tools[0]上产出cache_control: {type: ephemeral}并记录 breakpoint工具检索tool_search/tool_search_embeddings/tools_builder_searchPOST/sdk/v1/tool-searchembeddings ranker 原样透传builder 变体额外返回strategy与initial_tool_names压缩compress/compress_toon/compress_passthrough/compress_bad_report/compress_optimistic_ratio/compress_unchanged_false_claimPOST/sdk/v1/compresstransport: error与坏报告tokens_before: not-a-number都触发字节安全直通输出原输入、ratio: 0.0、token_count_basis: unavailable乐观 ratio服务端报 0.9 但 before/after 不符被纠正为 0.2优化计划cave_planGET/sdk/v1/cave-plan走otlp_headers计划逐字节透传result_from: response检查点checkpoint/checkpoint_expandPOST/sdk/v1/checkpointsbody 含workflow注入expand 路径做 URL 编码/sdk/v1/checkpoints/tenant%20a%2Fref%2B1/expand上下文打包context_packPOST/sdk/v1/context/packdeferred_ids: [billing]精确断言被挤出的项工具事件event_tool_call只断言body_keysduration_ms、name、options、outcome、sequence、span_type、tags、workflow容忍服务端补字段工件artifacts_page/artifacts_getpage 返回定格式字符串[cave-artifact idart_123 sourcetool:fetch typeapplication/json] … [/cave-artifact]模型调用model_create_async/model_create_traced/provider_create_untraced同样 POST/openai/v1/responses异步变体带x-cave-async: truetraced 变体带 trace 双头裸cave.openai()变体不带任何连续性 idBedrock 路由bedrock/bedrock_mantle无网络的路由描述符默认gateway_prefix: /bedrockmantle 为/bedrock/anthropicinstrumented: true、sdk_only: falseOTLP 导出otlp_export/otlp_export_tracedPOST/v1/traces完整断言 resourceSpans 结构service.name、cave.agent资源属性gen_ai.*span 属性纳秒时间戳为字符串kind: 3status.code: 1traced 变体把parentSpanId钉在注入的 root span 上预留能力jobs_unavailablecave.jobs.submit本地失败error_code: cave_async_jobs_unavailable不发任何网络请求循环熔断retry_loop_breaker/retry_loop_breaker_key_orderRetryLoopBreaker在第 2 次相同调用触发key order 变体断言{a:1,b:2}与{b:2,a:1}视为同一调用其中context_assemble*与bedrock*、jobs_unavailable、retry_loop_breaker*不带expect.wire断言逻辑会转而要求captured.length 0——纯本地 API 也必须是零 wire 请求防止误发网络调用。5. 如何强制两侧驱动的断言流水线TypeScript 侧parity.runtime.mjs用node --test运行构建后 import 自dist/installMock(op)替换globalThis.fetch捕获{url, method, headers, body}op.transport error时抛simulated transport error否则返回罐头op.responsefor (const op of fixtures.operations)为每个 operation 生成一个test(parity: op.name)handlers[op.name]不存在即断言失败错误信息为 the SDK is missing this capabilityheader 断言前用lowerKeys()把实际请求头转小写与 fixture 的命名集deepStrictEqual——因为 Python 的urllib会首字母大写化 header跨语言比较只能比键小写后的集合 值body 有body时做deepStrictEqual精确比对只有body_keys时按排序后的键集合比对结果断言result_from response取罐头响应否则取expect.result与实际结果deepStrictEqual。Python 侧test_parity.py结构完全对称pytest.mark.parametrize(op, OPS)逐条驱动patch(urllib.request.urlopen, side_effectfake_urlopen)捕获请求并在transport error时抛urllib.error.URLErrorHANDLERS字典与 TS 一一对应每个 handler 把 camel/对象结果显式映射回 canonical snake-key 字典再与期望比较。Python 侧还有一条额外守卫 test_tool_events_are_traced_while_bare_provider_calls_stay_untraced直接从 fixture 断言event_tool_call用std_headers_traced而provider_create_untraced用std_headers——把trace 内带连续性头、裸 provider 调用不带这条契约也钉死在数据上。两侧各有一个防退化守卫测试parity: fixtures cover the documented operations/test_parity_fixtures_cover_surfaceoperations长度必须 ≥ 10且每个 operation 都必须有 handler防止有人用空 fixture 或短 fixture 让门禁静默通过。TS 侧还额外验证bedrock描述符对未知 endpoint 抛/runtime or mantle/错误。6. 五条编辑规则来自契约文档必须遵守CLAUDE.md 的 Editing rules 一节给出了维护这份契约的操作规程逐条展开单边改字段/方法 → 先加 operation或 body key。加到 fixture 后另一个 SDK 的那一侧会立刻变红直到它对齐为止——That red is the point.红就是目的。也就是说变更流程是 fixture 先行红 CI 驱动另一侧实现。Header 按键小写比较Python 的urllib会把 header 首字母大写化跨语言必须匹配的是键集合 值而不是键的原始大小写。只用两种语言编码一致的取值。文档明确点名 expand 路径中避开! ( ) *JS 的encodeURIComponent与 Python 的quote对这几个字符的百分号编码行为不一致。这正是checkpoint_expand用例只使用空格、/、编码为%20、%2F、%2B两者行为一致的原因。任何随机值不得进入断言。SDK 本来会自己铸造的 idtrace id、span id全部通过 operation 的input注入并钉死在期望 header 集里——otlp_export钉 span id 用的是同一套手法。运行方式文档给出的命令是make product-test PRODUCTsdk-tsmake product-test PRODUCTsdk-python当前仓库根目录未包含 Makefile该命令应由外层构建环境提供。不依赖外层 make 时两侧测试文件头部给出了直跑方式TS 为pnpm build后node --test tests/parity.runtime.mjsPython 为在本目录直接pytest要求 Python ≥ 3.13dependencies []见 pyproject.toml。7. 与两个 SDK 文档的关系镜像约定是双向的parity 契约并不是孤立存在的——它只是把两个 SDK 文档中mirror约定机制化TS 侧CLAUDE.md的 Gotchas 写着 mirror sdk-python: every field/method exists in both, enforced by the shared parity suite — a divergence is a CI failure, not a convention slip. Change one SDK, change bothandthe fixturesPython 侧CLAUDE.md对称写着 sdk-python and sdk-ts mirror the same field names and /sdk/v1/* contract — enforced by the shared parity suite … A divergence is a CI failure。两侧文档还把若干具体契约与 fixture 对应起来x-cave-workflow永远不省略缺省unlabeled-workflow、延迟工具检索的session_id/x-cave-tool-session交接update sdk-python parity fixtures with any change、reduction_pct保留一位小数且saved_tokens是本地推导值等。理解这些对应关系后parity 目录的实际角色就很清楚它是两份 SDK 文档共享事实shared truth的唯一数据源测试代码只负责执行与断言而契约本身是可读、可 diff、可审计的 JSON。8. 小结从契约到门禁的最小闭环把整套机制压缩成一句话一个 JSON 文件 两份逐条执行它的测试。新增能力时的标准动作是在 fixtures.json 加 operation → 在 parity.runtime.mjs 与 test_parity.py 各加 handler → 跑两侧测试直到都绿。这套设计对任何维护多语言 SDK 的团队都有直接参考价值用共享 fixture 强制全覆盖缺 handler 即失败 随机值注入 编码一致性约束四件事就能让两个语言的 SDK 行为完全一致从口头约定变成 CI 里可验证的事实。【免费下载链接】caveman why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman项目地址: https://gitcode.com/GitHub_Trending/caveman1/caveman创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表